From bcb704366cb5e333a626c18c308c7e0448a8e69f Mon Sep 17 00:00:00 2001 From: toma Date: Wed, 25 Nov 2009 17:56:58 +0000 Subject: Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features. BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdenetwork@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- kopete/protocols/jabber/Makefile.am | 67 + kopete/protocols/jabber/TODO | 27 + kopete/protocols/jabber/icons/Makefile.am | 1 + .../jabber/icons/cr16-action-jabber_away.png | Bin 0 -> 360 bytes .../jabber/icons/cr16-action-jabber_chatty.png | Bin 0 -> 917 bytes .../jabber/icons/cr16-action-jabber_connecting.mng | Bin 0 -> 5825 bytes .../jabber/icons/cr16-action-jabber_group.png | Bin 0 -> 969 bytes .../jabber/icons/cr16-action-jabber_invisible.png | Bin 0 -> 1035 bytes .../jabber/icons/cr16-action-jabber_na.png | Bin 0 -> 387 bytes .../jabber/icons/cr16-action-jabber_offline.png | Bin 0 -> 828 bytes .../jabber/icons/cr16-action-jabber_online.png | Bin 0 -> 877 bytes .../jabber/icons/cr16-action-jabber_original.png | Bin 0 -> 20688 bytes .../jabber/icons/cr16-action-jabber_raw.png | Bin 0 -> 657 bytes .../jabber/icons/cr16-action-jabber_serv_off.png | Bin 0 -> 475 bytes .../jabber/icons/cr16-action-jabber_serv_on.png | Bin 0 -> 911 bytes .../jabber/icons/cr16-action-jabber_xa.png | Bin 0 -> 418 bytes .../jabber/icons/cr16-app-jabber_gateway_aim.png | Bin 0 -> 1021 bytes .../jabber/icons/cr16-app-jabber_gateway_gadu.png | Bin 0 -> 974 bytes .../icons/cr16-app-jabber_gateway_http-ws.png | Bin 0 -> 1022 bytes .../jabber/icons/cr16-app-jabber_gateway_icq.png | Bin 0 -> 904 bytes .../jabber/icons/cr16-app-jabber_gateway_irc.png | Bin 0 -> 963 bytes .../jabber/icons/cr16-app-jabber_gateway_msn.png | Bin 0 -> 982 bytes .../jabber/icons/cr16-app-jabber_gateway_qq.png | Bin 0 -> 1014 bytes .../jabber/icons/cr16-app-jabber_gateway_sms.png | Bin 0 -> 980 bytes .../jabber/icons/cr16-app-jabber_gateway_smtp.png | Bin 0 -> 882 bytes .../jabber/icons/cr16-app-jabber_gateway_tlen.png | Bin 0 -> 749 bytes .../jabber/icons/cr16-app-jabber_gateway_yahoo.png | Bin 0 -> 922 bytes .../jabber/icons/cr16-app-jabber_protocol.png | Bin 0 -> 877 bytes .../jabber/icons/cr32-app-jabber_protocol.png | Bin 0 -> 2221 bytes .../jabber/icons/cr48-app-jabber_protocol.png | Bin 0 -> 3899 bytes .../jabber/icons/hi16-action-jabber_away.png | Bin 0 -> 339 bytes .../jabber/icons/hi16-action-jabber_chatty.png | Bin 0 -> 534 bytes .../jabber/icons/hi16-action-jabber_connecting.mng | Bin 0 -> 2865 bytes .../jabber/icons/hi16-action-jabber_group.png | Bin 0 -> 548 bytes .../jabber/icons/hi16-action-jabber_invisible.png | Bin 0 -> 440 bytes .../jabber/icons/hi16-action-jabber_na.png | Bin 0 -> 392 bytes .../jabber/icons/hi16-action-jabber_offline.png | Bin 0 -> 353 bytes .../jabber/icons/hi16-action-jabber_online.png | Bin 0 -> 379 bytes .../jabber/icons/hi16-action-jabber_original.png | Bin 0 -> 72510 bytes .../jabber/icons/hi16-action-jabber_raw.png | Bin 0 -> 657 bytes .../jabber/icons/hi16-action-jabber_serv_off.png | Bin 0 -> 350 bytes .../jabber/icons/hi16-action-jabber_serv_on.png | Bin 0 -> 733 bytes .../jabber/icons/hi16-action-jabber_xa.png | Bin 0 -> 415 bytes .../jabber/icons/hi16-app-jabber_protocol.png | Bin 0 -> 379 bytes .../jabber/icons/hi32-app-jabber_protocol.png | Bin 0 -> 2500 bytes .../jabber/icons/hi48-app-jabber_protocol.png | Bin 0 -> 4882 bytes kopete/protocols/jabber/jabberaccount.cpp | 1752 +++++++++++++ kopete/protocols/jabber/jabberaccount.h | 309 +++ kopete/protocols/jabber/jabberbasecontact.cpp | 676 +++++ kopete/protocols/jabber/jabberbasecontact.h | 185 ++ kopete/protocols/jabber/jabberbookmarks.cpp | 149 ++ kopete/protocols/jabber/jabberbookmarks.h | 68 + kopete/protocols/jabber/jabberbytestream.cpp | 156 ++ kopete/protocols/jabber/jabberbytestream.h | 65 + .../protocols/jabber/jabbercapabilitiesmanager.cpp | 656 +++++ .../protocols/jabber/jabbercapabilitiesmanager.h | 211 ++ kopete/protocols/jabber/jabberchatsession.cpp | 357 +++ kopete/protocols/jabber/jabberchatsession.h | 103 + kopete/protocols/jabber/jabberchatui.rc | 19 + kopete/protocols/jabber/jabberclient.cpp | 1137 +++++++++ kopete/protocols/jabber/jabberclient.h | 600 +++++ kopete/protocols/jabber/jabberconnector.cpp | 132 + kopete/protocols/jabber/jabberconnector.h | 65 + kopete/protocols/jabber/jabbercontact.cpp | 1328 ++++++++++ kopete/protocols/jabber/jabbercontact.h | 266 ++ kopete/protocols/jabber/jabbercontactpool.cpp | 355 +++ kopete/protocols/jabber/jabbercontactpool.h | 124 + kopete/protocols/jabber/jabberfiletransfer.cpp | 326 +++ kopete/protocols/jabber/jabberfiletransfer.h | 74 + kopete/protocols/jabber/jabberformlineedit.cpp | 58 + kopete/protocols/jabber/jabberformlineedit.h | 59 + kopete/protocols/jabber/jabberformtranslator.cpp | 91 + kopete/protocols/jabber/jabberformtranslator.h | 49 + kopete/protocols/jabber/jabbergroupchatmanager.cpp | 163 ++ kopete/protocols/jabber/jabbergroupchatmanager.h | 78 + kopete/protocols/jabber/jabbergroupcontact.cpp | 378 +++ kopete/protocols/jabber/jabbergroupcontact.h | 107 + .../protocols/jabber/jabbergroupmembercontact.cpp | 168 ++ kopete/protocols/jabber/jabbergroupmembercontact.h | 80 + kopete/protocols/jabber/jabberprotocol.cpp | 345 +++ kopete/protocols/jabber/jabberprotocol.h | 164 ++ kopete/protocols/jabber/jabberresource.cpp | 171 ++ kopete/protocols/jabber/jabberresource.h | 86 + kopete/protocols/jabber/jabberresourcepool.cpp | 394 +++ kopete/protocols/jabber/jabberresourcepool.h | 129 + kopete/protocols/jabber/jabbertransport.cpp | 345 +++ kopete/protocols/jabber/jabbertransport.h | 138 + kopete/protocols/jabber/jingle/DESIGN | 121 + kopete/protocols/jabber/jingle/Makefile.am | 28 + kopete/protocols/jabber/jingle/configure.in.bot | 16 + kopete/protocols/jabber/jingle/configure.in.in | 87 + kopete/protocols/jabber/jingle/jinglesession.cpp | 72 + kopete/protocols/jabber/jingle/jinglesession.h | 94 + .../jabber/jingle/jinglesessionmanager.cpp | 205 ++ .../protocols/jabber/jingle/jinglesessionmanager.h | 89 + .../protocols/jabber/jingle/jinglevoicecaller.cpp | 376 +++ kopete/protocols/jabber/jingle/jinglevoicecaller.h | 72 + .../protocols/jabber/jingle/jinglevoicesession.cpp | 333 +++ .../protocols/jabber/jingle/jinglevoicesession.h | 70 + .../jabber/jingle/jinglevoicesessiondialog.cpp | 208 ++ .../jabber/jingle/jinglevoicesessiondialog.h | 66 + .../jabber/jingle/jinglevoicesessiondialogbase.ui | 369 +++ .../jabber/jingle/jinglewatchsessiontask.cpp | 75 + .../jabber/jingle/jinglewatchsessiontask.h | 39 + kopete/protocols/jabber/jingle/libjingle/AUTHORS | 1 + kopete/protocols/jabber/jingle/libjingle/COPYING | 25 + kopete/protocols/jabber/jingle/libjingle/ChangeLog | 4 + kopete/protocols/jabber/jingle/libjingle/INSTALL | 229 ++ .../protocols/jabber/jingle/libjingle/Makefile.am | 4 + kopete/protocols/jabber/jingle/libjingle/NEWS | 1 + kopete/protocols/jabber/jingle/libjingle/README | 59 + .../jabber/jingle/libjingle/libjingle.pro | 142 + .../jabber/jingle/libjingle/talk/Makefile.am | 1 + .../jabber/jingle/libjingle/talk/base/Makefile.am | 62 + .../jabber/jingle/libjingle/talk/base/asyncfile.h | 56 + .../libjingle/talk/base/asyncpacketsocket.cc | 83 + .../jingle/libjingle/talk/base/asyncpacketsocket.h | 63 + .../jingle/libjingle/talk/base/asyncsocket.h | 91 + .../jingle/libjingle/talk/base/asynctcpsocket.cc | 197 ++ .../jingle/libjingle/talk/base/asynctcpsocket.h | 68 + .../jingle/libjingle/talk/base/asyncudpsocket.cc | 83 + .../jingle/libjingle/talk/base/asyncudpsocket.h | 59 + .../jabber/jingle/libjingle/talk/base/base64.cc | 194 ++ .../jabber/jingle/libjingle/talk/base/base64.h | 29 + .../jabber/jingle/libjingle/talk/base/basicdefs.h | 53 + .../jabber/jingle/libjingle/talk/base/basictypes.h | 85 + .../jingle/libjingle/talk/base/bytebuffer.cc | 165 ++ .../jabber/jingle/libjingle/talk/base/bytebuffer.h | 71 + .../jabber/jingle/libjingle/talk/base/byteorder.h | 63 + .../jabber/jingle/libjingle/talk/base/common.h | 231 ++ .../jingle/libjingle/talk/base/criticalsection.h | 120 + .../jabber/jingle/libjingle/talk/base/host.cc | 99 + .../jabber/jingle/libjingle/talk/base/host.h | 59 + .../jabber/jingle/libjingle/talk/base/jtime.cc | 77 + .../jabber/jingle/libjingle/talk/base/jtime.h | 47 + .../jabber/jingle/libjingle/talk/base/linked_ptr.h | 138 + .../jabber/jingle/libjingle/talk/base/logging.h | 222 ++ .../jabber/jingle/libjingle/talk/base/md5.h | 45 + .../jabber/jingle/libjingle/talk/base/md5c.c | 256 ++ .../jingle/libjingle/talk/base/messagequeue.cc | 321 +++ .../jingle/libjingle/talk/base/messagequeue.h | 164 ++ .../jabber/jingle/libjingle/talk/base/network.cc | 382 +++ .../jabber/jingle/libjingle/talk/base/network.h | 136 + .../libjingle/talk/base/physicalsocketserver.cc | 1117 ++++++++ .../libjingle/talk/base/physicalsocketserver.h | 80 + .../jabber/jingle/libjingle/talk/base/proxyinfo.h | 52 + .../jabber/jingle/libjingle/talk/base/scoped_ptr.h | 259 ++ .../jabber/jingle/libjingle/talk/base/sigslot.h | 2700 ++++++++++++++++++++ .../jabber/jingle/libjingle/talk/base/socket.h | 158 ++ .../jingle/libjingle/talk/base/socketadapters.cc | 1130 ++++++++ .../jingle/libjingle/talk/base/socketadapters.h | 181 ++ .../jingle/libjingle/talk/base/socketaddress.cc | 267 ++ .../jingle/libjingle/talk/base/socketaddress.h | 154 ++ .../libjingle/talk/base/socketaddresspair.cc | 58 + .../jingle/libjingle/talk/base/socketaddresspair.h | 58 + .../jingle/libjingle/talk/base/socketfactory.h | 50 + .../jingle/libjingle/talk/base/socketserver.h | 53 + .../jabber/jingle/libjingle/talk/base/stl_decl.h | 85 + .../jingle/libjingle/talk/base/stringutils.h | 266 ++ .../jabber/jingle/libjingle/talk/base/task.cc | 238 ++ .../jabber/jingle/libjingle/talk/base/task.h | 186 ++ .../jingle/libjingle/talk/base/taskrunner.cc | 92 + .../jabber/jingle/libjingle/talk/base/taskrunner.h | 64 + .../jabber/jingle/libjingle/talk/base/thread.cc | 273 ++ .../jabber/jingle/libjingle/talk/base/thread.h | 141 + .../jabber/jingle/libjingle/talk/base/winping.h | 101 + .../jingle/libjingle/talk/examples/Makefile.am | 1 + .../libjingle/talk/examples/call/Makefile.am | 16 + .../jingle/libjingle/talk/examples/call/call.pro | 19 + .../libjingle/talk/examples/call/call_main.cc | 62 + .../libjingle/talk/examples/call/callclient.cc | 390 +++ .../libjingle/talk/examples/call/callclient.h | 88 + .../jingle/libjingle/talk/examples/call/console.cc | 196 ++ .../jingle/libjingle/talk/examples/call/console.h | 82 + .../talk/examples/call/presenceouttask.cc | 148 ++ .../libjingle/talk/examples/call/presenceouttask.h | 46 + .../talk/examples/call/presencepushtask.cc | 172 ++ .../talk/examples/call/presencepushtask.h | 44 + .../jingle/libjingle/talk/examples/call/status.h | 213 ++ .../libjingle/talk/examples/login/Makefile.am | 15 + .../libjingle/talk/examples/login/login_main.cc | 63 + .../libjingle/talk/examples/login/xmppauth.cc | 93 + .../libjingle/talk/examples/login/xmppauth.h | 71 + .../libjingle/talk/examples/login/xmpppump.cc | 73 + .../libjingle/talk/examples/login/xmpppump.h | 73 + .../libjingle/talk/examples/login/xmppsocket.cc | 144 ++ .../libjingle/talk/examples/login/xmppsocket.h | 63 + .../libjingle/talk/examples/login/xmppthread.cc | 80 + .../libjingle/talk/examples/login/xmppthread.h | 57 + .../jabber/jingle/libjingle/talk/p2p/Makefile.am | 1 + .../jingle/libjingle/talk/p2p/base/Makefile.am | 47 + .../jingle/libjingle/talk/p2p/base/candidate.h | 118 + .../jingle/libjingle/talk/p2p/base/helpers.cc | 129 + .../jingle/libjingle/talk/p2p/base/helpers.h | 51 + .../jingle/libjingle/talk/p2p/base/p2psocket.cc | 910 +++++++ .../jingle/libjingle/talk/p2p/base/p2psocket.h | 164 ++ .../jabber/jingle/libjingle/talk/p2p/base/port.cc | 869 +++++++ .../jabber/jingle/libjingle/talk/p2p/base/port.h | 367 +++ .../jingle/libjingle/talk/p2p/base/portallocator.h | 91 + .../jingle/libjingle/talk/p2p/base/relayport.cc | 640 +++++ .../jingle/libjingle/talk/p2p/base/relayport.h | 93 + .../jingle/libjingle/talk/p2p/base/relayserver.cc | 657 +++++ .../jingle/libjingle/talk/p2p/base/relayserver.h | 210 ++ .../jingle/libjingle/talk/p2p/base/relayserver.pro | 14 + .../libjingle/talk/p2p/base/relayserver_main.cc | 75 + .../jingle/libjingle/talk/p2p/base/session.cc | 421 +++ .../jingle/libjingle/talk/p2p/base/session.h | 140 + .../libjingle/talk/p2p/base/sessiondescription.h | 42 + .../jingle/libjingle/talk/p2p/base/sessionid.h | 94 + .../libjingle/talk/p2p/base/sessionmanager.cc | 173 ++ .../libjingle/talk/p2p/base/sessionmanager.h | 86 + .../libjingle/talk/p2p/base/sessionmessage.h | 133 + .../libjingle/talk/p2p/base/socketmanager.cc | 273 ++ .../jingle/libjingle/talk/p2p/base/socketmanager.h | 101 + .../jabber/jingle/libjingle/talk/p2p/base/stun.cc | 576 +++++ .../jabber/jingle/libjingle/talk/p2p/base/stun.h | 364 +++ .../jingle/libjingle/talk/p2p/base/stunport.cc | 171 ++ .../jingle/libjingle/talk/p2p/base/stunport.h | 72 + .../jingle/libjingle/talk/p2p/base/stunrequest.cc | 198 ++ .../jingle/libjingle/talk/p2p/base/stunrequest.h | 126 + .../jingle/libjingle/talk/p2p/base/stunserver.cc | 160 ++ .../jingle/libjingle/talk/p2p/base/stunserver.h | 73 + .../jingle/libjingle/talk/p2p/base/stunserver.pro | 14 + .../libjingle/talk/p2p/base/stunserver_main.cc | 66 + .../jingle/libjingle/talk/p2p/base/tcpport.cc | 250 ++ .../jingle/libjingle/talk/p2p/base/tcpport.h | 116 + .../jingle/libjingle/talk/p2p/base/udpport.cc | 117 + .../jingle/libjingle/talk/p2p/base/udpport.h | 81 + .../jingle/libjingle/talk/p2p/client/Makefile.am | 11 + .../talk/p2p/client/basicportallocator.cc | 667 +++++ .../libjingle/talk/p2p/client/basicportallocator.h | 172 ++ .../libjingle/talk/p2p/client/sessionclient.cc | 545 ++++ .../libjingle/talk/p2p/client/sessionclient.h | 104 + .../libjingle/talk/p2p/client/socketmonitor.cc | 149 ++ .../libjingle/talk/p2p/client/socketmonitor.h | 85 + .../jingle/libjingle/talk/session/Makefile.am | 3 + .../libjingle/talk/session/phone/Makefile.am | 18 + .../libjingle/talk/session/phone/audiomonitor.cc | 119 + .../libjingle/talk/session/phone/audiomonitor.h | 73 + .../jingle/libjingle/talk/session/phone/call.cc | 258 ++ .../jingle/libjingle/talk/session/phone/call.h | 97 + .../libjingle/talk/session/phone/channelmanager.cc | 203 ++ .../libjingle/talk/session/phone/channelmanager.h | 81 + .../talk/session/phone/linphonemediaengine.cc | 170 ++ .../talk/session/phone/linphonemediaengine.h | 75 + .../libjingle/talk/session/phone/mediachannel.h | 55 + .../libjingle/talk/session/phone/mediaengine.h | 95 + .../talk/session/phone/phonesessionclient.cc | 267 ++ .../talk/session/phone/phonesessionclient.h | 122 + .../talk/session/phone/portaudiomediaengine.cc | 331 +++ .../talk/session/phone/portaudiomediaengine.h | 69 + .../libjingle/talk/session/phone/voicechannel.cc | 331 +++ .../libjingle/talk/session/phone/voicechannel.h | 129 + .../jingle/libjingle/talk/session/receiver.h | 72 + .../libjingle/talk/session/sessionsendtask.h | 111 + .../jingle/libjingle/talk/third_party/Makefile.am | 1 + .../talk/third_party/mediastreamer/Makefile.am | 92 + .../talk/third_party/mediastreamer/Makefile.ms | 34 + .../talk/third_party/mediastreamer/README | 3 + .../talk/third_party/mediastreamer/affine.h | 43 + .../talk/third_party/mediastreamer/alsacard.c | 640 +++++ .../talk/third_party/mediastreamer/alsacard.h | 50 + .../talk/third_party/mediastreamer/audiostream.c | 343 +++ .../talk/third_party/mediastreamer/g711common.h | 171 ++ .../talk/third_party/mediastreamer/hpuxsndcard.c | 301 +++ .../talk/third_party/mediastreamer/jackcard.c | 574 +++++ .../talk/third_party/mediastreamer/jackcard.h | 81 + .../talk/third_party/mediastreamer/mediastream.h | 130 + .../libjingle/talk/third_party/mediastreamer/ms.c | 342 +++ .../libjingle/talk/third_party/mediastreamer/ms.h | 81 + .../talk/third_party/mediastreamer/msAlawdec.c | 132 + .../talk/third_party/mediastreamer/msAlawdec.h | 65 + .../talk/third_party/mediastreamer/msAlawenc.c | 124 + .../talk/third_party/mediastreamer/msAlawenc.h | 64 + .../talk/third_party/mediastreamer/msGSMdecoder.h | 64 + .../talk/third_party/mediastreamer/msGSMencoder.h | 61 + .../third_party/mediastreamer/msLPC10decoder.h | 64 + .../third_party/mediastreamer/msLPC10encoder.h | 74 + .../talk/third_party/mediastreamer/msMUlawdec.c | 130 + .../talk/third_party/mediastreamer/msMUlawdec.h | 66 + .../talk/third_party/mediastreamer/msMUlawenc.c | 99 + .../talk/third_party/mediastreamer/msMUlawenc.h | 63 + .../talk/third_party/mediastreamer/msavdecoder.h | 87 + .../talk/third_party/mediastreamer/msavencoder.h | 90 + .../talk/third_party/mediastreamer/msbuffer.c | 94 + .../talk/third_party/mediastreamer/msbuffer.h | 75 + .../talk/third_party/mediastreamer/mscodec.c | 250 ++ .../talk/third_party/mediastreamer/mscodec.h | 67 + .../talk/third_party/mediastreamer/mscopy.c | 96 + .../talk/third_party/mediastreamer/mscopy.h | 61 + .../talk/third_party/mediastreamer/msfdispatcher.c | 94 + .../talk/third_party/mediastreamer/msfdispatcher.h | 61 + .../talk/third_party/mediastreamer/msfifo.c | 168 ++ .../talk/third_party/mediastreamer/msfifo.h | 73 + .../talk/third_party/mediastreamer/msfilter.c | 537 ++++ .../talk/third_party/mediastreamer/msfilter.h | 201 ++ .../talk/third_party/mediastreamer/msilbcdec.c | 194 ++ .../talk/third_party/mediastreamer/msilbcdec.h | 72 + .../talk/third_party/mediastreamer/msilbcenc.c | 244 ++ .../talk/third_party/mediastreamer/msilbcenc.h | 84 + .../talk/third_party/mediastreamer/msnosync.c | 82 + .../talk/third_party/mediastreamer/msnosync.h | 60 + .../talk/third_party/mediastreamer/msossread.c | 148 ++ .../talk/third_party/mediastreamer/msossread.h | 77 + .../talk/third_party/mediastreamer/msosswrite.c | 247 ++ .../talk/third_party/mediastreamer/msosswrite.h | 78 + .../talk/third_party/mediastreamer/msqdispatcher.c | 91 + .../talk/third_party/mediastreamer/msqdispatcher.h | 60 + .../talk/third_party/mediastreamer/msqueue.c | 56 + .../talk/third_party/mediastreamer/msqueue.h | 49 + .../talk/third_party/mediastreamer/msread.c | 182 ++ .../talk/third_party/mediastreamer/msread.h | 80 + .../talk/third_party/mediastreamer/msringplayer.c | 246 ++ .../talk/third_party/mediastreamer/msringplayer.h | 81 + .../talk/third_party/mediastreamer/msrtprecv.c | 163 ++ .../talk/third_party/mediastreamer/msrtprecv.h | 80 + .../talk/third_party/mediastreamer/msrtpsend.c | 211 ++ .../talk/third_party/mediastreamer/msrtpsend.h | 85 + .../talk/third_party/mediastreamer/mssdlout.h | 64 + .../talk/third_party/mediastreamer/mssoundread.c | 39 + .../talk/third_party/mediastreamer/mssoundread.h | 80 + .../talk/third_party/mediastreamer/mssoundwrite.c | 39 + .../talk/third_party/mediastreamer/mssoundwrite.h | 80 + .../talk/third_party/mediastreamer/msspeexdec.c | 218 ++ .../talk/third_party/mediastreamer/msspeexdec.h | 69 + .../talk/third_party/mediastreamer/msspeexenc.c | 192 ++ .../talk/third_party/mediastreamer/msspeexenc.h | 66 + .../talk/third_party/mediastreamer/mssync.c | 193 ++ .../talk/third_party/mediastreamer/mssync.h | 136 + .../talk/third_party/mediastreamer/mstimer.c | 114 + .../talk/third_party/mediastreamer/mstimer.h | 68 + .../mediastreamer/mstruespeechdecoder.h | 55 + .../mediastreamer/mstruespeechencoder.h | 62 + .../talk/third_party/mediastreamer/msutils.h | 61 + .../talk/third_party/mediastreamer/msv4l.h | 96 + .../talk/third_party/mediastreamer/msvideosource.h | 74 + .../talk/third_party/mediastreamer/mswrite.c | 121 + .../talk/third_party/mediastreamer/mswrite.h | 63 + .../talk/third_party/mediastreamer/osscard.c | 495 ++++ .../talk/third_party/mediastreamer/osscard.h | 47 + .../talk/third_party/mediastreamer/portaudiocard.c | 315 +++ .../talk/third_party/mediastreamer/portaudiocard.h | 35 + .../talk/third_party/mediastreamer/sndcard.c | 209 ++ .../talk/third_party/mediastreamer/sndcard.h | 143 ++ .../talk/third_party/mediastreamer/waveheader.h | 111 + .../jingle/libjingle/talk/xmllite/Makefile.am | 18 + .../jabber/jingle/libjingle/talk/xmllite/qname.cc | 167 ++ .../jabber/jingle/libjingle/talk/xmllite/qname.h | 87 + .../jingle/libjingle/talk/xmllite/xmlbuilder.cc | 151 ++ .../jingle/libjingle/talk/xmllite/xmlbuilder.h | 79 + .../jingle/libjingle/talk/xmllite/xmlconstants.cc | 65 + .../jingle/libjingle/talk/xmllite/xmlconstants.h | 61 + .../jingle/libjingle/talk/xmllite/xmlelement.cc | 491 ++++ .../jingle/libjingle/talk/xmllite/xmlelement.h | 231 ++ .../jingle/libjingle/talk/xmllite/xmlnsstack.cc | 205 ++ .../jingle/libjingle/talk/xmllite/xmlnsstack.h | 62 + .../jingle/libjingle/talk/xmllite/xmlparser.cc | 250 ++ .../jingle/libjingle/talk/xmllite/xmlparser.h | 108 + .../jingle/libjingle/talk/xmllite/xmlprinter.cc | 190 ++ .../jingle/libjingle/talk/xmllite/xmlprinter.h | 49 + .../jabber/jingle/libjingle/talk/xmpp/Makefile.am | 34 + .../jingle/libjingle/talk/xmpp/asyncsocket.h | 85 + .../jabber/jingle/libjingle/talk/xmpp/constants.cc | 331 +++ .../jabber/jingle/libjingle/talk/xmpp/constants.h | 300 +++ .../jabber/jingle/libjingle/talk/xmpp/jid.cc | 477 ++++ .../jabber/jingle/libjingle/talk/xmpp/jid.h | 144 ++ .../jingle/libjingle/talk/xmpp/plainsaslhandler.h | 80 + .../jingle/libjingle/talk/xmpp/prexmppauth.h | 85 + .../libjingle/talk/xmpp/saslcookiemechanism.h | 67 + .../jingle/libjingle/talk/xmpp/saslhandler.h | 59 + .../jingle/libjingle/talk/xmpp/saslmechanism.cc | 68 + .../jingle/libjingle/talk/xmpp/saslmechanism.h | 74 + .../libjingle/talk/xmpp/saslplainmechanism.h | 65 + .../jingle/libjingle/talk/xmpp/xmppclient.cc | 372 +++ .../jabber/jingle/libjingle/talk/xmpp/xmppclient.h | 157 ++ .../libjingle/talk/xmpp/xmppclientsettings.h | 94 + .../jabber/jingle/libjingle/talk/xmpp/xmppengine.h | 332 +++ .../jingle/libjingle/talk/xmpp/xmppengineimpl.cc | 480 ++++ .../jingle/libjingle/talk/xmpp/xmppengineimpl.h | 262 ++ .../libjingle/talk/xmpp/xmppengineimpl_iq.cc | 279 ++ .../jingle/libjingle/talk/xmpp/xmpplogintask.cc | 357 +++ .../jingle/libjingle/talk/xmpp/xmpplogintask.h | 95 + .../jingle/libjingle/talk/xmpp/xmpppassword.h | 163 ++ .../jingle/libjingle/talk/xmpp/xmppstanzaparser.cc | 104 + .../jingle/libjingle/talk/xmpp/xmppstanzaparser.h | 96 + .../jabber/jingle/libjingle/talk/xmpp/xmpptask.cc | 168 ++ .../jabber/jingle/libjingle/talk/xmpp/xmpptask.h | 113 + kopete/protocols/jabber/jingle/voicecaller.h | 96 + kopete/protocols/jabber/kioslave/Makefile.am | 25 + kopete/protocols/jabber/kioslave/jabberdisco.cpp | 399 +++ kopete/protocols/jabber/kioslave/jabberdisco.h | 82 + .../protocols/jabber/kioslave/jabberdisco.protocol | 53 + kopete/protocols/jabber/kopete_jabber.desktop | 79 + .../jabber/libiris/001_last_activity.patch | 113 + .../jabber/libiris/002_offline_event.patch | 17 + .../jabber/libiris/003_case_insensitive_jid.patch | 14 + kopete/protocols/jabber/libiris/004_xhtml_im.patch | 266 ++ .../libiris/005_join_muc_with_password.patch | 163 ++ .../jabber/libiris/006_private_storage.patch | 130 + .../protocols/jabber/libiris/007_chatstates.patch | 132 + .../jabber/libiris/008_chatstatesfix.patch | 38 + kopete/protocols/jabber/libiris/Makefile.am | 2 + .../jabber/libiris/README_BEFORE_COMMITTING | 21 + .../protocols/jabber/libiris/cutestuff/Makefile.am | 1 + kopete/protocols/jabber/libiris/cutestuff/README | 13 + kopete/protocols/jabber/libiris/cutestuff/TODO | 25 + .../jabber/libiris/cutestuff/network/Makefile.am | 16 + .../jabber/libiris/cutestuff/network/bsocket.cpp | 394 +++ .../jabber/libiris/cutestuff/network/bsocket.h | 87 + .../libiris/cutestuff/network/httpconnect.cpp | 369 +++ .../jabber/libiris/cutestuff/network/httpconnect.h | 67 + .../jabber/libiris/cutestuff/network/httppoll.cpp | 666 +++++ .../jabber/libiris/cutestuff/network/httppoll.h | 104 + .../jabber/libiris/cutestuff/network/ndns.cpp | 378 +++ .../jabber/libiris/cutestuff/network/ndns.h | 88 + .../jabber/libiris/cutestuff/network/servsock.cpp | 112 + .../jabber/libiris/cutestuff/network/servsock.h | 68 + .../jabber/libiris/cutestuff/network/socks.cpp | 1223 +++++++++ .../jabber/libiris/cutestuff/network/socks.h | 160 ++ .../libiris/cutestuff/network/srvresolver.cpp | 320 +++ .../jabber/libiris/cutestuff/network/srvresolver.h | 65 + .../jabber/libiris/cutestuff/util/Makefile.am | 12 + .../protocols/jabber/libiris/cutestuff/util/TODO | 7 + .../jabber/libiris/cutestuff/util/base64.cpp | 182 ++ .../jabber/libiris/cutestuff/util/base64.h | 40 + .../jabber/libiris/cutestuff/util/bytestream.cpp | 268 ++ .../jabber/libiris/cutestuff/util/bytestream.h | 78 + .../jabber/libiris/cutestuff/util/cipher.cpp | 357 +++ .../jabber/libiris/cutestuff/util/cipher.h | 79 + .../jabber/libiris/cutestuff/util/qrandom.cpp | 24 + .../jabber/libiris/cutestuff/util/qrandom.h | 14 + .../jabber/libiris/cutestuff/util/safedelete.cpp | 119 + .../jabber/libiris/cutestuff/util/safedelete.h | 60 + .../jabber/libiris/cutestuff/util/sha1.cpp | 196 ++ .../protocols/jabber/libiris/cutestuff/util/sha1.h | 63 + .../jabber/libiris/cutestuff/util/showtextdlg.cpp | 61 + .../jabber/libiris/cutestuff/util/showtextdlg.h | 33 + kopete/protocols/jabber/libiris/iris/Makefile.am | 1 + kopete/protocols/jabber/libiris/iris/TODO | 16 + .../jabber/libiris/iris/include/Makefile.am | 7 + .../jabber/libiris/iris/include/empty.cpp | 0 kopete/protocols/jabber/libiris/iris/include/im.h | 721 ++++++ .../protocols/jabber/libiris/iris/include/xmpp.h | 553 ++++ .../jabber/libiris/iris/jabber/Makefile.am | 15 + .../jabber/libiris/iris/jabber/all_mocs.cpp | 23 + .../jabber/libiris/iris/jabber/filetransfer.cpp | 770 ++++++ .../jabber/libiris/iris/jabber/filetransfer.h | 170 ++ .../protocols/jabber/libiris/iris/jabber/s5b.cpp | 2538 ++++++++++++++++++ kopete/protocols/jabber/libiris/iris/jabber/s5b.h | 341 +++ .../jabber/libiris/iris/jabber/xmpp_ibb.cpp | 638 +++++ .../jabber/libiris/iris/jabber/xmpp_ibb.h | 145 ++ .../jabber/libiris/iris/jabber/xmpp_jidlink.cpp | 318 +++ .../jabber/libiris/iris/jabber/xmpp_jidlink.h | 114 + .../jabber/libiris/iris/xmpp-core/Makefile.am | 30 + .../jabber/libiris/iris/xmpp-core/connector.cpp | 719 ++++++ .../jabber/libiris/iris/xmpp-core/hash.cpp | 670 +++++ .../protocols/jabber/libiris/iris/xmpp-core/hash.h | 31 + .../jabber/libiris/iris/xmpp-core/jid.cpp | 409 +++ .../jabber/libiris/iris/xmpp-core/parser.cpp | 798 ++++++ .../jabber/libiris/iris/xmpp-core/parser.h | 86 + .../jabber/libiris/iris/xmpp-core/protocol.cpp | 1595 ++++++++++++ .../jabber/libiris/iris/xmpp-core/protocol.h | 355 +++ .../jabber/libiris/iris/xmpp-core/qcaprovider.h | 191 ++ .../jabber/libiris/iris/xmpp-core/securestream.cpp | 589 +++++ .../jabber/libiris/iris/xmpp-core/securestream.h | 84 + .../jabber/libiris/iris/xmpp-core/simplesasl.cpp | 459 ++++ .../jabber/libiris/iris/xmpp-core/simplesasl.h | 31 + .../jabber/libiris/iris/xmpp-core/stream.cpp | 1762 +++++++++++++ .../protocols/jabber/libiris/iris/xmpp-core/td.h | 20 + .../jabber/libiris/iris/xmpp-core/tlshandler.cpp | 138 + .../jabber/libiris/iris/xmpp-core/xmlprotocol.cpp | 543 ++++ .../jabber/libiris/iris/xmpp-core/xmlprotocol.h | 145 ++ .../jabber/libiris/iris/xmpp-im/Makefile.am | 19 + .../jabber/libiris/iris/xmpp-im/client.cpp | 1522 +++++++++++ .../jabber/libiris/iris/xmpp-im/types.cpp | 1876 ++++++++++++++ .../jabber/libiris/iris/xmpp-im/xmpp_tasks.cpp | 2120 +++++++++++++++ .../jabber/libiris/iris/xmpp-im/xmpp_tasks.h | 485 ++++ .../jabber/libiris/iris/xmpp-im/xmpp_vcard.cpp | 1241 +++++++++ .../jabber/libiris/iris/xmpp-im/xmpp_vcard.h | 284 ++ .../jabber/libiris/iris/xmpp-im/xmpp_xmlcommon.cpp | 386 +++ .../jabber/libiris/iris/xmpp-im/xmpp_xmlcommon.h | 71 + kopete/protocols/jabber/libiris/jingle_iris.patch | 432 ++++ kopete/protocols/jabber/libiris/qca/COPYING | 504 ++++ kopete/protocols/jabber/libiris/qca/INSTALL | 12 + kopete/protocols/jabber/libiris/qca/Makefile.am | 1 + kopete/protocols/jabber/libiris/qca/README | 29 + kopete/protocols/jabber/libiris/qca/TODO | 6 + .../protocols/jabber/libiris/qca/src/Makefile.am | 7 + kopete/protocols/jabber/libiris/qca/src/qca.cpp | 1481 +++++++++++ kopete/protocols/jabber/libiris/qca/src/qca.h | 466 ++++ .../protocols/jabber/libiris/qca/src/qcaprovider.h | 191 ++ kopete/protocols/jabber/ui/Makefile.am | 31 + kopete/protocols/jabber/ui/dlgaddcontact.ui | 105 + kopete/protocols/jabber/ui/dlgbrowse.ui | 201 ++ kopete/protocols/jabber/ui/dlgchangepassword.ui | 88 + kopete/protocols/jabber/ui/dlgchatjoin.ui | 130 + kopete/protocols/jabber/ui/dlgchatroomslist.ui | 185 ++ kopete/protocols/jabber/ui/dlgjabberbrowse.cpp | 144 ++ kopete/protocols/jabber/ui/dlgjabberbrowse.h | 54 + .../jabber/ui/dlgjabberchangepassword.cpp | 135 + .../protocols/jabber/ui/dlgjabberchangepassword.h | 51 + kopete/protocols/jabber/ui/dlgjabberchatjoin.cpp | 129 + kopete/protocols/jabber/ui/dlgjabberchatjoin.h | 65 + .../protocols/jabber/ui/dlgjabberchatroomslist.cpp | 117 + .../protocols/jabber/ui/dlgjabberchatroomslist.h | 54 + .../protocols/jabber/ui/dlgjabberchooseserver.ui | 107 + .../jabber/ui/dlgjabbereditaccountwidget.ui | 993 +++++++ kopete/protocols/jabber/ui/dlgjabberregister.cpp | 117 + kopete/protocols/jabber/ui/dlgjabberregister.h | 58 + .../jabber/ui/dlgjabberregisteraccount.ui | 319 +++ kopete/protocols/jabber/ui/dlgjabbersendraw.cpp | 116 + kopete/protocols/jabber/ui/dlgjabbersendraw.h | 85 + kopete/protocols/jabber/ui/dlgjabberservices.cpp | 237 ++ kopete/protocols/jabber/ui/dlgjabberservices.h | 73 + kopete/protocols/jabber/ui/dlgjabbervcard.cpp | 563 ++++ kopete/protocols/jabber/ui/dlgjabbervcard.h | 118 + kopete/protocols/jabber/ui/dlgregister.ui | 162 ++ kopete/protocols/jabber/ui/dlgsendraw.ui | 159 ++ kopete/protocols/jabber/ui/dlgservices.ui | 199 ++ kopete/protocols/jabber/ui/dlgvcard.ui | 1064 ++++++++ kopete/protocols/jabber/ui/empty.cpp | 0 .../protocols/jabber/ui/jabberaddcontactpage.cpp | 224 ++ kopete/protocols/jabber/ui/jabberaddcontactpage.h | 76 + kopete/protocols/jabber/ui/jabberchooseserver.cpp | 149 ++ kopete/protocols/jabber/ui/jabberchooseserver.h | 65 + .../jabber/ui/jabbereditaccountwidget.cpp | 286 +++ .../protocols/jabber/ui/jabbereditaccountwidget.h | 62 + .../protocols/jabber/ui/jabberregisteraccount.cpp | 389 +++ kopete/protocols/jabber/ui/jabberregisteraccount.h | 75 + 529 files changed, 101021 insertions(+) create mode 100644 kopete/protocols/jabber/Makefile.am create mode 100644 kopete/protocols/jabber/TODO create mode 100644 kopete/protocols/jabber/icons/Makefile.am create mode 100644 kopete/protocols/jabber/icons/cr16-action-jabber_away.png create mode 100644 kopete/protocols/jabber/icons/cr16-action-jabber_chatty.png create mode 100644 kopete/protocols/jabber/icons/cr16-action-jabber_connecting.mng create mode 100644 kopete/protocols/jabber/icons/cr16-action-jabber_group.png create mode 100644 kopete/protocols/jabber/icons/cr16-action-jabber_invisible.png create mode 100644 kopete/protocols/jabber/icons/cr16-action-jabber_na.png create mode 100644 kopete/protocols/jabber/icons/cr16-action-jabber_offline.png create mode 100644 kopete/protocols/jabber/icons/cr16-action-jabber_online.png create mode 100644 kopete/protocols/jabber/icons/cr16-action-jabber_original.png create mode 100644 kopete/protocols/jabber/icons/cr16-action-jabber_raw.png create mode 100644 kopete/protocols/jabber/icons/cr16-action-jabber_serv_off.png create mode 100644 kopete/protocols/jabber/icons/cr16-action-jabber_serv_on.png create mode 100644 kopete/protocols/jabber/icons/cr16-action-jabber_xa.png create mode 100644 kopete/protocols/jabber/icons/cr16-app-jabber_gateway_aim.png create mode 100644 kopete/protocols/jabber/icons/cr16-app-jabber_gateway_gadu.png create mode 100644 kopete/protocols/jabber/icons/cr16-app-jabber_gateway_http-ws.png create mode 100644 kopete/protocols/jabber/icons/cr16-app-jabber_gateway_icq.png create mode 100644 kopete/protocols/jabber/icons/cr16-app-jabber_gateway_irc.png create mode 100644 kopete/protocols/jabber/icons/cr16-app-jabber_gateway_msn.png create mode 100644 kopete/protocols/jabber/icons/cr16-app-jabber_gateway_qq.png create mode 100644 kopete/protocols/jabber/icons/cr16-app-jabber_gateway_sms.png create mode 100644 kopete/protocols/jabber/icons/cr16-app-jabber_gateway_smtp.png create mode 100644 kopete/protocols/jabber/icons/cr16-app-jabber_gateway_tlen.png create mode 100644 kopete/protocols/jabber/icons/cr16-app-jabber_gateway_yahoo.png create mode 100644 kopete/protocols/jabber/icons/cr16-app-jabber_protocol.png create mode 100644 kopete/protocols/jabber/icons/cr32-app-jabber_protocol.png create mode 100644 kopete/protocols/jabber/icons/cr48-app-jabber_protocol.png create mode 100644 kopete/protocols/jabber/icons/hi16-action-jabber_away.png create mode 100644 kopete/protocols/jabber/icons/hi16-action-jabber_chatty.png create mode 100644 kopete/protocols/jabber/icons/hi16-action-jabber_connecting.mng create mode 100644 kopete/protocols/jabber/icons/hi16-action-jabber_group.png create mode 100644 kopete/protocols/jabber/icons/hi16-action-jabber_invisible.png create mode 100644 kopete/protocols/jabber/icons/hi16-action-jabber_na.png create mode 100644 kopete/protocols/jabber/icons/hi16-action-jabber_offline.png create mode 100644 kopete/protocols/jabber/icons/hi16-action-jabber_online.png create mode 100644 kopete/protocols/jabber/icons/hi16-action-jabber_original.png create mode 100644 kopete/protocols/jabber/icons/hi16-action-jabber_raw.png create mode 100644 kopete/protocols/jabber/icons/hi16-action-jabber_serv_off.png create mode 100644 kopete/protocols/jabber/icons/hi16-action-jabber_serv_on.png create mode 100644 kopete/protocols/jabber/icons/hi16-action-jabber_xa.png create mode 100644 kopete/protocols/jabber/icons/hi16-app-jabber_protocol.png create mode 100644 kopete/protocols/jabber/icons/hi32-app-jabber_protocol.png create mode 100644 kopete/protocols/jabber/icons/hi48-app-jabber_protocol.png create mode 100644 kopete/protocols/jabber/jabberaccount.cpp create mode 100644 kopete/protocols/jabber/jabberaccount.h create mode 100644 kopete/protocols/jabber/jabberbasecontact.cpp create mode 100644 kopete/protocols/jabber/jabberbasecontact.h create mode 100644 kopete/protocols/jabber/jabberbookmarks.cpp create mode 100644 kopete/protocols/jabber/jabberbookmarks.h create mode 100644 kopete/protocols/jabber/jabberbytestream.cpp create mode 100644 kopete/protocols/jabber/jabberbytestream.h create mode 100644 kopete/protocols/jabber/jabbercapabilitiesmanager.cpp create mode 100644 kopete/protocols/jabber/jabbercapabilitiesmanager.h create mode 100644 kopete/protocols/jabber/jabberchatsession.cpp create mode 100644 kopete/protocols/jabber/jabberchatsession.h create mode 100644 kopete/protocols/jabber/jabberchatui.rc create mode 100644 kopete/protocols/jabber/jabberclient.cpp create mode 100644 kopete/protocols/jabber/jabberclient.h create mode 100644 kopete/protocols/jabber/jabberconnector.cpp create mode 100644 kopete/protocols/jabber/jabberconnector.h create mode 100644 kopete/protocols/jabber/jabbercontact.cpp create mode 100644 kopete/protocols/jabber/jabbercontact.h create mode 100644 kopete/protocols/jabber/jabbercontactpool.cpp create mode 100644 kopete/protocols/jabber/jabbercontactpool.h create mode 100644 kopete/protocols/jabber/jabberfiletransfer.cpp create mode 100644 kopete/protocols/jabber/jabberfiletransfer.h create mode 100644 kopete/protocols/jabber/jabberformlineedit.cpp create mode 100644 kopete/protocols/jabber/jabberformlineedit.h create mode 100644 kopete/protocols/jabber/jabberformtranslator.cpp create mode 100644 kopete/protocols/jabber/jabberformtranslator.h create mode 100644 kopete/protocols/jabber/jabbergroupchatmanager.cpp create mode 100644 kopete/protocols/jabber/jabbergroupchatmanager.h create mode 100644 kopete/protocols/jabber/jabbergroupcontact.cpp create mode 100644 kopete/protocols/jabber/jabbergroupcontact.h create mode 100644 kopete/protocols/jabber/jabbergroupmembercontact.cpp create mode 100644 kopete/protocols/jabber/jabbergroupmembercontact.h create mode 100644 kopete/protocols/jabber/jabberprotocol.cpp create mode 100644 kopete/protocols/jabber/jabberprotocol.h create mode 100644 kopete/protocols/jabber/jabberresource.cpp create mode 100644 kopete/protocols/jabber/jabberresource.h create mode 100644 kopete/protocols/jabber/jabberresourcepool.cpp create mode 100644 kopete/protocols/jabber/jabberresourcepool.h create mode 100644 kopete/protocols/jabber/jabbertransport.cpp create mode 100644 kopete/protocols/jabber/jabbertransport.h create mode 100644 kopete/protocols/jabber/jingle/DESIGN create mode 100644 kopete/protocols/jabber/jingle/Makefile.am create mode 100644 kopete/protocols/jabber/jingle/configure.in.bot create mode 100644 kopete/protocols/jabber/jingle/configure.in.in create mode 100644 kopete/protocols/jabber/jingle/jinglesession.cpp create mode 100644 kopete/protocols/jabber/jingle/jinglesession.h create mode 100644 kopete/protocols/jabber/jingle/jinglesessionmanager.cpp create mode 100644 kopete/protocols/jabber/jingle/jinglesessionmanager.h create mode 100644 kopete/protocols/jabber/jingle/jinglevoicecaller.cpp create mode 100644 kopete/protocols/jabber/jingle/jinglevoicecaller.h create mode 100644 kopete/protocols/jabber/jingle/jinglevoicesession.cpp create mode 100644 kopete/protocols/jabber/jingle/jinglevoicesession.h create mode 100644 kopete/protocols/jabber/jingle/jinglevoicesessiondialog.cpp create mode 100644 kopete/protocols/jabber/jingle/jinglevoicesessiondialog.h create mode 100644 kopete/protocols/jabber/jingle/jinglevoicesessiondialogbase.ui create mode 100644 kopete/protocols/jabber/jingle/jinglewatchsessiontask.cpp create mode 100644 kopete/protocols/jabber/jingle/jinglewatchsessiontask.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/AUTHORS create mode 100644 kopete/protocols/jabber/jingle/libjingle/COPYING create mode 100644 kopete/protocols/jabber/jingle/libjingle/ChangeLog create mode 100644 kopete/protocols/jabber/jingle/libjingle/INSTALL create mode 100644 kopete/protocols/jabber/jingle/libjingle/Makefile.am create mode 100644 kopete/protocols/jabber/jingle/libjingle/NEWS create mode 100644 kopete/protocols/jabber/jingle/libjingle/README create mode 100644 kopete/protocols/jabber/jingle/libjingle/libjingle.pro create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/Makefile.am create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/Makefile.am create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/asyncfile.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/asyncpacketsocket.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/asyncpacketsocket.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/asyncsocket.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/asynctcpsocket.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/asynctcpsocket.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/asyncudpsocket.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/asyncudpsocket.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/base64.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/base64.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/basicdefs.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/basictypes.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/bytebuffer.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/bytebuffer.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/byteorder.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/common.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/criticalsection.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/host.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/host.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/jtime.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/jtime.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/linked_ptr.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/logging.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/md5.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/md5c.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/messagequeue.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/messagequeue.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/network.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/network.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/physicalsocketserver.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/physicalsocketserver.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/proxyinfo.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/scoped_ptr.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/sigslot.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/socket.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/socketadapters.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/socketadapters.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddress.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddress.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddresspair.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddresspair.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/socketfactory.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/socketserver.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/stl_decl.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/stringutils.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/task.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/task.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/taskrunner.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/taskrunner.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/thread.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/thread.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/base/winping.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/Makefile.am create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/call/Makefile.am create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/call/call.pro create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/call/call_main.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/call/callclient.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/call/callclient.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/call/console.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/call/console.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presenceouttask.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presenceouttask.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presencepushtask.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presencepushtask.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/call/status.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/login/Makefile.am create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/login/login_main.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppauth.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppauth.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmpppump.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmpppump.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppsocket.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppsocket.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppthread.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppthread.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/Makefile.am create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/Makefile.am create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/candidate.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/helpers.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/helpers.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/p2psocket.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/p2psocket.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/port.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/port.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/portallocator.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayport.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayport.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver.pro create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver_main.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/session.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/session.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessiondescription.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionid.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionmanager.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionmanager.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionmessage.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/socketmanager.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/socketmanager.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stun.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stun.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunport.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunport.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunrequest.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunrequest.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver.pro create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver_main.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/tcpport.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/tcpport.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/udpport.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/udpport.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/Makefile.am create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/basicportallocator.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/basicportallocator.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/sessionclient.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/sessionclient.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/socketmonitor.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/socketmonitor.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/session/Makefile.am create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/session/phone/Makefile.am create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/session/phone/audiomonitor.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/session/phone/audiomonitor.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/session/phone/call.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/session/phone/call.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/session/phone/channelmanager.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/session/phone/channelmanager.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/session/phone/linphonemediaengine.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/session/phone/linphonemediaengine.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/session/phone/mediachannel.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/session/phone/mediaengine.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/session/phone/phonesessionclient.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/session/phone/phonesessionclient.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/session/phone/portaudiomediaengine.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/session/phone/portaudiomediaengine.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/session/phone/voicechannel.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/session/phone/voicechannel.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/session/receiver.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/session/sessionsendtask.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/Makefile.am create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/Makefile.am create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/Makefile.ms create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/README create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/affine.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/alsacard.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/alsacard.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/audiostream.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/g711common.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/hpuxsndcard.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/jackcard.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/jackcard.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mediastream.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/ms.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/ms.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msAlawdec.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msAlawdec.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msAlawenc.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msAlawenc.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msGSMdecoder.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msGSMencoder.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msLPC10decoder.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msLPC10encoder.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msMUlawdec.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msMUlawdec.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msMUlawenc.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msMUlawenc.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msavdecoder.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msavencoder.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msbuffer.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msbuffer.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mscodec.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mscodec.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mscopy.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mscopy.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfdispatcher.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfdispatcher.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfifo.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfifo.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfilter.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfilter.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcdec.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcdec.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcenc.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcenc.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msnosync.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msnosync.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msossread.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msossread.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msosswrite.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msosswrite.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqdispatcher.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqdispatcher.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqueue.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqueue.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msread.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msread.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msringplayer.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msringplayer.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtprecv.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtprecv.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtpsend.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtpsend.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssdlout.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundread.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundread.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundwrite.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundwrite.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexdec.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexdec.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexenc.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexenc.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssync.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssync.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstimer.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstimer.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstruespeechdecoder.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstruespeechencoder.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msutils.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msv4l.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msvideosource.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mswrite.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mswrite.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/osscard.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/osscard.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/portaudiocard.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/portaudiocard.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/sndcard.c create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/sndcard.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/waveheader.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmllite/Makefile.am create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmllite/qname.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmllite/qname.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlbuilder.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlbuilder.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlconstants.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlconstants.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlelement.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlelement.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlnsstack.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlnsstack.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlparser.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlparser.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlprinter.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlprinter.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/Makefile.am create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/asyncsocket.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/constants.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/constants.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/jid.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/jid.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/plainsaslhandler.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/prexmppauth.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslcookiemechanism.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslhandler.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslmechanism.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslmechanism.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslplainmechanism.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppclient.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppclient.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppclientsettings.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengine.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengineimpl.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengineimpl.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengineimpl_iq.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpplogintask.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpplogintask.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpppassword.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppstanzaparser.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppstanzaparser.h create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpptask.cc create mode 100644 kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpptask.h create mode 100644 kopete/protocols/jabber/jingle/voicecaller.h create mode 100644 kopete/protocols/jabber/kioslave/Makefile.am create mode 100644 kopete/protocols/jabber/kioslave/jabberdisco.cpp create mode 100644 kopete/protocols/jabber/kioslave/jabberdisco.h create mode 100644 kopete/protocols/jabber/kioslave/jabberdisco.protocol create mode 100644 kopete/protocols/jabber/kopete_jabber.desktop create mode 100644 kopete/protocols/jabber/libiris/001_last_activity.patch create mode 100644 kopete/protocols/jabber/libiris/002_offline_event.patch create mode 100644 kopete/protocols/jabber/libiris/003_case_insensitive_jid.patch create mode 100644 kopete/protocols/jabber/libiris/004_xhtml_im.patch create mode 100644 kopete/protocols/jabber/libiris/005_join_muc_with_password.patch create mode 100644 kopete/protocols/jabber/libiris/006_private_storage.patch create mode 100644 kopete/protocols/jabber/libiris/007_chatstates.patch create mode 100644 kopete/protocols/jabber/libiris/008_chatstatesfix.patch create mode 100644 kopete/protocols/jabber/libiris/Makefile.am create mode 100644 kopete/protocols/jabber/libiris/README_BEFORE_COMMITTING create mode 100644 kopete/protocols/jabber/libiris/cutestuff/Makefile.am create mode 100644 kopete/protocols/jabber/libiris/cutestuff/README create mode 100644 kopete/protocols/jabber/libiris/cutestuff/TODO create mode 100644 kopete/protocols/jabber/libiris/cutestuff/network/Makefile.am create mode 100644 kopete/protocols/jabber/libiris/cutestuff/network/bsocket.cpp create mode 100644 kopete/protocols/jabber/libiris/cutestuff/network/bsocket.h create mode 100644 kopete/protocols/jabber/libiris/cutestuff/network/httpconnect.cpp create mode 100644 kopete/protocols/jabber/libiris/cutestuff/network/httpconnect.h create mode 100644 kopete/protocols/jabber/libiris/cutestuff/network/httppoll.cpp create mode 100644 kopete/protocols/jabber/libiris/cutestuff/network/httppoll.h create mode 100644 kopete/protocols/jabber/libiris/cutestuff/network/ndns.cpp create mode 100644 kopete/protocols/jabber/libiris/cutestuff/network/ndns.h create mode 100644 kopete/protocols/jabber/libiris/cutestuff/network/servsock.cpp create mode 100644 kopete/protocols/jabber/libiris/cutestuff/network/servsock.h create mode 100644 kopete/protocols/jabber/libiris/cutestuff/network/socks.cpp create mode 100644 kopete/protocols/jabber/libiris/cutestuff/network/socks.h create mode 100644 kopete/protocols/jabber/libiris/cutestuff/network/srvresolver.cpp create mode 100644 kopete/protocols/jabber/libiris/cutestuff/network/srvresolver.h create mode 100644 kopete/protocols/jabber/libiris/cutestuff/util/Makefile.am create mode 100644 kopete/protocols/jabber/libiris/cutestuff/util/TODO create mode 100644 kopete/protocols/jabber/libiris/cutestuff/util/base64.cpp create mode 100644 kopete/protocols/jabber/libiris/cutestuff/util/base64.h create mode 100644 kopete/protocols/jabber/libiris/cutestuff/util/bytestream.cpp create mode 100644 kopete/protocols/jabber/libiris/cutestuff/util/bytestream.h create mode 100644 kopete/protocols/jabber/libiris/cutestuff/util/cipher.cpp create mode 100644 kopete/protocols/jabber/libiris/cutestuff/util/cipher.h create mode 100644 kopete/protocols/jabber/libiris/cutestuff/util/qrandom.cpp create mode 100644 kopete/protocols/jabber/libiris/cutestuff/util/qrandom.h create mode 100644 kopete/protocols/jabber/libiris/cutestuff/util/safedelete.cpp create mode 100644 kopete/protocols/jabber/libiris/cutestuff/util/safedelete.h create mode 100644 kopete/protocols/jabber/libiris/cutestuff/util/sha1.cpp create mode 100644 kopete/protocols/jabber/libiris/cutestuff/util/sha1.h create mode 100644 kopete/protocols/jabber/libiris/cutestuff/util/showtextdlg.cpp create mode 100644 kopete/protocols/jabber/libiris/cutestuff/util/showtextdlg.h create mode 100644 kopete/protocols/jabber/libiris/iris/Makefile.am create mode 100644 kopete/protocols/jabber/libiris/iris/TODO create mode 100644 kopete/protocols/jabber/libiris/iris/include/Makefile.am create mode 100644 kopete/protocols/jabber/libiris/iris/include/empty.cpp create mode 100644 kopete/protocols/jabber/libiris/iris/include/im.h create mode 100644 kopete/protocols/jabber/libiris/iris/include/xmpp.h create mode 100644 kopete/protocols/jabber/libiris/iris/jabber/Makefile.am create mode 100644 kopete/protocols/jabber/libiris/iris/jabber/all_mocs.cpp create mode 100644 kopete/protocols/jabber/libiris/iris/jabber/filetransfer.cpp create mode 100644 kopete/protocols/jabber/libiris/iris/jabber/filetransfer.h create mode 100644 kopete/protocols/jabber/libiris/iris/jabber/s5b.cpp create mode 100644 kopete/protocols/jabber/libiris/iris/jabber/s5b.h create mode 100644 kopete/protocols/jabber/libiris/iris/jabber/xmpp_ibb.cpp create mode 100644 kopete/protocols/jabber/libiris/iris/jabber/xmpp_ibb.h create mode 100644 kopete/protocols/jabber/libiris/iris/jabber/xmpp_jidlink.cpp create mode 100644 kopete/protocols/jabber/libiris/iris/jabber/xmpp_jidlink.h create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-core/Makefile.am create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-core/connector.cpp create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-core/hash.cpp create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-core/hash.h create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-core/jid.cpp create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-core/parser.cpp create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-core/parser.h create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.cpp create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.h create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-core/qcaprovider.h create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.cpp create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.h create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.cpp create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.h create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-core/stream.cpp create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-core/td.h create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-core/tlshandler.cpp create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.cpp create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.h create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-im/Makefile.am create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-im/client.cpp create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-im/types.cpp create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_tasks.cpp create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_tasks.h create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_vcard.cpp create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_vcard.h create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_xmlcommon.cpp create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_xmlcommon.h create mode 100644 kopete/protocols/jabber/libiris/jingle_iris.patch create mode 100644 kopete/protocols/jabber/libiris/qca/COPYING create mode 100644 kopete/protocols/jabber/libiris/qca/INSTALL create mode 100644 kopete/protocols/jabber/libiris/qca/Makefile.am create mode 100644 kopete/protocols/jabber/libiris/qca/README create mode 100644 kopete/protocols/jabber/libiris/qca/TODO create mode 100644 kopete/protocols/jabber/libiris/qca/src/Makefile.am create mode 100644 kopete/protocols/jabber/libiris/qca/src/qca.cpp create mode 100644 kopete/protocols/jabber/libiris/qca/src/qca.h create mode 100644 kopete/protocols/jabber/libiris/qca/src/qcaprovider.h create mode 100644 kopete/protocols/jabber/ui/Makefile.am create mode 100644 kopete/protocols/jabber/ui/dlgaddcontact.ui create mode 100644 kopete/protocols/jabber/ui/dlgbrowse.ui create mode 100644 kopete/protocols/jabber/ui/dlgchangepassword.ui create mode 100644 kopete/protocols/jabber/ui/dlgchatjoin.ui create mode 100644 kopete/protocols/jabber/ui/dlgchatroomslist.ui create mode 100644 kopete/protocols/jabber/ui/dlgjabberbrowse.cpp create mode 100644 kopete/protocols/jabber/ui/dlgjabberbrowse.h create mode 100644 kopete/protocols/jabber/ui/dlgjabberchangepassword.cpp create mode 100644 kopete/protocols/jabber/ui/dlgjabberchangepassword.h create mode 100644 kopete/protocols/jabber/ui/dlgjabberchatjoin.cpp create mode 100644 kopete/protocols/jabber/ui/dlgjabberchatjoin.h create mode 100644 kopete/protocols/jabber/ui/dlgjabberchatroomslist.cpp create mode 100644 kopete/protocols/jabber/ui/dlgjabberchatroomslist.h create mode 100644 kopete/protocols/jabber/ui/dlgjabberchooseserver.ui create mode 100644 kopete/protocols/jabber/ui/dlgjabbereditaccountwidget.ui create mode 100644 kopete/protocols/jabber/ui/dlgjabberregister.cpp create mode 100644 kopete/protocols/jabber/ui/dlgjabberregister.h create mode 100644 kopete/protocols/jabber/ui/dlgjabberregisteraccount.ui create mode 100644 kopete/protocols/jabber/ui/dlgjabbersendraw.cpp create mode 100644 kopete/protocols/jabber/ui/dlgjabbersendraw.h create mode 100644 kopete/protocols/jabber/ui/dlgjabberservices.cpp create mode 100644 kopete/protocols/jabber/ui/dlgjabberservices.h create mode 100644 kopete/protocols/jabber/ui/dlgjabbervcard.cpp create mode 100644 kopete/protocols/jabber/ui/dlgjabbervcard.h create mode 100644 kopete/protocols/jabber/ui/dlgregister.ui create mode 100644 kopete/protocols/jabber/ui/dlgsendraw.ui create mode 100644 kopete/protocols/jabber/ui/dlgservices.ui create mode 100644 kopete/protocols/jabber/ui/dlgvcard.ui create mode 100644 kopete/protocols/jabber/ui/empty.cpp create mode 100644 kopete/protocols/jabber/ui/jabberaddcontactpage.cpp create mode 100644 kopete/protocols/jabber/ui/jabberaddcontactpage.h create mode 100644 kopete/protocols/jabber/ui/jabberchooseserver.cpp create mode 100644 kopete/protocols/jabber/ui/jabberchooseserver.h create mode 100644 kopete/protocols/jabber/ui/jabbereditaccountwidget.cpp create mode 100644 kopete/protocols/jabber/ui/jabbereditaccountwidget.h create mode 100644 kopete/protocols/jabber/ui/jabberregisteraccount.cpp create mode 100644 kopete/protocols/jabber/ui/jabberregisteraccount.h (limited to 'kopete/protocols/jabber') diff --git a/kopete/protocols/jabber/Makefile.am b/kopete/protocols/jabber/Makefile.am new file mode 100644 index 00000000..ce462f74 --- /dev/null +++ b/kopete/protocols/jabber/Makefile.am @@ -0,0 +1,67 @@ +if include_jingle +JINGLE=jingle +JINGLE_LIBS=jingle/libkopetejabberjingle.la +JINGLE_INCLUDES=-I$(srcdir)/jingle -I$(top_builddir)/kopete/protocols/jabber/jingle +endif + +METASOURCES = AUTO +SUBDIRS = ui icons libiris $(JINGLE) . kioslave +AM_CPPFLAGS = $(KOPETE_INCLUDES) \ + -I$(srcdir)/libiris/iris/include \ + -I$(srcdir)/libiris/iris/xmpp-im \ + -I$(srcdir)/libiris/iris/jabber \ + -I$(srcdir)/libiris/qca/src \ + -I$(srcdir)/libiris/cutestuff/util \ + -I$(srcdir)/libiris/cutestuff/network \ + -I$(srcdir)/ui \ + -I./ui \ + $(all_includes) $(JINGLE_INCLUDES) + +noinst_LTLIBRARIES = libjabberclient.la +libjabberclient_la_SOURCES = \ + jabberclient.cpp \ + jabberconnector.cpp \ + jabberbytestream.cpp + +kde_module_LTLIBRARIES = kopete_jabber.la + +kopete_jabber_la_SOURCES = \ + jabberprotocol.cpp \ + jabberaccount.cpp \ + jabberresource.cpp \ + jabberresourcepool.cpp \ + jabberbasecontact.cpp \ + jabbercontact.cpp \ + jabbergroupcontact.cpp \ + jabbergroupmembercontact.cpp \ + jabbercontactpool.cpp \ + jabberformtranslator.cpp \ + jabberformlineedit.cpp \ + jabberchatsession.cpp \ + jabbergroupchatmanager.cpp \ + jabberfiletransfer.cpp \ + jabbercapabilitiesmanager.cpp\ + jabbertransport.cpp\ + jabberbookmarks.cpp + +kopete_jabber_la_LDFLAGS = -no-undefined -module $(KDE_PLUGIN) $(all_libraries) +kopete_jabber_la_LIBADD = $(top_builddir)/kopete/libkopete/libkopete.la \ + ui/libkopetejabberui.la \ + libiris/iris/include/libiris.la \ + libiris/iris/jabber/libiris_jabber.la \ + libiris/iris/xmpp-core/libiris_xmpp_core.la \ + libiris/iris/xmpp-im/libiris_xmpp_im.la \ + libiris/qca/src/libqca.la \ + libiris/cutestuff/network/libcutestuff_network.la \ + libiris/cutestuff/util/libcutestuff_util.la \ + libjabberclient.la \ + $(JINGLE_LIBS) + +service_DATA = kopete_jabber.desktop +servicedir = $(kde_servicesdir) + +mydatadir = $(kde_datadir)/kopete_jabber +mydata_DATA = jabberchatui.rc + +noinst_HEADERS = jabberresourcepool.h jabbercontact.h jabbergroupcontact.h \ + jabberclient.h diff --git a/kopete/protocols/jabber/TODO b/kopete/protocols/jabber/TODO new file mode 100644 index 00000000..64da5133 --- /dev/null +++ b/kopete/protocols/jabber/TODO @@ -0,0 +1,27 @@ +TODO for Jabber: + +- implement support for transports/agents +- support all message types (chat/ticker/etc) +- add a button for server defaults +- port dialogs to KDialogBase +- support different icons for contacts from servers with broken connections etc. +- show (i.e. with a QToolTip) the subscription status: both, to, from +- add "querying..." feedback while waiting for vCard +- clean up class names in the ui directory, no real scheme there right now +- if a contact subscribed to you, it is being added as a real contact, + should either be added as temporary or not at all +- provide better feedback for dialogs querying the server +- support avatars and idle times for tooltips +- when trying to register an account, try to display the actual server error + message +- clean up JabberAddContactPage (needs rewrite) +- support advanced auth methods +- subclass TLS to make use of KDE classes +- allow SSL fallback with setOptProbe +- support account deletion +- factor out client backend to single class JabberClient +- make vCard dialog better, maybe use KIMProxy somehow +- allow fetching vCard from "auth user?" dialog +- allow adding file transfer reasons +- support resuming +- support addAddressBookField diff --git a/kopete/protocols/jabber/icons/Makefile.am b/kopete/protocols/jabber/icons/Makefile.am new file mode 100644 index 00000000..56681824 --- /dev/null +++ b/kopete/protocols/jabber/icons/Makefile.am @@ -0,0 +1 @@ +KDE_ICON=AUTO \ No newline at end of file diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_away.png b/kopete/protocols/jabber/icons/cr16-action-jabber_away.png new file mode 100644 index 00000000..a6727e71 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_away.png differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_chatty.png b/kopete/protocols/jabber/icons/cr16-action-jabber_chatty.png new file mode 100644 index 00000000..baca2e22 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_chatty.png differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_connecting.mng b/kopete/protocols/jabber/icons/cr16-action-jabber_connecting.mng new file mode 100644 index 00000000..3098ca1f Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_connecting.mng differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_group.png b/kopete/protocols/jabber/icons/cr16-action-jabber_group.png new file mode 100644 index 00000000..0240ab6e Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_group.png differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_invisible.png b/kopete/protocols/jabber/icons/cr16-action-jabber_invisible.png new file mode 100644 index 00000000..279f1397 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_invisible.png differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_na.png b/kopete/protocols/jabber/icons/cr16-action-jabber_na.png new file mode 100644 index 00000000..b1aa91af Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_na.png differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_offline.png b/kopete/protocols/jabber/icons/cr16-action-jabber_offline.png new file mode 100644 index 00000000..5e473faa Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_offline.png differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_online.png b/kopete/protocols/jabber/icons/cr16-action-jabber_online.png new file mode 100644 index 00000000..48dd715e Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_online.png differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_original.png b/kopete/protocols/jabber/icons/cr16-action-jabber_original.png new file mode 100644 index 00000000..3a8e5042 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_original.png differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_raw.png b/kopete/protocols/jabber/icons/cr16-action-jabber_raw.png new file mode 100644 index 00000000..7b170c19 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_raw.png differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_serv_off.png b/kopete/protocols/jabber/icons/cr16-action-jabber_serv_off.png new file mode 100644 index 00000000..8d5005e7 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_serv_off.png differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_serv_on.png b/kopete/protocols/jabber/icons/cr16-action-jabber_serv_on.png new file mode 100644 index 00000000..378afc5c Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_serv_on.png differ diff --git a/kopete/protocols/jabber/icons/cr16-action-jabber_xa.png b/kopete/protocols/jabber/icons/cr16-action-jabber_xa.png new file mode 100644 index 00000000..347a4753 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-action-jabber_xa.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_aim.png b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_aim.png new file mode 100644 index 00000000..32aea50a Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_aim.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_gadu.png b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_gadu.png new file mode 100644 index 00000000..85c7ee81 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_gadu.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_http-ws.png b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_http-ws.png new file mode 100644 index 00000000..71da6df4 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_http-ws.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_icq.png b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_icq.png new file mode 100644 index 00000000..77df20aa Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_icq.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_irc.png b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_irc.png new file mode 100644 index 00000000..c4283af4 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_irc.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_msn.png b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_msn.png new file mode 100644 index 00000000..2a349148 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_msn.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_qq.png b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_qq.png new file mode 100644 index 00000000..0dd883dd Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_qq.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_sms.png b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_sms.png new file mode 100644 index 00000000..eeb212a3 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_sms.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_smtp.png b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_smtp.png new file mode 100644 index 00000000..bebd2e69 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_smtp.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_tlen.png b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_tlen.png new file mode 100644 index 00000000..d4421eed Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_tlen.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_yahoo.png b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_yahoo.png new file mode 100644 index 00000000..6f1bb81d Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_gateway_yahoo.png differ diff --git a/kopete/protocols/jabber/icons/cr16-app-jabber_protocol.png b/kopete/protocols/jabber/icons/cr16-app-jabber_protocol.png new file mode 100644 index 00000000..48dd715e Binary files /dev/null and b/kopete/protocols/jabber/icons/cr16-app-jabber_protocol.png differ diff --git a/kopete/protocols/jabber/icons/cr32-app-jabber_protocol.png b/kopete/protocols/jabber/icons/cr32-app-jabber_protocol.png new file mode 100644 index 00000000..9d091b13 Binary files /dev/null and b/kopete/protocols/jabber/icons/cr32-app-jabber_protocol.png differ diff --git a/kopete/protocols/jabber/icons/cr48-app-jabber_protocol.png b/kopete/protocols/jabber/icons/cr48-app-jabber_protocol.png new file mode 100644 index 00000000..0964749c Binary files /dev/null and b/kopete/protocols/jabber/icons/cr48-app-jabber_protocol.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_away.png b/kopete/protocols/jabber/icons/hi16-action-jabber_away.png new file mode 100644 index 00000000..b3959b1a Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_away.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_chatty.png b/kopete/protocols/jabber/icons/hi16-action-jabber_chatty.png new file mode 100644 index 00000000..8fd5a24b Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_chatty.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_connecting.mng b/kopete/protocols/jabber/icons/hi16-action-jabber_connecting.mng new file mode 100644 index 00000000..5fabdd4c Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_connecting.mng differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_group.png b/kopete/protocols/jabber/icons/hi16-action-jabber_group.png new file mode 100644 index 00000000..fe2062a9 Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_group.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_invisible.png b/kopete/protocols/jabber/icons/hi16-action-jabber_invisible.png new file mode 100644 index 00000000..e1a30342 Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_invisible.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_na.png b/kopete/protocols/jabber/icons/hi16-action-jabber_na.png new file mode 100644 index 00000000..d4950ec0 Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_na.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_offline.png b/kopete/protocols/jabber/icons/hi16-action-jabber_offline.png new file mode 100644 index 00000000..199b75ed Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_offline.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_online.png b/kopete/protocols/jabber/icons/hi16-action-jabber_online.png new file mode 100644 index 00000000..f3566eab Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_online.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_original.png b/kopete/protocols/jabber/icons/hi16-action-jabber_original.png new file mode 100644 index 00000000..4613cb67 Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_original.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_raw.png b/kopete/protocols/jabber/icons/hi16-action-jabber_raw.png new file mode 100644 index 00000000..7b170c19 Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_raw.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_serv_off.png b/kopete/protocols/jabber/icons/hi16-action-jabber_serv_off.png new file mode 100644 index 00000000..822b70fb Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_serv_off.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_serv_on.png b/kopete/protocols/jabber/icons/hi16-action-jabber_serv_on.png new file mode 100644 index 00000000..b123c82e Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_serv_on.png differ diff --git a/kopete/protocols/jabber/icons/hi16-action-jabber_xa.png b/kopete/protocols/jabber/icons/hi16-action-jabber_xa.png new file mode 100644 index 00000000..e6a17e7c Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-action-jabber_xa.png differ diff --git a/kopete/protocols/jabber/icons/hi16-app-jabber_protocol.png b/kopete/protocols/jabber/icons/hi16-app-jabber_protocol.png new file mode 100644 index 00000000..f3566eab Binary files /dev/null and b/kopete/protocols/jabber/icons/hi16-app-jabber_protocol.png differ diff --git a/kopete/protocols/jabber/icons/hi32-app-jabber_protocol.png b/kopete/protocols/jabber/icons/hi32-app-jabber_protocol.png new file mode 100644 index 00000000..2cb3ef57 Binary files /dev/null and b/kopete/protocols/jabber/icons/hi32-app-jabber_protocol.png differ diff --git a/kopete/protocols/jabber/icons/hi48-app-jabber_protocol.png b/kopete/protocols/jabber/icons/hi48-app-jabber_protocol.png new file mode 100644 index 00000000..7d4cf34b Binary files /dev/null and b/kopete/protocols/jabber/icons/hi48-app-jabber_protocol.png differ diff --git a/kopete/protocols/jabber/jabberaccount.cpp b/kopete/protocols/jabber/jabberaccount.cpp new file mode 100644 index 00000000..785e9c53 --- /dev/null +++ b/kopete/protocols/jabber/jabberaccount.cpp @@ -0,0 +1,1752 @@ + +/*************************************************************************** + jabberaccount.cpp - core Jabber account class + ------------------- + begin : Sat M??? 8 2003 + copyright : (C) 2003 by Till Gerken + Based on JabberProtocol by Daniel Stone + and Till Gerken . + copyright : (C) 2006 by Olivier Goffart + + Kopete (C) 2001-2003 Kopete developers + . + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "im.h" +#include "filetransfer.h" +#include "xmpp.h" +#include "xmpp_tasks.h" +#include "qca.h" +#include "bsocket.h" + +#include "jabberaccount.h" +#include "jabberbookmarks.h" + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kopetepassword.h" +#include "kopeteawayaction.h" +#include "kopetemetacontact.h" +#include "kopeteuiglobal.h" +#include "kopetegroup.h" +#include "kopetecontactlist.h" +#include "kopeteaccountmanager.h" +#include "contactaddednotifydialog.h" + +#include "jabberconnector.h" +#include "jabberclient.h" +#include "jabberprotocol.h" +#include "jabberresourcepool.h" +#include "jabbercontactpool.h" +#include "jabberfiletransfer.h" +#include "jabbercontact.h" +#include "jabbergroupcontact.h" +#include "jabbercapabilitiesmanager.h" +#include "jabbertransport.h" +#include "dlgjabbersendraw.h" +#include "dlgjabberservices.h" +#include "dlgjabberchatjoin.h" + +#include + +#ifdef SUPPORT_JINGLE +#include "voicecaller.h" +#include "jinglevoicecaller.h" + +// NOTE: Disabled for 0.12, will develop them futher in KDE4 +// #include "jinglesessionmanager.h" +// #include "jinglesession.h" +// #include "jinglevoicesession.h" +#include "jinglevoicesessiondialog.h" +#endif + +#define KOPETE_CAPS_NODE "http://kopete.kde.org/jabber/caps" + + + +JabberAccount::JabberAccount (JabberProtocol * parent, const QString & accountId, const char *name) + :Kopete::PasswordedAccount ( parent, accountId, 0, name ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Instantiating new account " << accountId << endl; + + m_protocol = parent; + + m_jabberClient = 0L; + + m_resourcePool = 0L; + m_contactPool = 0L; +#ifdef SUPPORT_JINGLE + m_voiceCaller = 0L; + //m_jingleSessionManager = 0L; // NOTE: Disabled for 0.12 +#endif + m_bookmarks = new JabberBookmarks(this); + m_removing=false; + m_notifiedUserCannotBindTransferPort = false; + // add our own contact to the pool + JabberContact *myContact = contactPool()->addContact ( XMPP::RosterItem ( accountId ), Kopete::ContactList::self()->myself(), false ); + setMyself( myContact ); + + QObject::connect(Kopete::ContactList::self(), SIGNAL( globalIdentityChanged(const QString&, const QVariant& ) ), SLOT( slotGlobalIdentityChanged(const QString&, const QVariant& ) ) ); + + m_initialPresence = XMPP::Status ( "", "", 5, true ); + +} + +JabberAccount::~JabberAccount () +{ + disconnect ( Kopete::Account::Manual ); + + // Remove this account from Capabilities manager. + protocol()->capabilitiesManager()->removeAccount( this ); + + cleanup (); + + QMap tranposrts_copy=m_transports; + QMap::Iterator it; + for ( it = tranposrts_copy.begin(); it != tranposrts_copy.end(); ++it ) + delete it.data(); +} + +void JabberAccount::cleanup () +{ + + delete m_jabberClient; + + m_jabberClient = 0L; + + delete m_resourcePool; + m_resourcePool = 0L; + + delete m_contactPool; + m_contactPool = 0L; + +#ifdef SUPPORT_JINGLE + delete m_voiceCaller; + m_voiceCaller = 0L; + +// delete m_jingleSessionManager; +// m_jingleSessionManager = 0L; +#endif +} + +void JabberAccount::setS5BServerPort ( int port ) +{ + + if ( !m_jabberClient ) + { + return; + } + + if ( !m_jabberClient->setS5BServerPort ( port ) && !m_notifiedUserCannotBindTransferPort) + { + KMessageBox::queuedMessageBox ( Kopete::UI::Global::mainWidget (), KMessageBox::Sorry, + i18n ( "Could not bind Jabber file transfer manager to local port. Please check if the file transfer port is already in use or choose another port in the account settings." ), + i18n ( "Failed to start Jabber File Transfer Manager" ) ); + m_notifiedUserCannotBindTransferPort = true; + } + +} + +KActionMenu *JabberAccount::actionMenu () +{ + KActionMenu *m_actionMenu = Kopete::Account::actionMenu(); + + m_actionMenu->popupMenu()->insertSeparator(); + + KAction *action; + + action = new KAction (i18n ("Join Groupchat..."), "jabber_group", 0, this, SLOT (slotJoinNewChat ()), this, "actionJoinChat"); + m_actionMenu->insert(action); + action->setEnabled( isConnected() ); + + action = m_bookmarks->bookmarksAction( m_bookmarks ); + m_actionMenu->insert(action); + action->setEnabled( isConnected() ); + + + m_actionMenu->popupMenu()->insertSeparator(); + + action = new KAction ( i18n ("Services..."), "jabber_serv_on", 0, + this, SLOT ( slotGetServices () ), this, "actionJabberServices"); + action->setEnabled( isConnected() ); + m_actionMenu->insert ( action ); + + action = new KAction ( i18n ("Send Raw Packet to Server..."), "mail_new", 0, + this, SLOT ( slotSendRaw () ), this, "actionJabberSendRaw") ; + action->setEnabled( isConnected() ); + m_actionMenu->insert ( action ); + + action = new KAction ( i18n ("Edit User Info..."), "identity", 0, + this, SLOT ( slotEditVCard () ), this, "actionEditVCard") ; + action->setEnabled( isConnected() ); + m_actionMenu->insert ( action ); + + + return m_actionMenu; + +} + +JabberResourcePool *JabberAccount::resourcePool () +{ + + if ( !m_resourcePool ) + m_resourcePool = new JabberResourcePool ( this ); + + return m_resourcePool; + +} + +JabberContactPool *JabberAccount::contactPool () +{ + + if ( !m_contactPool ) + m_contactPool = new JabberContactPool ( this ); + + return m_contactPool; + +} + +bool JabberAccount::createContact (const QString & contactId, Kopete::MetaContact * metaContact) +{ + + // collect all group names + QStringList groupNames; + Kopete::GroupList groupList = metaContact->groups(); + for(Kopete::Group *group = groupList.first(); group; group = groupList.next()) + groupNames += group->displayName(); + + XMPP::Jid jid ( contactId ); + XMPP::RosterItem item ( jid ); + item.setName ( metaContact->displayName () ); + item.setGroups ( groupNames ); + + // this contact will be created with the "dirty" flag set + // (it will get reset if the contact appears in the roster during connect) + JabberContact *contact = contactPool()->addContact ( item, metaContact, true ); + + return ( contact != 0 ); + +} + +void JabberAccount::errorConnectFirst () +{ + + KMessageBox::queuedMessageBox ( Kopete::UI::Global::mainWidget (), + KMessageBox::Error, + i18n ("Please connect first."), i18n ("Jabber Error") ); + +} + +void JabberAccount::errorConnectionLost () +{ + disconnected( Kopete::Account::ConnectionReset ); +} + +bool JabberAccount::isConnecting () +{ + + XMPP::Jid jid ( myself()->contactId () ); + + // see if we are currently trying to connect + return resourcePool()->bestResource ( jid ).status().show () == QString("connecting"); + +} + +void JabberAccount::connectWithPassword ( const QString &password ) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "called" << endl; + + /* Cancel connection process if no password has been supplied. */ + if ( password.isEmpty () ) + { + disconnect ( Kopete::Account::Manual ); + return; + } + + /* Don't do anything if we are already connected. */ + if ( isConnected () ) + return; + + // instantiate new client backend or clean up old one + if ( !m_jabberClient ) + { + m_jabberClient = new JabberClient; + + QObject::connect ( m_jabberClient, SIGNAL ( csDisconnected () ), this, SLOT ( slotCSDisconnected () ) ); + QObject::connect ( m_jabberClient, SIGNAL ( csError ( int ) ), this, SLOT ( slotCSError ( int ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( tlsWarning ( int ) ), this, SLOT ( slotHandleTLSWarning ( int ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( connected () ), this, SLOT ( slotConnected () ) ); + QObject::connect ( m_jabberClient, SIGNAL ( error ( JabberClient::ErrorCode ) ), this, SLOT ( slotClientError ( JabberClient::ErrorCode ) ) ); + + QObject::connect ( m_jabberClient, SIGNAL ( subscription ( const XMPP::Jid &, const QString & ) ), + this, SLOT ( slotSubscription ( const XMPP::Jid &, const QString & ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( rosterRequestFinished ( bool ) ), + this, SLOT ( slotRosterRequestFinished ( bool ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( newContact ( const XMPP::RosterItem & ) ), + this, SLOT ( slotContactUpdated ( const XMPP::RosterItem & ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( contactUpdated ( const XMPP::RosterItem & ) ), + this, SLOT ( slotContactUpdated ( const XMPP::RosterItem & ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( contactDeleted ( const XMPP::RosterItem & ) ), + this, SLOT ( slotContactDeleted ( const XMPP::RosterItem & ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( resourceAvailable ( const XMPP::Jid &, const XMPP::Resource & ) ), + this, SLOT ( slotResourceAvailable ( const XMPP::Jid &, const XMPP::Resource & ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( resourceUnavailable ( const XMPP::Jid &, const XMPP::Resource & ) ), + this, SLOT ( slotResourceUnavailable ( const XMPP::Jid &, const XMPP::Resource & ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( messageReceived ( const XMPP::Message & ) ), + this, SLOT ( slotReceivedMessage ( const XMPP::Message & ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( incomingFileTransfer () ), + this, SLOT ( slotIncomingFileTransfer () ) ); + QObject::connect ( m_jabberClient, SIGNAL ( groupChatJoined ( const XMPP::Jid & ) ), + this, SLOT ( slotGroupChatJoined ( const XMPP::Jid & ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( groupChatLeft ( const XMPP::Jid & ) ), + this, SLOT ( slotGroupChatLeft ( const XMPP::Jid & ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( groupChatPresence ( const XMPP::Jid &, const XMPP::Status & ) ), + this, SLOT ( slotGroupChatPresence ( const XMPP::Jid &, const XMPP::Status & ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( groupChatError ( const XMPP::Jid &, int, const QString & ) ), + this, SLOT ( slotGroupChatError ( const XMPP::Jid &, int, const QString & ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( debugMessage ( const QString & ) ), + this, SLOT ( slotClientDebugMessage ( const QString & ) ) ); + } + else + { + m_jabberClient->disconnect (); + } + + // we need to use the old protocol for now + m_jabberClient->setUseXMPP09 ( true ); + + // set SSL flag (this should be converted to forceTLS when using the new protocol) + m_jabberClient->setUseSSL ( configGroup()->readBoolEntry ( "UseSSL", false ) ); + + // override server and port (this should be dropped when using the new protocol and no direct SSL) + m_jabberClient->setOverrideHost ( true, server (), port () ); + + // allow plaintext password authentication or not? + m_jabberClient->setAllowPlainTextPassword ( configGroup()->readBoolEntry ( "AllowPlainTextPassword", false ) ); + + // enable file transfer (if empty, IP will be set after connection has been established) + KGlobal::config()->setGroup ( "Jabber" ); + m_jabberClient->setFileTransfersEnabled ( true, KGlobal::config()->readEntry ( "LocalIP" ) ); + setS5BServerPort ( KGlobal::config()->readNumEntry ( "LocalPort", 8010 ) ); + + // + // Determine system name + // + if ( !configGroup()->readBoolEntry ( "HideSystemInfo", false ) ) + { + struct utsname utsBuf; + + uname (&utsBuf); + + m_jabberClient->setClientName ("Kopete"); + m_jabberClient->setClientVersion (kapp->aboutData ()->version ()); + m_jabberClient->setOSName (QString ("%1 %2").arg (utsBuf.sysname, 1).arg (utsBuf.release, 2)); + } + + // Set caps node information + m_jabberClient->setCapsNode(KOPETE_CAPS_NODE); + m_jabberClient->setCapsVersion(kapp->aboutData()->version()); + + // Set Disco Identity information + DiscoItem::Identity identity; + identity.category = "client"; + identity.type = "pc"; + identity.name = "Kopete"; + m_jabberClient->setDiscoIdentity(identity); + + //BEGIN TIMEZONE INFORMATION + // + // Set timezone information (code from Psi) + // Copyright (C) 2001-2003 Justin Karneges + // + time_t x; + time(&x); + char str[256]; + char fmt[32]; + int timezoneOffset; + QString timezoneString; + + strcpy ( fmt, "%z" ); + strftime ( str, 256, fmt, localtime ( &x ) ); + + if ( strcmp ( fmt, str ) ) + { + QString s = str; + if ( s.at ( 0 ) == '+' ) + s.remove ( 0, 1 ); + s.truncate ( s.length () - 2 ); + timezoneOffset = s.toInt(); + } + + strcpy ( fmt, "%Z" ); + strftime ( str, 256, fmt, localtime ( &x ) ); + + if ( strcmp ( fmt, str ) ) + timezoneString = str; + //END of timezone code + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Determined timezone " << timezoneString << " with UTC offset " << timezoneOffset << " hours." << endl; + + m_jabberClient->setTimeZone ( timezoneString, timezoneOffset ); + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Connecting to Jabber server " << server() << ":" << port() << endl; + + setPresence( XMPP::Status ("connecting", "", 0, true) ); + + switch ( m_jabberClient->connect ( XMPP::Jid ( accountId () + QString("/") + resource () ), password ) ) + { + case JabberClient::NoTLS: + // no SSL support, at the connecting stage this means the problem is client-side + KMessageBox::queuedMessageBox(Kopete::UI::Global::mainWidget (), KMessageBox::Error, + i18n ("SSL support could not be initialized for account %1. This is most likely because the QCA TLS plugin is not installed on your system."). + arg(myself()->contactId()), + i18n ("Jabber SSL Error")); + break; + + case JabberClient::Ok: + default: + // everything alright! + + break; + } + +} + +void JabberAccount::slotClientDebugMessage ( const QString &msg ) +{ + + kdDebug (JABBER_DEBUG_PROTOCOL) << k_funcinfo << msg << endl; + +} + +bool JabberAccount::handleTLSWarning ( JabberClient *jabberClient, int warning ) +{ + QString validityString, code; + + QString server = jabberClient->jid().domain (); + QString accountId = jabberClient->jid().bare (); + + switch ( warning ) + { + case QCA::TLS::NoCert: + validityString = i18n("No certificate was presented."); + code = "NoCert"; + break; + case QCA::TLS::HostMismatch: + validityString = i18n("The host name does not match the one in the certificate."); + code = "HostMismatch"; + break; + case QCA::TLS::Rejected: + validityString = i18n("The Certificate Authority rejected the certificate."); + code = "Rejected"; + break; + case QCA::TLS::Untrusted: + // FIXME: write better error message here + validityString = i18n("The certificate is untrusted."); + code = "Untrusted"; + break; + case QCA::TLS::SignatureFailed: + validityString = i18n("The signature is invalid."); + code = "SignatureFailed"; + break; + case QCA::TLS::InvalidCA: + validityString = i18n("The Certificate Authority is invalid."); + code = "InvalidCA"; + break; + case QCA::TLS::InvalidPurpose: + // FIXME: write better error message here + validityString = i18n("Invalid certificate purpose."); + code = "InvalidPurpose"; + break; + case QCA::TLS::SelfSigned: + validityString = i18n("The certificate is self-signed."); + code = "SelfSigned"; + break; + case QCA::TLS::Revoked: + validityString = i18n("The certificate has been revoked."); + code = "Revoked"; + break; + case QCA::TLS::PathLengthExceeded: + validityString = i18n("Maximum certificate chain length was exceeded."); + code = "PathLengthExceeded"; + break; + case QCA::TLS::Expired: + validityString = i18n("The certificate has expired."); + code = "Expired"; + break; + case QCA::TLS::Unknown: + default: + validityString = i18n("An unknown error occurred trying to validate the certificate."); + code = "Unknown"; + break; + } + + return ( KMessageBox::warningContinueCancel ( Kopete::UI::Global::mainWidget (), + i18n("

The certificate of server %1 could not be validated for account %2: %3

Do you want to continue?

"). + arg(server, accountId, validityString), + i18n("Jabber Connection Certificate Problem"), + KStdGuiItem::cont(), + QString("KopeteTLSWarning") + server + code) == KMessageBox::Continue ); + +} + +void JabberAccount::slotHandleTLSWarning ( int validityResult ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Handling TLS warning..." << endl; + + if ( handleTLSWarning ( m_jabberClient, validityResult ) ) + { + // resume stream + m_jabberClient->continueAfterTLSWarning (); + } + else + { + // disconnect stream + disconnect ( Kopete::Account::Manual ); + } + +} + +void JabberAccount::slotClientError ( JabberClient::ErrorCode errorCode ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Handling client error..." << endl; + + switch ( errorCode ) + { + case JabberClient::NoTLS: + default: + KMessageBox::queuedMessageBox ( Kopete::UI::Global::mainWidget (), KMessageBox::Error, + i18n ("An encrypted connection with the Jabber server could not be established."), + i18n ("Jabber Connection Error")); + disconnect ( Kopete::Account::Manual ); + break; + } + +} + +void JabberAccount::slotConnected () +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Connected to Jabber server." << endl; + +#ifdef SUPPORT_JINGLE + if(!m_voiceCaller) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Creating Jingle Voice caller..." << endl; + m_voiceCaller = new JingleVoiceCaller( this ); + QObject::connect(m_voiceCaller,SIGNAL(incoming(const Jid&)),this,SLOT(slotIncomingVoiceCall( const Jid& ))); + m_voiceCaller->initialize(); + } + + + +#if 0 + if(!m_jingleSessionManager) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Creating Jingle Session Manager..." << endl; + m_jingleSessionManager = new JingleSessionManager( this ); + QObject::connect(m_jingleSessionManager, SIGNAL(incomingSession(const QString &, JingleSession *)), this, SLOT(slotIncomingJingleSession(const QString &, JingleSession *))); + } +#endif + + // Set caps extensions + m_jabberClient->client()->addExtension("voice-v1", Features(QString("http://www.google.com/xmpp/protocol/voice/v1"))); +#endif + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Requesting roster..." << endl; + m_jabberClient->requestRoster (); +} + +void JabberAccount::slotRosterRequestFinished ( bool success ) +{ + + if ( success ) + { + // the roster was imported successfully, clear + // all "dirty" items from the contact list + contactPool()->cleanUp (); + } + + /* Since we are online now, set initial presence. Don't do this + * before the roster request or we will receive presence + * information before we have updated our roster with actual + * contacts from the server! (Iris won't forward presence + * information in that case either). */ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Setting initial presence..." << endl; + setPresence ( m_initialPresence ); + +} + +void JabberAccount::slotIncomingFileTransfer () +{ + + // delegate the work to a file transfer object + new JabberFileTransfer ( this, client()->fileTransferManager()->takeIncoming () ); + +} + +void JabberAccount::setOnlineStatus( const Kopete::OnlineStatus& status , const QString &reason) +{ + XMPP::Status xmppStatus = m_protocol->kosToStatus( status, reason); + + if( status.status() == Kopete::OnlineStatus::Offline ) + { + xmppStatus.setIsAvailable( false ); + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "CROSS YOUR FINGERS! THIS IS GONNA BE WILD" << endl; + disconnect (Manual, xmppStatus); + return; + } + + if( isConnecting () ) + { + return; + } + + + if ( !isConnected () ) + { + // we are not connected yet, so connect now + m_initialPresence = xmppStatus; + connect ( status ); + } + else + { + setPresence ( xmppStatus ); + } +} + +void JabberAccount::disconnect ( Kopete::Account::DisconnectReason reason ) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "disconnect() called" << endl; + + if (isConnected ()) + { + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Still connected, closing connection..." << endl; + /* Tell backend class to disconnect. */ + m_jabberClient->disconnect (); + } + + // make sure that the connection animation gets stopped if we're still + // in the process of connecting + setPresence ( XMPP::Status ("", "", 0, false) ); + m_initialPresence = XMPP::Status ("", "", 5, true); + + /* FIXME: + * We should delete the JabberClient instance here, + * but active timers in Iris prevent us from doing so. + * (in a failed connection attempt, these timers will + * try to access an already deleted object). + * Instead, the instance will lurk until the next + * connection attempt. + */ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Disconnected." << endl; + + disconnected ( reason ); +} + +void JabberAccount::disconnect( Kopete::Account::DisconnectReason reason, XMPP::Status &status ) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "disconnect( reason, status ) called" << endl; + + if (isConnected ()) + { + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Still connected, closing connection..." << endl; + /* Tell backend class to disconnect. */ + m_jabberClient->disconnect (status); + } + + // make sure that the connection animation gets stopped if we're still + // in the process of connecting + setPresence ( status ); + + /* FIXME: + * We should delete the JabberClient instance here, + * but active timers in Iris prevent us from doing so. + * (in a failed connection attempt, these timers will + * try to access an already deleted object). + * Instead, the instance will lurk until the next + * connection attempt. + */ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Disconnected." << endl; + + Kopete::Account::disconnected ( reason ); +} + +void JabberAccount::disconnect () +{ + disconnect ( Manual ); +} + +void JabberAccount::slotConnect () +{ + connect (); +} + +void JabberAccount::slotDisconnect () +{ + disconnect ( Kopete::Account::Manual ); +} + +void JabberAccount::slotCSDisconnected () +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Disconnected from Jabber server." << endl; + + /* + * We should delete the JabberClient instance here, + * but timers etc prevent us from doing so. Iris does + * not like to be deleted from a slot. + */ + + /* It seems that we don't get offline notifications when going offline + * with the protocol, so clear all resources manually. */ + resourcePool()->clear(); + +} + +void JabberAccount::handleStreamError (int streamError, int streamCondition, int connectorCode, const QString &server, Kopete::Account::DisconnectReason &errorClass) +{ + QString errorText; + QString errorCondition; + + errorClass = Kopete::Account::InvalidHost; + + /* + * Display error to user. + * FIXME: for unknown errors, maybe add error codes? + */ + switch(streamError) + { + case XMPP::Stream::ErrParse: + errorClass = Kopete::Account::Unknown; + errorText = i18n("Malformed packet received."); + break; + + case XMPP::Stream::ErrProtocol: + errorClass = Kopete::Account::Unknown; + errorText = i18n("There was an unrecoverable error in the protocol."); + break; + + case XMPP::Stream::ErrStream: + switch(streamCondition) + { + case XMPP::Stream::GenericStreamError: + errorCondition = i18n("Generic stream error (sorry, I do not have a more-detailed reason)"); + break; + case XMPP::Stream::Conflict: + // FIXME: need a better error message here + errorCondition = i18n("There was a conflict in the information received."); + break; + case XMPP::Stream::ConnectionTimeout: + errorCondition = i18n("The stream timed out."); + break; + case XMPP::Stream::InternalServerError: + errorCondition = i18n("Internal server error."); + break; + case XMPP::Stream::InvalidFrom: + errorCondition = i18n("Stream packet received from an invalid address."); + break; + case XMPP::Stream::InvalidXml: + errorCondition = i18n("Malformed stream packet received."); + break; + case XMPP::Stream::PolicyViolation: + // FIXME: need a better error message here + errorCondition = i18n("Policy violation in the protocol stream."); + break; + case XMPP::Stream::ResourceConstraint: + // FIXME: need a better error message here + errorCondition = i18n("Resource constraint."); + break; + case XMPP::Stream::SystemShutdown: + // FIXME: need a better error message here + errorCondition = i18n("System shutdown."); + break; + default: + errorCondition = i18n("Unknown reason."); + break; + } + + errorText = i18n("There was an error in the protocol stream: %1").arg(errorCondition); + break; + + case XMPP::ClientStream::ErrConnection: + switch(connectorCode) + { + case KNetwork::KSocketBase::LookupFailure: + errorClass = Kopete::Account::InvalidHost; + errorCondition = i18n("Host not found."); + break; + case KNetwork::KSocketBase::AddressInUse: + errorCondition = i18n("Address is already in use."); + break; + case KNetwork::KSocketBase::AlreadyCreated: + errorCondition = i18n("Cannot recreate the socket."); + break; + case KNetwork::KSocketBase::AlreadyBound: + errorCondition = i18n("Cannot bind the socket again."); + break; + case KNetwork::KSocketBase::AlreadyConnected: + errorCondition = i18n("Socket is already connected."); + break; + case KNetwork::KSocketBase::NotConnected: + errorCondition = i18n("Socket is not connected."); + break; + case KNetwork::KSocketBase::NotBound: + errorCondition = i18n("Socket is not bound."); + break; + case KNetwork::KSocketBase::NotCreated: + errorCondition = i18n("Socket has not been created."); + break; + case KNetwork::KSocketBase::WouldBlock: + errorCondition = i18n("Socket operation would block. You should not see this error, please use \"Report Bug\" from the Help menu."); + break; + case KNetwork::KSocketBase::ConnectionRefused: + errorCondition = i18n("Connection refused."); + break; + case KNetwork::KSocketBase::ConnectionTimedOut: + errorCondition = i18n("Connection timed out."); + break; + case KNetwork::KSocketBase::InProgress: + errorCondition = i18n("Connection attempt already in progress."); + break; + case KNetwork::KSocketBase::NetFailure: + errorCondition = i18n("Network failure."); + break; + case KNetwork::KSocketBase::NotSupported: + errorCondition = i18n("Operation is not supported."); + break; + case KNetwork::KSocketBase::Timeout: + errorCondition = i18n("Socket timed out."); + break; + default: + errorClass = Kopete::Account::ConnectionReset; + //errorCondition = i18n("Sorry, something unexpected happened that I do not know more about."); + break; + } + if(!errorCondition.isEmpty()) + errorText = i18n("There was a connection error: %1").arg(errorCondition); + break; + + case XMPP::ClientStream::ErrNeg: + switch(streamCondition) + { + case XMPP::ClientStream::HostUnknown: + // FIXME: need a better error message here + errorCondition = i18n("Unknown host."); + break; + case XMPP::ClientStream::RemoteConnectionFailed: + // FIXME: need a better error message here + errorCondition = i18n("Could not connect to a required remote resource."); + break; + case XMPP::ClientStream::SeeOtherHost: + errorCondition = i18n("It appears we have been redirected to another server; I do not know how to handle this."); + break; + case XMPP::ClientStream::UnsupportedVersion: + errorCondition = i18n("Unsupported protocol version."); + break; + default: + errorCondition = i18n("Unknown error."); + break; + } + + errorText = i18n("There was a negotiation error: %1").arg(errorCondition); + break; + + case XMPP::ClientStream::ErrTLS: + switch(streamCondition) + { + case XMPP::ClientStream::TLSStart: + errorCondition = i18n("Server rejected our request to start the TLS handshake."); + break; + case XMPP::ClientStream::TLSFail: + errorCondition = i18n("Failed to establish a secure connection."); + break; + default: + errorCondition = i18n("Unknown error."); + break; + } + + errorText = i18n("There was a Transport Layer Security (TLS) error: %1").arg(errorCondition); + break; + + case XMPP::ClientStream::ErrAuth: + switch(streamCondition) + { + case XMPP::ClientStream::GenericAuthError: + errorCondition = i18n("Login failed with unknown reason."); + break; + case XMPP::ClientStream::NoMech: + errorCondition = i18n("No appropriate authentication mechanism available."); + break; + case XMPP::ClientStream::BadProto: + errorCondition = i18n("Bad SASL authentication protocol."); + break; + case XMPP::ClientStream::BadServ: + errorCondition = i18n("Server failed mutual authentication."); + break; + case XMPP::ClientStream::EncryptionRequired: + errorCondition = i18n("Encryption is required but not present."); + break; + case XMPP::ClientStream::InvalidAuthzid: + errorCondition = i18n("Invalid user ID."); + break; + case XMPP::ClientStream::InvalidMech: + errorCondition = i18n("Invalid mechanism."); + break; + case XMPP::ClientStream::InvalidRealm: + errorCondition = i18n("Invalid realm."); + break; + case XMPP::ClientStream::MechTooWeak: + errorCondition = i18n("Mechanism too weak."); + break; + case XMPP::ClientStream::NotAuthorized: + errorCondition = i18n("Wrong credentials supplied. (check your user ID and password)"); + break; + case XMPP::ClientStream::TemporaryAuthFailure: + errorCondition = i18n("Temporary failure, please try again later."); + break; + default: + errorCondition = i18n("Unknown error."); + break; + } + + errorText = i18n("There was an error authenticating with the server: %1").arg(errorCondition); + break; + + case XMPP::ClientStream::ErrSecurityLayer: + switch(streamCondition) + { + case XMPP::ClientStream::LayerTLS: + errorCondition = i18n("Transport Layer Security (TLS) problem."); + break; + case XMPP::ClientStream::LayerSASL: + errorCondition = i18n("Simple Authentication and Security Layer (SASL) problem."); + break; + default: + errorCondition = i18n("Unknown error."); + break; + } + + errorText = i18n("There was an error in the security layer: %1").arg(errorCondition); + break; + + case XMPP::ClientStream::ErrBind: + switch(streamCondition) + { + case XMPP::ClientStream::BindNotAllowed: + errorCondition = i18n("No permission to bind the resource."); + break; + case XMPP::ClientStream::BindConflict: + errorCondition = i18n("The resource is already in use."); + break; + default: + errorCondition = i18n("Unknown error."); + break; + } + + errorText = i18n("Could not bind a resource: %1").arg(errorCondition); + break; + + default: + errorText = i18n("Unknown error."); + break; + } + + /* + * This mustn't be queued as otherwise the reconnection + * API will attempt to reconnect, queueing another + * error until memory is exhausted. + */ + if(!errorText.isEmpty()) + KMessageBox::error (Kopete::UI::Global::mainWidget (), + errorText, + i18n("Connection problem with Jabber server %1").arg(server)); + + +} + +void JabberAccount::slotCSError ( int error ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Error in stream signalled." << endl; + + if ( ( error == XMPP::ClientStream::ErrAuth ) + && ( client()->clientStream()->errorCondition () == XMPP::ClientStream::NotAuthorized ) ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Incorrect password, retrying." << endl; + disconnect(Kopete::Account::BadPassword); + } + else + { + Kopete::Account::DisconnectReason errorClass = Kopete::Account::Unknown; + + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Disconnecting." << endl; + + // display message to user + if(!m_removing) //when removing the account, connection errors are normal. + handleStreamError (error, client()->clientStream()->errorCondition (), client()->clientConnector()->errorCode (), server (), errorClass); + + disconnect ( errorClass ); + + /* slotCSDisconnected will not be called*/ + resourcePool()->clear(); + } + +} + +/* Set presence (usually called by dialog widget). */ +void JabberAccount::setPresence ( const XMPP::Status &status ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Status: " << status.show () << ", Reason: " << status.status () << endl; + + // fetch input status + XMPP::Status newStatus = status; + + // TODO: Check if Caps is enabled + // Send entity capabilities + if( client() ) + { + newStatus.setCapsNode( client()->capsNode() ); + newStatus.setCapsVersion( client()->capsVersion() ); + newStatus.setCapsExt( client()->capsExt() ); + } + + // make sure the status gets the correct priority + newStatus.setPriority ( configGroup()->readNumEntry ( "Priority", 5 ) ); + + XMPP::Jid jid ( myself()->contactId () ); + XMPP::Resource newResource ( resource (), newStatus ); + + // update our resource in the resource pool + resourcePool()->addResource ( jid, newResource ); + + // make sure that we only consider our own resource locally + resourcePool()->lockToResource ( jid, newResource ); + + /* + * Unless we are in the connecting status, send a presence packet to the server + */ + if(status.show () != QString("connecting") ) + { + /* + * Make sure we are actually connected before sending out a packet. + */ + if (isConnected()) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Sending new presence to the server." << endl; + + XMPP::JT_Presence * task = new XMPP::JT_Presence ( client()->rootTask ()); + + task->pres ( newStatus ); + task->go ( true ); + } + else + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "We were not connected, presence update aborted." << endl; + } + } + +} + +void JabberAccount::slotSendRaw () +{ + /* Check if we're connected. */ + if ( !isConnected () ) + { + errorConnectFirst (); + return; + } + + new dlgJabberSendRaw ( client (), Kopete::UI::Global::mainWidget()); + +} + +void JabberAccount::slotSubscription (const XMPP::Jid & jid, const QString & type) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << jid.full () << ", " << type << endl; + + if (type == "subscribe") + { + /* + * A user wants to subscribe to our presence. + */ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << jid.full () << " is asking for authorization to subscribe." << endl; + + // Is the user already in our contact list? + JabberBaseContact *contact = contactPool()->findExactMatch( jid ); + Kopete::MetaContact *metaContact=0L; + if(contact) + metaContact=contact->metaContact(); + + int hideFlags=Kopete::UI::ContactAddedNotifyDialog::InfoButton; + if( metaContact && !metaContact->isTemporary() ) + hideFlags |= Kopete::UI::ContactAddedNotifyDialog::AddCheckBox | Kopete::UI::ContactAddedNotifyDialog::AddGroupBox ; + + Kopete::UI::ContactAddedNotifyDialog *dialog= + new Kopete::UI::ContactAddedNotifyDialog( jid.full() ,QString::null,this, hideFlags ); + QObject::connect(dialog,SIGNAL(applyClicked(const QString&)), + this,SLOT(slotContactAddedNotifyDialogClosed(const QString& ))); + dialog->show(); + } + else if (type == "unsubscribed") + { + /* + * Someone else removed our authorization to see them. + */ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << jid.full() << " revoked our presence authorization" << endl; + + XMPP::JT_Roster *task; + + switch (KMessageBox::warningYesNo (Kopete::UI::Global::mainWidget(), + i18n + ("The Jabber user %1 removed %2's subscription to them. " + "This account will no longer be able to view their online/offline status. " + "Do you want to delete the contact?"). + arg (jid.full(), 1).arg (accountId(), 2), i18n ("Notification"), KStdGuiItem::del(), i18n("Keep"))) + { + + case KMessageBox::Yes: + /* + * Delete this contact from our roster. + */ + task = new XMPP::JT_Roster ( client()->rootTask ()); + + task->remove (jid); + task->go (true); + + break; + + default: + /* + * We want to leave the contact in our contact list. + * In this case, we need to delete all the resources + * we have for it, as the Jabber server won't signal us + * that the contact is offline now. + */ + resourcePool()->removeAllResources ( jid ); + break; + + } + } +} + +void JabberAccount::slotContactAddedNotifyDialogClosed( const QString & contactid ) +{ // the dialog that asked the authorisation is closed. (it was shown in slotSubscrition) + + XMPP::JT_Presence *task; + XMPP::Jid jid(contactid); + + const Kopete::UI::ContactAddedNotifyDialog *dialog = + dynamic_cast(sender()); + if(!dialog || !isConnected()) + return; + + if ( dialog->authorized() ) + { + /* + * Authorize user. + */ + + task = new XMPP::JT_Presence ( client()->rootTask () ); + task->sub ( jid, "subscribed" ); + task->go ( true ); + } + else + { + /* + * Reject subscription. + */ + task = new XMPP::JT_Presence ( client()->rootTask () ); + task->sub ( jid, "unsubscribed" ); + task->go ( true ); + } + + + if(dialog->added()) + { + Kopete::MetaContact *parentContact=dialog->addContact(); + if(parentContact) + { + QStringList groupNames; + Kopete::GroupList groupList = parentContact->groups(); + for(Kopete::Group *group = groupList.first(); group; group = groupList.next()) + groupNames += group->displayName(); + + XMPP::RosterItem item; +// XMPP::Jid jid ( contactId ); + + item.setJid ( jid ); + item.setName ( parentContact->displayName() ); + item.setGroups ( groupNames ); + + // add the new contact to our roster. + XMPP::JT_Roster * rosterTask = new XMPP::JT_Roster ( client()->rootTask () ); + + rosterTask->set ( item.jid(), item.name(), item.groups() ); + rosterTask->go ( true ); + + // send a subscription request. + XMPP::JT_Presence *presenceTask = new XMPP::JT_Presence ( client()->rootTask () ); + + presenceTask->sub ( jid, "subscribe" ); + presenceTask->go ( true ); + } + } +} + + + +void JabberAccount::slotContactUpdated (const XMPP::RosterItem & item) +{ + + /** + * Subscription types are: Both, From, To, Remove, None. + * Both: Both sides have authed each other, each side + * can see each other's presence + * From: The other side can see us. + * To: We can see the other side. (implies we are + * authed) + * Remove: Other side revoked our subscription request. + * Not to be handled here. + * None: No subscription. + * + * Regardless of the subscription type, we have to add + * a roster item here. + */ + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "New roster item " << item.jid().full () << " (Subscription: " << item.subscription().toString () << ")" << endl; + + /* + * See if the contact need to be added, according to the criterias of + * JEP-0162: Best Practices for Roster and Subscription Management + * http://www.jabber.org/jeps/jep-0162.html#contacts + */ + bool need_to_add=false; + if(item.subscription().type() == XMPP::Subscription::Both || item.subscription().type() == XMPP::Subscription::To) + need_to_add = true; + else if( !item.ask().isEmpty() ) + need_to_add = true; + else if( !item.name().isEmpty() || !item.groups().isEmpty() ) + need_to_add = true; + + /* + * See if the contact is already on our contact list + */ + Kopete::Contact *c= contactPool()->findExactMatch( item.jid() ); + + if( c && c == c->Kopete::Contact::account()->myself() ) //don't use JabberBaseContact::account() which return alwaus the JabberAccount, and not the transport + { + // don't let remove the gateway contact, eh! + need_to_add = true; + } + + if(need_to_add) + { + Kopete::MetaContact *metaContact=0L; + if (!c) + { + /* + * No metacontact has been found which contains a contact with this ID, + * so add a new metacontact to the list. + */ + metaContact = new Kopete::MetaContact (); + QStringList groups = item.groups (); + + // add this metacontact to all groups the contact is a member of + for (QStringList::Iterator it = groups.begin (); it != groups.end (); ++it) + metaContact->addToGroup (Kopete::ContactList::self ()->findGroup (*it)); + + // put it onto contact list + Kopete::ContactList::self ()->addMetaContact ( metaContact ); + } + else + { + metaContact=c->metaContact(); + //TODO: syncronize groups + } + + /* + * Add / update the contact in our pool. In case the contact is already there, + * it will be updated. In case the contact is not in the meta contact yet, it + * will be added to it. + * The "dirty" flag is false here, because we just received the contact from + * the server's roster. As such, it is now a synchronized entry. + */ + JabberContact *contact = contactPool()->addContact ( item, metaContact, false ); + + /* + * Set authorization property + */ + if ( !item.ask().isEmpty () ) + { + contact->setProperty ( protocol()->propAuthorizationStatus, i18n ( "Waiting for authorization" ) ); + } + else + { + contact->removeProperty ( protocol()->propAuthorizationStatus ); + } + } + else if(c) //we don't need to add it, and it is in the contactlist + { + Kopete::MetaContact *metaContact=c->metaContact(); + if(metaContact->isTemporary()) + return; + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << c->contactId() << + " is on the contactlist while it shouldn't. we are removing it. - " << c << endl; + delete c; + if(metaContact->contacts().isEmpty()) + Kopete::ContactList::self()->removeMetaContact( metaContact ); + } + +} + +void JabberAccount::slotContactDeleted (const XMPP::RosterItem & item) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Deleting contact " << item.jid().full () << endl; + + // since the contact instance will get deleted here, the GUI should be updated + contactPool()->removeContact ( item.jid () ); + +} + +void JabberAccount::slotReceivedMessage (const XMPP::Message & message) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "New message from " << message.from().full () << endl; + + JabberBaseContact *contactFrom; + + if ( message.type() == "groupchat" ) + { + // this is a group chat message, forward it to the group contact + // (the one without resource name) + XMPP::Jid jid ( message.from().userHost () ); + + // try to locate an exact match in our pool first + contactFrom = contactPool()->findExactMatch ( jid ); + + /** + * If there was no exact match, something is really messed up. + * We can't receive group chat messages from rooms that we are + * not a member of and if the room contact vanished somehow, + * we're in deep trouble. + */ + if ( !contactFrom ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "WARNING: Received a groupchat message but couldn't find room contact. Ignoring message." << endl; + return; + } + } + else + { + // try to locate an exact match in our pool first + contactFrom = contactPool()->findExactMatch ( message.from () ); + + if ( !contactFrom ) + { + // we have no exact match, try a broader search + contactFrom = contactPool()->findRelevantRecipient ( message.from () ); + } + + // see if we found the contact in our pool + if ( !contactFrom ) + { + // eliminate the resource from this contact, + // otherwise we will add the contact with the + // resource to our list + // NOTE: This is a stupid way to do it, but + // message.from().setResource("") had no + // effect. Iris bug? + XMPP::Jid jid ( message.from().userHost () ); + + // the contact is not in our pool, add it as a temporary contact + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << jid.full () << " is unknown to us, creating temporary contact." << endl; + + Kopete::MetaContact *metaContact = new Kopete::MetaContact (); + + metaContact->setTemporary (true); + + contactFrom = contactPool()->addContact ( XMPP::RosterItem ( jid ), metaContact, false ); + + Kopete::ContactList::self ()->addMetaContact (metaContact); + } + } + + // pass the message on to the contact + contactFrom->handleIncomingMessage (message); + +} + +void JabberAccount::slotJoinNewChat () +{ + + if (!isConnected ()) + { + errorConnectFirst (); + return; + } + + dlgJabberChatJoin *joinDialog = new dlgJabberChatJoin ( this, Kopete::UI::Global::mainWidget () ); + joinDialog->show (); + +} + +void JabberAccount::slotGroupChatJoined (const XMPP::Jid & jid) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Joined group chat " << jid.full () << endl; + + // Create new meta contact that holds the group chat contact. + Kopete::MetaContact *metaContact = new Kopete::MetaContact (); + + metaContact->setTemporary ( true ); + + // Create a groupchat contact for this room + JabberGroupContact *groupContact = dynamic_cast( contactPool()->addGroupContact ( XMPP::RosterItem ( jid ), true, metaContact, false ) ); + + if(groupContact) + { + // Add the groupchat contact to the meta contact. + //metaContact->addContact ( groupContact ); + + Kopete::ContactList::self ()->addMetaContact ( metaContact ); + } + else + delete metaContact; + + /** + * Add an initial resource for this contact to the pool. We need + * to do this to be able to lock the group status to our own presence. + * Our own presence will be updated right after this method returned + * by slotGroupChatPresence(), since the server will signal our own + * presence back to us. + */ + resourcePool()->addResource ( XMPP::Jid ( jid.userHost () ), XMPP::Resource ( jid.resource () ) ); + + // lock the room to our own status + resourcePool()->lockToResource ( XMPP::Jid ( jid.userHost () ), jid.resource () ); + + m_bookmarks->insertGroupChat(jid); +} + +void JabberAccount::slotGroupChatLeft (const XMPP::Jid & jid) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo "Left groupchat " << jid.full () << endl; + + // remove group contact from list + Kopete::Contact *contact = + Kopete::ContactList::self()->findContact( protocol()->pluginId() , accountId() , jid.userHost() ); + + if ( contact ) + { + Kopete::MetaContact *metaContact= contact->metaContact(); + if( metaContact && metaContact->isTemporary() ) + Kopete::ContactList::self()->removeMetaContact ( metaContact ); + else + contact->deleteLater(); + } + + // now remove it from our pool, which should clean up all subcontacts as well + contactPool()->removeContact ( XMPP::Jid ( jid.userHost () ) ); + +} + +void JabberAccount::slotGroupChatPresence (const XMPP::Jid & jid, const XMPP::Status & status) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Received groupchat presence for room " << jid.full () << endl; + + // fetch room contact (the one without resource) + JabberGroupContact *groupContact = dynamic_cast( contactPool()->findExactMatch ( XMPP::Jid ( jid.userHost () ) ) ); + + if ( !groupContact ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "WARNING: Groupchat presence signalled, but we don't have a room contact?" << endl; + return; + } + + if ( !status.isAvailable () ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << jid.full () << " has become unavailable, removing from room" << endl; + + // remove the resource from the pool + resourcePool()->removeResource ( jid, XMPP::Resource ( jid.resource (), status ) ); + + // the person has become unavailable, remove it + groupContact->removeSubContact ( XMPP::RosterItem ( jid ) ); + } + else + { + // add a resource for this contact to the pool (existing resources will be updated) + resourcePool()->addResource ( jid, XMPP::Resource ( jid.resource (), status ) ); + + // make sure the contact exists in the room (if it exists already, it won't be added twice) + groupContact->addSubContact ( XMPP::RosterItem ( jid ) ); + } + +} + +void JabberAccount::slotGroupChatError (const XMPP::Jid &jid, int error, const QString &reason) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Group chat error - room " << jid.full () << " had error " << error << " (" << reason << ")" << endl; + + switch (error) + { + case JabberClient::InvalidPasswordForMUC: + { + QCString password; + int result = KPasswordDialog::getPassword(password, i18n("A password is required to join the room %1.").arg(jid.node())); + if (result == KPasswordDialog::Accepted) + m_jabberClient->joinGroupChat(jid.domain(), jid.node(), jid.resource(), password); + } + break; + + case JabberClient::NicknameConflict: + { + bool ok; + QString nickname = KInputDialog::getText(i18n("Error trying to join %1 : nickname %2 is already in use").arg(jid.node(), jid.resource()), + i18n("Give your nickname"), + QString(), + &ok); + if (ok) + { + m_jabberClient->joinGroupChat(jid.domain(), jid.node(), nickname); + } + } + break; + + case JabberClient::BannedFromThisMUC: + KMessageBox::queuedMessageBox ( Kopete::UI::Global::mainWidget (), + KMessageBox::Error, + i18n ("You can't join the room %1 because you were banned").arg(jid.node()), + i18n ("Jabber Group Chat") ); + break; + + case JabberClient::MaxUsersReachedForThisMuc: + KMessageBox::queuedMessageBox ( Kopete::UI::Global::mainWidget (), + KMessageBox::Error, + i18n ("You can't join the room %1 because the maximum users has been reached").arg(jid.node()), + i18n ("Jabber Group Chat") ); + break; + + default: + { + QString detailedReason = reason.isEmpty () ? i18n ( "No reason given by the server" ) : reason; + + KMessageBox::queuedMessageBox ( Kopete::UI::Global::mainWidget (), + KMessageBox::Error, + i18n ("There was an error processing your request for group chat %1. (Reason: %2, Code %3)").arg ( jid.full (), detailedReason, QString::number ( error ) ), + i18n ("Jabber Group Chat") ); + } + } +} + +void JabberAccount::slotResourceAvailable (const XMPP::Jid & jid, const XMPP::Resource & resource) +{ + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "New resource available for " << jid.full() << endl; + + resourcePool()->addResource ( jid, resource ); + +} + +void JabberAccount::slotResourceUnavailable (const XMPP::Jid & jid, const XMPP::Resource & resource) +{ + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Resource now unavailable for " << jid.full () << endl; + + resourcePool()->removeResource ( jid, resource ); + +} + +void JabberAccount::slotEditVCard () +{ + static_cast( myself() )->slotUserInfo (); +} + +void JabberAccount::slotGlobalIdentityChanged (const QString &key, const QVariant &value) +{ + // Check if this account is excluded from Global Identity. + if( !configGroup()->readBoolEntry("ExcludeGlobalIdentity", false) ) + { + JabberContact *jabberMyself = static_cast( myself() ); + if( key == Kopete::Global::Properties::self()->nickName().key() ) + { + QString oldNick = jabberMyself->property( protocol()->propNickName ).value().toString(); + QString newNick = value.toString(); + + if( newNick != oldNick && isConnected() ) + { + jabberMyself->setProperty( protocol()->propNickName, newNick ); + jabberMyself->slotSendVCard(); + } + } + if( key == Kopete::Global::Properties::self()->photo().key() ) + { + if( isConnected() ) + { + jabberMyself->setPhoto( value.toString() ); + jabberMyself->slotSendVCard(); + } + } + } +} + +const QString JabberAccount::resource () const +{ + + return configGroup()->readEntry ( "Resource", "Kopete" ); + +} + +const QString JabberAccount::server () const +{ + + return configGroup()->readEntry ( "Server" ); + +} + +const int JabberAccount::port () const +{ + + return configGroup()->readNumEntry ( "Port", 5222 ); + +} + +void JabberAccount::slotGetServices () +{ + dlgJabberServices *dialog = new dlgJabberServices (this); + + dialog->show (); + dialog->raise (); +} + +void JabberAccount::slotIncomingVoiceCall( const Jid &jid ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << endl; +#ifdef SUPPORT_JINGLE + if(voiceCaller()) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Showing voice dialog." << endl; + JingleVoiceSessionDialog *voiceDialog = new JingleVoiceSessionDialog( jid, voiceCaller() ); + voiceDialog->show(); + } +#else + Q_UNUSED(jid); +#endif +} + +// void JabberAccount::slotIncomingJingleSession( const QString &sessionType, JingleSession *session ) +// { +// #ifdef SUPPORT_JINGLE +// if(sessionType == "http://www.google.com/session/phone") +// { +// QString from = ((XMPP::Jid)session->peers().first()).full(); +// //KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Information, QString("Received a voice session invitation from %1.").arg(from) ); +// JingleVoiceSessionDialog *voiceDialog = new JingleVoiceSessionDialog( static_cast(session) ); +// voiceDialog->show(); +// } +// #else +// Q_UNUSED( sessionType ); +// Q_UNUSED( session ); +// #endif +// } + + +void JabberAccount::addTransport( JabberTransport * tr, const QString &jid ) +{ + m_transports.insert(jid,tr); +} + +void JabberAccount::removeTransport( const QString &jid ) +{ + m_transports.remove(jid); +} + +bool JabberAccount::removeAccount( ) +{ + if(!m_removing) + { + int result=KMessageBox::warningYesNoCancel( Kopete::UI::Global::mainWidget () , + i18n( "Do you want to also unregister \"%1\" from the Jabber server ?\n" + "If you unregister, all your contact list may be removed on the server," + "And you will never be able to connect to this account with any client").arg( accountLabel() ), + i18n("Unregister"), + KGuiItem(i18n( "Remove and Unregister" ), "editdelete"), + KGuiItem(i18n( "Remove from kopete only"), "edittrash"), + QString(), KMessageBox::Notify | KMessageBox::Dangerous ); + if(result == KMessageBox::Cancel) + { + return false; + } + else if(result == KMessageBox::Yes) + { + if (!isConnected()) + { + errorConnectFirst (); + return false; + } + + XMPP::JT_Register *task = new XMPP::JT_Register ( client()->rootTask () ); + QObject::connect ( task, SIGNAL ( finished () ), this, SLOT ( slotUnregisterFinished ) ); + task->unreg (); + task->go ( true ); + m_removing=true; + // from my experiment, not all server reply us with a response. it simply dosconnect + // so after one seconde, we will force to remove the account + QTimer::singleShot(1111, this, SLOT(slotUnregisterFinished())); + + return false; //the account will be removed when the task will be finished + } + } + + //remove transports from config file. + QMap tranposrts_copy=m_transports; + QMap::Iterator it; + for ( it = tranposrts_copy.begin(); it != tranposrts_copy.end(); ++it ) + { + (*it)->jabberAccountRemoved(); + } + return true; +} + +void JabberAccount::slotUnregisterFinished( ) +{ + const XMPP::JT_Register * task = dynamic_cast(sender ()); + + if ( task && ! task->success ()) + { + KMessageBox::queuedMessageBox ( 0L, KMessageBox::Error, + i18n ("An error occured when trying to remove the account:\n%1").arg(task->statusString()), + i18n ("Jabber Account Unregistration")); + m_removing=false; + return; + } + if(m_removing) //it may be because this is now the timer. + Kopete::AccountManager::self()->removeAccount( this ); //this will delete this +} + + + + + +#include "jabberaccount.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/protocols/jabber/jabberaccount.h b/kopete/protocols/jabber/jabberaccount.h new file mode 100644 index 00000000..3731b590 --- /dev/null +++ b/kopete/protocols/jabber/jabberaccount.h @@ -0,0 +1,309 @@ + +/*************************************************************************** + jabberaccount.h - core Jabber account class + ------------------- + begin : Sat Mar 8 2003 + copyright : (C) 2003 by Till Gerken + Based on JabberProtocol by Daniel Stone + and Till Gerken . + copyright : (C) 2006 by Olivier Goffart + + Kopete (C) 2001-2003 Kopete developers . + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef JABBERACCOUNT_H +#define JABBERACCOUNT_H + +#ifdef HAVE_CONFIG_H +#include +#endif + +// we need these for type reasons +#include +#include +#include +#include "jabberclient.h" + +class QString; +class QStringList; +class KActionMenu; +class JabberResourcePool; +class JabberContact; +class JabberContactPool; +class JabberProtocol; +class JabberTransport; +class JabberBookmarks; + +namespace Kopete { class MetaContact; } + +#ifdef SUPPORT_JINGLE +//class JingleSessionManager; +//class JingleSession; +class VoiceCaller; +#endif + + +/* @author Daniel Stone, Till Gerken */ + +class JabberAccount : public Kopete::PasswordedAccount +{ + Q_OBJECT + +public: + JabberAccount (JabberProtocol * parent, const QString & accountID, const char *name = 0L); + ~JabberAccount (); + + /* Returns the action menu for this account. */ + virtual KActionMenu *actionMenu (); + + /* Return the resource of the client */ + const QString resource () const; + const QString server () const; + const int port () const; + + JabberResourcePool *resourcePool (); + JabberContactPool *contactPool (); + + /* to get the protocol from the account */ + JabberProtocol *protocol () const + { + return m_protocol; + } + + JabberClient *client () const + { + return m_jabberClient; + } + +#ifdef SUPPORT_JINGLE + VoiceCaller *voiceCaller() const + { + return m_voiceCaller; + } + +// JingleSessionManager *sessionManager() const +// { +// return m_jingleSessionManager; +// } +#endif + + // change the default S5B server port + void setS5BServerPort ( int port ); + + /* Tells the user to connect first before they can do whatever it is + * that they want to do. */ + void errorConnectFirst (); + + /* Tells the user that the connection was lost while we waited for + * an answer of him. */ + void errorConnectionLost (); + + /* + * Handle TLS warnings. Displays a dialog and returns the user's choice. + * Parameters: Warning code from QCA::TLS + * Automatically resumes the stream if wanted. + */ + /** + * Handle a TLS warning. Displays a dialog and returns if the + * stream can be continued or not. + * @param client JabberClient instance + * @param warning Warning code from QCA::TLS + * @return True if stream can be resumed. + */ + static bool handleTLSWarning ( JabberClient *client, int warning ); + + /* + * Handle stream errors. Displays a dialog and returns. + */ + static void handleStreamError (int streamError, int streamCondition, int connectorCode, const QString &server, Kopete::Account::DisconnectReason &errorClass); + + const QMap &transports() + { return m_transports; } + + + /** + * called when the account is removed in the config ui + */ + virtual bool removeAccount(); + +public slots: + /* Connects to the server. */ + void connectWithPassword ( const QString &password ); + + /* Disconnects from the server. */ + void disconnect (); + + /* Disconnect with a reason */ + void disconnect ( Kopete::Account::DisconnectReason reason ); + + /* Disconnect with a reason, and status */ + void disconnect( Kopete::Account::DisconnectReason reason, XMPP::Status &status ); + /* Reimplemented from Kopete::Account */ + void setOnlineStatus( const Kopete::OnlineStatus& status , const QString &reason = QString::null); + + void addTransport( JabberTransport *tr , const QString &jid); + void removeTransport( const QString &jid ); + + +protected: + /** + * Create a new contact in the specified metacontact + * + * You shouldn't ever call this method yourself, For adding contacts see @ref addContact() + * + * This method is called by @ref Kopete::Account::addContact() in this method, you should + * simply create the new custom @ref Kopete::Contact in the given metacontact. You should + * NOT add the contact to the server here as this method gets only called when synchronizing + * the contact list on disk with the one in memory. As such, all created contacts from this + * method should have the "dirty" flag set. + * + * This method should simply be used to intantiate the new contact, everything else + * (updating the GUI, parenting to meta contact, etc.) is being taken care of. + * + * @param contactId The unique ID for this protocol + * @param parentContact The metacontact to add this contact to + */ + virtual bool createContact (const QString & contactID, Kopete::MetaContact * parentContact); + + + +private: + JabberProtocol *m_protocol; + + // backend for this account + JabberClient *m_jabberClient; + + JabberResourcePool *m_resourcePool; + JabberContactPool *m_contactPool; + +#ifdef SUPPORT_JINGLE + VoiceCaller *m_voiceCaller; + //JingleSessionManager *m_jingleSessionManager; +#endif + + JabberBookmarks *m_bookmarks; + + /* Set up our actions for the status menu. */ + void initActions (); + + void cleanup (); + + /* Initial presence to set after connecting. */ + XMPP::Status m_initialPresence; + + /** + * Sets our own presence. Updates our resource in the + * resource pool and sends a presence packet to the server. + */ + void setPresence ( const XMPP::Status &status ); + + /** + * Returns if a connection attempt is currently in progress. + */ + bool isConnecting (); + + QMapm_transports; + + /* used in removeAccount() */ + bool m_removing; + /* keep track if we told the user we were not able to bind the + jabber transfer port, to avoid popup insanity */ + bool m_notifiedUserCannotBindTransferPort; +private slots: + /* Connects to the server. */ + void slotConnect (); + + /* Disconnects from the server. */ + void slotDisconnect (); + + // handle a TLS warning + void slotHandleTLSWarning ( int validityResult ); + + // handle client errors + void slotClientError ( JabberClient::ErrorCode errorCode ); + + // we are connected to the server + void slotConnected (); + + /* Called from Psi: tells us when we've been disconnected from the server. */ + void slotCSDisconnected (); + + /* Called from Psi: alerts us to a protocol error. */ + void slotCSError (int); + + /* Called from Psi: roster request finished */ + void slotRosterRequestFinished ( bool success ); + + /* Called from Psi: incoming file transfer */ + void slotIncomingFileTransfer (); + + /* Called from Psi: debug messages from the backend. */ + void slotClientDebugMessage (const QString &msg); + + /* Sends a raw message to the server (use with caution) */ + void slotSendRaw (); + + /* Slots for handling group chats. */ + void slotJoinNewChat (); + void slotGroupChatJoined ( const XMPP::Jid &jid ); + void slotGroupChatLeft ( const XMPP::Jid &jid ); + void slotGroupChatPresence ( const XMPP::Jid &jid, const XMPP::Status &status ); + void slotGroupChatError ( const XMPP::Jid &jid, int error, const QString &reason ); + + /* Incoming subscription request. */ + void slotSubscription ( const XMPP::Jid &jid, const QString &type ); + + /* the dialog that asked to add the contact was closed (that dialog is shown in slotSubscription) */ + void slotContactAddedNotifyDialogClosed(const QString& contactid); + + /** + * A new item appeared in our roster, synch it with the + * contact list. + * (or the contact has been updated + */ + void slotContactUpdated ( const XMPP::RosterItem & ); + + /** + * An item has been deleted from our roster, + * delete it from our contact pool. + */ + void slotContactDeleted ( const XMPP::RosterItem & ); + + + /* Someone on our contact list had (another) resource come online. */ + void slotResourceAvailable ( const XMPP::Jid &, const XMPP::Resource & ); + + /* Someone on our contact list had (another) resource go offline. */ + void slotResourceUnavailable ( const XMPP::Jid &, const XMPP::Resource & ); + + /* Displays a new message. */ + void slotReceivedMessage ( const XMPP::Message & ); + + /* Gets the user's vCard from the server for editing. */ + void slotEditVCard (); + + /* Get the services list from the server for management. */ + void slotGetServices (); + + /* Update the myself information if the global identity changes. */ + void slotGlobalIdentityChanged( const QString &key, const QVariant &value ); + + /* we received a voice invitation */ + void slotIncomingVoiceCall(const Jid&); + + /* the unregister task finished */ + void slotUnregisterFinished(); + + //void slotIncomingJingleSession(const QString &sessionType, JingleSession *session); +}; + +#endif diff --git a/kopete/protocols/jabber/jabberbasecontact.cpp b/kopete/protocols/jabber/jabberbasecontact.cpp new file mode 100644 index 00000000..56cc1de4 --- /dev/null +++ b/kopete/protocols/jabber/jabberbasecontact.cpp @@ -0,0 +1,676 @@ + /* + * jabbercontact.cpp - Base class for the Kopete Jabber protocol contact + * + * Copyright (c) 2002-2004 by Till Gerken + * Copyright (c) 2006 by Olivier Goffart + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * * + * ************************************************************************* + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include + +#include "jabberbasecontact.h" + +#include "xmpp_tasks.h" + +#include "jabberprotocol.h" +#include "jabberaccount.h" +#include "jabberresource.h" +#include "jabberresourcepool.h" +#include "kopetemetacontact.h" +#include "kopetemessage.h" +#include "kopeteuiglobal.h" +#include "jabbertransport.h" +#include "dlgjabbervcard.h" + + +/** + * JabberBaseContact constructor + */ +JabberBaseContact::JabberBaseContact (const XMPP::RosterItem &rosterItem, Kopete::Account *account, Kopete::MetaContact * mc, const QString &legacyId) + : Kopete::Contact (account, legacyId.isEmpty() ? rosterItem.jid().full() : legacyId , mc ) +{ + setDontSync ( false ); + + JabberTransport *t=transport(); + m_account= t ? t->account() : static_cast(Kopete::Contact::account()); + + + // take roster item and update display name + updateContact ( rosterItem ); + +} + + +JabberProtocol *JabberBaseContact::protocol () +{ + + return static_cast(Kopete::Contact::protocol ()); +} + + +JabberTransport * JabberBaseContact::transport( ) +{ + return dynamic_cast(Kopete::Contact::account()); +} + + +/* Return if we are reachable (defaults to true because + we can send on- and offline, only return false if the + account itself is offline, too) */ +bool JabberBaseContact::isReachable () +{ + if (account()->isConnected()) + return true; + + return false; + +} + +void JabberBaseContact::updateContact ( const XMPP::RosterItem & item ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Synchronizing local copy of " << contactId() << " with information received from server. (name='" << item.name() << "' groups='" << item.groups() << "')"<< endl; + + mRosterItem = item; + + // if we don't have a meta contact yet, stop processing here + if ( !metaContact () ) + return; + + /* + * We received the information from the server, as such, + * don't attempt to synch while we update our local copy. + */ + setDontSync ( true ); + + // The myself contact is not in the roster on server, ignore this code + // because the myself MetaContact displayname become the latest + // Jabber acccount jid. + if( metaContact() != Kopete::ContactList::self()->myself() ) + { + // only update the alias if its not empty + if ( !item.name().isEmpty () && item.name() != item.jid().bare() ) + { + QString newName = item.name (); + QString oldName = metaContact()->displayName(); + Kopete::Contact *source=metaContact()->displayNameSourceContact(); +// kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "setting display name of " << contactId () << " to " << newName << endl; + metaContact()->setDisplayName ( newName ); + //automatically set to custom source if the source is to this contact. + if( metaContact()->displayNameSource()==Kopete::MetaContact::SourceContact && newName != oldName && ( source == this || source == 0L ) ) + metaContact()->setDisplayNameSource( Kopete::MetaContact::SourceCustom ); + } + } + + /* + * Set the contact's subscription status + */ + switch ( item.subscription().type () ) + { + case XMPP::Subscription::None: + setProperty ( protocol()->propSubscriptionStatus, + i18n ( "You cannot see each others' status." ) ); + break; + case XMPP::Subscription::To: + setProperty ( protocol()->propSubscriptionStatus, + i18n ( "You can see this contact's status but they cannot see your status." ) ); + break; + case XMPP::Subscription::From: + setProperty ( protocol()->propSubscriptionStatus, + i18n ( "This contact can see your status but you cannot see their status." ) ); + break; + case XMPP::Subscription::Both: + setProperty ( protocol()->propSubscriptionStatus, + i18n ( "You can see each others' status." ) ); + break; + } + + if( !metaContact()->isTemporary() ) + { + /* + * In this method, as opposed to KC::syncGroups(), + * the group list from the server is authoritative. + * As such, we need to find a list of all groups + * that the meta contact resides in but does not + * reside in on the server anymore, as well as all + * groups that the meta contact does not reside in, + * but resides in on the server. + * Then, we'll have to synchronize the KMC using + * that information. + */ + Kopete::GroupList groupsToRemoveFrom, groupsToAddTo; + + // find all groups our contact is in but that are not in the server side roster + for ( unsigned i = 0; i < metaContact()->groups().count (); i++ ) + { + if ( item.groups().find ( metaContact()->groups().at(i)->displayName () ) == item.groups().end () ) + groupsToRemoveFrom.append ( metaContact()->groups().at ( i ) ); + } + + // now find all groups that are in the server side roster but not in the local group + for ( unsigned i = 0; i < item.groups().count (); i++ ) + { + bool found = false; + for ( unsigned j = 0; j < metaContact()->groups().count (); j++) + { + if ( metaContact()->groups().at(j)->displayName () == *item.groups().at(i) ) + { + found = true; + break; + } + } + + if ( !found ) + { + groupsToAddTo.append ( Kopete::ContactList::self()->findGroup ( *item.groups().at(i) ) ); + } + } + + /* + * Special case: if we don't add the contact to any group and the + * list of groups to remove from contains the top level group, we + * risk removing the contact from the visible contact list. In this + * case, we need to make sure at least the top level group stays. + */ + if ( ( groupsToAddTo.count () == 0 ) && ( groupsToRemoveFrom.contains ( Kopete::Group::topLevel () ) ) ) + { + groupsToRemoveFrom.remove ( Kopete::Group::topLevel () ); + } + + for ( Kopete::Group *group = groupsToRemoveFrom.first (); group; group = groupsToRemoveFrom.next () ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Removing " << contactId() << " from group " << group->displayName () << endl; + metaContact()->removeFromGroup ( group ); + } + + for ( Kopete::Group *group = groupsToAddTo.first (); group; group = groupsToAddTo.next () ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Adding " << contactId() << " to group " << group->displayName () << endl; + metaContact()->addToGroup ( group ); + } + } + + /* + * Enable updates for the server again. + */ + setDontSync ( false ); + + //can't do it now because it's called from contructor at a point some virtual function are not available + QTimer::singleShot(0, this, SLOT(reevaluateStatus())); + +} + +void JabberBaseContact::updateResourceList () +{ + /* + * Set available resources. + * This is a bit more complicated: We need to generate + * all images dynamically from the KOS icons and store + * them into the mime factory, then plug them into + * the richtext. + */ + JabberResourcePool::ResourceList resourceList; + account()->resourcePool()->findResources ( rosterItem().jid() , resourceList ); + + if ( resourceList.isEmpty () ) + { + removeProperty ( protocol()->propAvailableResources ); + return; + } + + QString resourceListStr = ""; + + for ( JabberResourcePool::ResourceList::iterator it = resourceList.begin (); it != resourceList.end (); ++it ) + { + // icon, resource name and priority + resourceListStr += QString ( "" ). + arg ( protocol()->resourceToKOS((*it)->resource()).mimeSourceFor ( account () ), + (*it)->resource().name (), QString::number ( (*it)->resource().priority () ) ); + + // client name, version, OS + if ( !(*it)->clientName().isEmpty () ) + { + resourceListStr += QString ( "" ). + arg ( i18n ( "Client" ), (*it)->clientName (), (*it)->clientSystem () ); + } + + // Supported features +#if 0 //disabled because it's just an ugly and long list of incomprehensible namespaces to the user + QStringList supportedFeatures = (*it)->features().list(); + QStringList::ConstIterator featuresIt, featuresItEnd = supportedFeatures.constEnd(); + if( !supportedFeatures.empty() ) + resourceListStr += QString( "" ); +#endif + + // resource timestamp + resourceListStr += QString ( "" ). + arg ( i18n ( "Timestamp" ), KGlobal::locale()->formatDateTime ( (*it)->resource().status().timeStamp(), true, true ) ); + + // message, if any + if ( !(*it)->resource().status().status().stripWhiteSpace().isEmpty () ) + { + resourceListStr += QString ( "" ). + arg ( + i18n ( "Message" ), + Kopete::Message::escape( (*it)->resource().status().status () ) + ); + } + } + + resourceListStr += "
%2 (Priority: %3)
%1: %2 (%3)
Supported Features:" ); + for( featuresIt = supportedFeatures.constBegin(); featuresIt != featuresItEnd; ++featuresIt ) + { + XMPP::Features tempFeature(*featuresIt); + resourceListStr += QString("\n
"); + if ( tempFeature.id() > XMPP::Features::FID_None ) + resourceListStr += tempFeature.name() + QString(" ("); + resourceListStr += *featuresIt; + if ( tempFeature.id() > Features::FID_None ) + resourceListStr += QString(")"); + } + if( !supportedFeatures.empty() ) + resourceListStr += QString( "
%1: %2
%1: %2
"; + + setProperty ( protocol()->propAvailableResources, resourceListStr ); +} + +void JabberBaseContact::reevaluateStatus () +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Determining new status for " << contactId () << endl; + + Kopete::OnlineStatus status; + XMPP::Resource resource = account()->resourcePool()->bestResource ( mRosterItem.jid () ); + + status = protocol()->resourceToKOS ( resource ); + + + /* Add some icon to show the subscription */ + if( ( mRosterItem.subscription().type() == XMPP::Subscription::None || mRosterItem.subscription().type() == XMPP::Subscription::From) + && inherits ( "JabberContact" ) && metaContact() != Kopete::ContactList::self()->myself() && account()->isConnected() ) + { + status = Kopete::OnlineStatus(status.status() , + status.weight() , + protocol() , + status.internalStatus() | 0x0100, + status.overlayIcons() + QStringList("status_unknown_overlay") , //FIXME: find better icon + status.description() ); + } + + + updateResourceList (); + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "New status for " << contactId () << " is " << status.description () << endl; + setOnlineStatus ( status ); + + /* + * Set away message property. + * We just need to read it from the current resource. + */ + if ( !resource.status ().status ().isEmpty () ) + { + setProperty ( protocol()->propAwayMessage, resource.status().status () ); + } + else + { + removeProperty ( protocol()->propAwayMessage ); + } + +} + +QString JabberBaseContact::fullAddress () +{ + + XMPP::Jid jid = rosterItem().jid(); + + if ( jid.resource().isEmpty () ) + { + jid.setResource ( account()->resourcePool()->bestResource ( jid ).name () ); + } + + return jid.full (); + +} + +XMPP::Jid JabberBaseContact::bestAddress () +{ + + // see if we are subscribed with a preselected resource + if ( !mRosterItem.jid().resource().isEmpty () ) + { + // we have a preselected resource, so return our default full address + return mRosterItem.jid (); + } + + // construct address out of user@host and current best resource + XMPP::Jid jid = mRosterItem.jid (); + jid.setResource ( account()->resourcePool()->bestResource( mRosterItem.jid() ).name () ); + + return jid; + +} + +void JabberBaseContact::setDontSync ( bool flag ) +{ + + mDontSync = flag; + +} + +bool JabberBaseContact::dontSync () +{ + + return mDontSync; + +} + +void JabberBaseContact::serialize (QMap < QString, QString > &serializedData, QMap < QString, QString > & /* addressBookData */ ) +{ + + // Contact id and display name are already set for us, only add the rest + serializedData["JID"] = mRosterItem.jid().full(); + + serializedData["groups"] = mRosterItem.groups ().join (QString::fromLatin1 (",")); +} + +void JabberBaseContact::slotUserInfo( ) +{ + if ( !account()->isConnected () ) + { + account()->errorConnectFirst (); + return; + } + + // Update the vCard + //slotGetTimedVCard(); + + new dlgJabberVCard ( account(), this, Kopete::UI::Global::mainWidget () ); +} + +void JabberBaseContact::setPropertiesFromVCard ( const XMPP::VCard &vCard ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Updating vCard for " << contactId () << endl; + + // update vCard cache timestamp if this is not a temporary contact + if ( metaContact() && !metaContact()->isTemporary () ) + { + setProperty ( protocol()->propVCardCacheTimeStamp, QDateTime::currentDateTime().toString ( Qt::ISODate ) ); + } + + + /* + * Set the nickname property. + * but ignore it if we are in a groupchat, or it will clash with the normal nickname + */ + if(inherits ( "JabberContact" )) + { + if ( !vCard.nickName().isEmpty () ) + { + setProperty ( protocol()->propNickName, vCard.nickName () ); + } + else + { + removeProperty ( protocol()->propNickName ); + } + } + + /** + * Kopete does not allow a modification of the "full name" + * property. However, some vCards specify only the full name, + * some specify only first and last name. + * Due to these inconsistencies, if first and last name don't + * exist, it is attempted to parse the full name. + */ + + // remove all properties first + removeProperty ( protocol()->propFirstName ); + removeProperty ( protocol()->propLastName ); + removeProperty ( protocol()->propFullName ); + + if ( !vCard.fullName().isEmpty () && vCard.givenName().isEmpty () && vCard.familyName().isEmpty () ) + { + QString lastName = vCard.fullName().section ( ' ', 0, -1 ); + QString firstName = vCard.fullName().left(vCard.fullName().length () - lastName.length ()).stripWhiteSpace (); + + setProperty ( protocol()->propFirstName, firstName ); + setProperty ( protocol()->propLastName, lastName ); + } + else + { + if ( !vCard.givenName().isEmpty () ) + setProperty ( protocol()->propFirstName, vCard.givenName () ); + + if ( !vCard.familyName().isEmpty () ) + setProperty ( protocol()->propLastName, vCard.familyName () ); + } + if( !vCard.fullName().isEmpty() ) + setProperty ( protocol()->propFullName, vCard.fullName() ); + + /* + * Set the general information + */ + removeProperty( protocol()->propJid ); + removeProperty( protocol()->propBirthday ); + removeProperty( protocol()->propTimezone ); + removeProperty( protocol()->propHomepage ); + + setProperty( protocol()->propJid, vCard.jid() ); + + if( !vCard.bdayStr().isEmpty () ) + setProperty( protocol()->propBirthday, vCard.bdayStr() ); + if( !vCard.timezone().isEmpty () ) + setProperty( protocol()->propTimezone, vCard.timezone() ); + if( !vCard.url().isEmpty () ) + setProperty( protocol()->propHomepage, vCard.url() ); + + /* + * Set the work information. + */ + removeProperty( protocol()->propCompanyName ); + removeProperty( protocol()->propCompanyDepartement ); + removeProperty( protocol()->propCompanyPosition ); + removeProperty( protocol()->propCompanyRole ); + + if( !vCard.org().name.isEmpty() ) + setProperty( protocol()->propCompanyName, vCard.org().name ); + if( !vCard.org().unit.join(",").isEmpty() ) + setProperty( protocol()->propCompanyDepartement, vCard.org().unit.join(",")) ; + if( !vCard.title().isEmpty() ) + setProperty( protocol()->propCompanyPosition, vCard.title() ); + if( !vCard.role().isEmpty() ) + setProperty( protocol()->propCompanyRole, vCard.role() ); + + /* + * Set the about information + */ + removeProperty( protocol()->propAbout ); + + if( !vCard.desc().isEmpty() ) + setProperty( protocol()->propAbout, vCard.desc() ); + + + /* + * Set the work and home addresses information + */ + removeProperty( protocol()->propWorkStreet ); + removeProperty( protocol()->propWorkExtAddr ); + removeProperty( protocol()->propWorkPOBox ); + removeProperty( protocol()->propWorkCity ); + removeProperty( protocol()->propWorkPostalCode ); + removeProperty( protocol()->propWorkCountry ); + + removeProperty( protocol()->propHomeStreet ); + removeProperty( protocol()->propHomeExtAddr ); + removeProperty( protocol()->propHomePOBox ); + removeProperty( protocol()->propHomeCity ); + removeProperty( protocol()->propHomePostalCode ); + removeProperty( protocol()->propHomeCountry ); + + for(XMPP::VCard::AddressList::const_iterator it = vCard.addressList().begin(); it != vCard.addressList().end(); it++) + { + XMPP::VCard::Address address = (*it); + + if(address.work) + { + setProperty( protocol()->propWorkStreet, address.street ); + setProperty( protocol()->propWorkExtAddr, address.extaddr ); + setProperty( protocol()->propWorkPOBox, address.pobox ); + setProperty( protocol()->propWorkCity, address.locality ); + setProperty( protocol()->propWorkPostalCode, address.pcode ); + setProperty( protocol()->propWorkCountry, address.country ); + } + else + if(address.home) + { + setProperty( protocol()->propHomeStreet, address.street ); + setProperty( protocol()->propHomeExtAddr, address.extaddr ); + setProperty( protocol()->propHomePOBox, address.pobox ); + setProperty( protocol()->propHomeCity, address.locality ); + setProperty( protocol()->propHomePostalCode, address.pcode ); + setProperty( protocol()->propHomeCountry, address.country ); + } + } + + + /* + * Delete emails first, they might not be present + * in the vCard at all anymore. + */ + removeProperty ( protocol()->propEmailAddress ); + removeProperty ( protocol()->propWorkEmailAddress ); + + /* + * Set the home and work email information. + */ + XMPP::VCard::EmailList::const_iterator emailEnd = vCard.emailList().end (); + for(XMPP::VCard::EmailList::const_iterator it = vCard.emailList().begin(); it != emailEnd; ++it) + { + XMPP::VCard::Email email = (*it); + + if(email.work) + { + if( !email.userid.isEmpty() ) + setProperty ( protocol()->propWorkEmailAddress, email.userid ); + } + else + if(email.home) + { + if( !email.userid.isEmpty() ) + setProperty ( protocol()->propEmailAddress, email.userid ); + } + } + + /* + * Delete phone number properties first as they might have + * been unset during an update and are not present in + * the vCard at all anymore. + */ + removeProperty ( protocol()->propPrivatePhone ); + removeProperty ( protocol()->propPrivateMobilePhone ); + removeProperty ( protocol()->propWorkPhone ); + removeProperty ( protocol()->propWorkMobilePhone ); + + /* + * Set phone numbers. Note that if a mobile phone number + * is specified, it's assigned to the private mobile + * phone number property. This might not be the desired + * behavior for all users. + */ + XMPP::VCard::PhoneList::const_iterator phoneEnd = vCard.phoneList().end (); + for(XMPP::VCard::PhoneList::const_iterator it = vCard.phoneList().begin(); it != phoneEnd; ++it) + { + XMPP::VCard::Phone phone = (*it); + + if(phone.work) + { + setProperty ( protocol()->propWorkPhone, phone.number ); + } + else + if(phone.fax) + { + setProperty ( protocol()->propPhoneFax, phone.number); + } + else + if(phone.cell) + { + setProperty ( protocol()->propPrivateMobilePhone, phone.number ); + } + else + if(phone.home) + { + setProperty ( protocol()->propPrivatePhone, phone.number ); + } + + } + + /* + * Set photo/avatar property. + */ + removeProperty( protocol()->propPhoto ); + + QImage contactPhoto; + QString fullJid = mRosterItem.jid().full(); + QString finalPhotoPath = locateLocal("appdata", "jabberphotos/" + fullJid.replace(QRegExp("[./~]"),"-") +".png"); + + // photo() is a QByteArray + if ( !vCard.photo().isEmpty() ) + { + kdDebug( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Contact has a photo embedded into his vCard." << endl; + + // QImage is used to save to disk in PNG later. + contactPhoto = QImage( vCard.photo() ); + } + // Contact photo is a URI. + else if( !vCard.photoURI().isEmpty() ) + { + QString tempPhotoPath = 0; + + // Downalod photo from URI. + if( !KIO::NetAccess::download( vCard.photoURI(), tempPhotoPath, 0) ) + { + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget (), KMessageBox::Sorry, i18n( "Downloading of Jabber contact photo failed!" ) ); + return; + } + + kdDebug( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Contact photo is a URI." << endl; + + contactPhoto = QImage( tempPhotoPath ); + + KIO::NetAccess::removeTempFile( tempPhotoPath ); + } + + // Save the image to the disk, then set the property. + if( !contactPhoto.isNull() && contactPhoto.save(finalPhotoPath, "PNG") ) + { + kdDebug( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Setting photo for contact: " << fullJid << endl; + setProperty( protocol()->propPhoto, finalPhotoPath ); + } + +} + + + + +#include "jabberbasecontact.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/protocols/jabber/jabberbasecontact.h b/kopete/protocols/jabber/jabberbasecontact.h new file mode 100644 index 00000000..7ba5c3fb --- /dev/null +++ b/kopete/protocols/jabber/jabberbasecontact.h @@ -0,0 +1,185 @@ + /* + * jabbercontact.h - Base class for the Kopete Jabber protocol contact + * + * Copyright (c) 2002-2004 by Till Gerken + * Copyright (c) 2002 by Daniel Stone + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * * + * ************************************************************************* + */ + +#ifndef JABBERBASECONTACT_H +#define JABBERBASECONTACT_H + +#include "kopetecontact.h" +#include "xmpp.h" +#include "im.h" + +class dlgJabberVCard; +class JabberProtocol; +class JabberAccount; +class JabberResource; +class JabberTransport; +namespace Kopete { class MetaContact; } +namespace XMPP { class VCard; } + +class JabberBaseContact : public Kopete::Contact +{ + +Q_OBJECT +friend class JabberAccount; /* Friends can touch each other's private parts. */ + +public: + + /** + * @param legacyId is the contactId of the contact if != Jid + */ + JabberBaseContact (const XMPP::RosterItem &rosterItem, + Kopete::Account *account, Kopete::MetaContact * mc, + const QString &legacyId=QString()); + + /******************************************************************** + * + * Kopete::Contact reimplementation start + * + ********************************************************************/ + + /** + * Return the protocol instance associated with this contact + */ + JabberProtocol *protocol (); + + /** + * Return the account instance associated with this contact + */ + JabberAccount *account () const { return m_account; }; + + /** + * return the transport if any, or null + */ + JabberTransport *transport(); + + /** + * Return if the contact is reachable (this is true if the account + * is online) + */ + virtual bool isReachable (); + + /** + * Create custom context menu items for the contact + * FIXME: implement manager version here? + */ + virtual QPtrList *customContextMenuActions () = 0; + + /** + * Serialize contact + */ + virtual void serialize (QMap < QString, QString > &serializedData, QMap < QString, QString > &addressBookData); + + /** + * Update contact if a roster item has been + * received for it. (used during login) + */ + void updateContact ( const XMPP::RosterItem &item ); + + /** + * Deal with an incoming message for this contact. + */ + virtual void handleIncomingMessage ( const XMPP::Message &message ) = 0; + + /** + * Update the resource property of the + * contact, listing all available resources. + */ + void updateResourceList (); + + /** + * Return current full address. + * Uses bestResource() if no presubscribed + * address exists. + */ + QString fullAddress (); + + /** + * Set the dontSync flag for this contact. + * If this flag is set, calls to @ref sync will + * be ignored. This is required if the contact + * has been moved between groups on the server + * after we logged in and we try to update our + * local contact list. Since libkopete can only + * handle one group update at a time, moving + * between groups requires to operations which + * each in turn would cause a call to sync(), + * overwriting the change that is being carried + * out. (besides causing unnecessary traffic) + * This is avoided by setting the dontSync flag + * while synchronizing the local copy. + */ + void setDontSync ( bool flag ); + + /** + * Return the status of the dontSync flag. + * See @ref setDontSync for a full description. + */ + bool dontSync (); + + /** + * return the roster item of the contact. + * to get the jid, use rosterItem().jid().full() don't use contactId as it is not the same with transport + */ + XMPP::RosterItem rosterItem() const { return mRosterItem; } + + /** + * Reads a vCard object and updates the contact's + * properties accordingly. + */ + void setPropertiesFromVCard ( const XMPP::VCard &vCard ); + + +public slots: + + /** + * Retrieve a vCard for the contact + */ + virtual void slotUserInfo (); + + + /** + * Re-evaluate online status. Gets called + * whenever a resource is added, removed, or + * changed in the resource pool. + */ + void reevaluateStatus (); + +protected: + /** + * Construct best address out of + * eventually preselected resource + * (due to subscription) and best + * available resource. + */ + XMPP::Jid bestAddress (); + + /** + * This will simply cache all + * relevant data for this contact. + */ + XMPP::RosterItem mRosterItem; + +private: + bool mDontSync; + JabberAccount *m_account; + +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/protocols/jabber/jabberbookmarks.cpp b/kopete/protocols/jabber/jabberbookmarks.cpp new file mode 100644 index 00000000..720705b2 --- /dev/null +++ b/kopete/protocols/jabber/jabberbookmarks.cpp @@ -0,0 +1,149 @@ + /* + Copyright (c) 2006 by Olivier Goffart + + Kopete (c) 2006 by the Kopete developers + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* + */ + +#include "jabberbookmarks.h" +#include "jabberaccount.h" + +#include + + +#include +#include +#include + +#include "xmpp_tasks.h" + + +JabberBookmarks::JabberBookmarks(JabberAccount *parent) : QObject(parent) , m_account(parent) +{ + connect( m_account , SIGNAL( isConnectedChanged() ) , this , SLOT( accountConnected() ) ); +} + +void JabberBookmarks::accountConnected() +{ + if(!m_account->isConnected()) + return; + + XMPP::JT_PrivateStorage * task = new XMPP::JT_PrivateStorage ( m_account->client()->rootTask ()); + task->get( "storage" , "storage:bookmarks" ); + QObject::connect ( task, SIGNAL ( finished () ), this, SLOT ( slotReceivedBookmarks() ) ); + task->go ( true ); +} + +void JabberBookmarks::slotReceivedBookmarks( ) +{ + XMPP::JT_PrivateStorage * task = (XMPP::JT_PrivateStorage*)(sender()); + m_storage=QDomDocument("storage"); + m_conferencesJID.clear(); + if(task->success()) + { + QDomElement storage_e=task->element(); + if(!storage_e.isNull() && storage_e.tagName() == "storage") + { + storage_e=m_storage.importNode(storage_e,true).toElement(); + m_storage.appendChild(storage_e); + + for(QDomNode n = storage_e.firstChild(); !n.isNull(); n = n.nextSibling()) + { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + if(i.tagName() == "conference") + { + QString jid=i.attribute("jid"); + QString password; + for(QDomNode n = i.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement e = n.toElement(); + if(e.isNull()) + continue; + else if(e.tagName() == "nick") + jid+="/"+e.text(); + else if(e.tagName() == "password") + password=e.text(); + + } + m_conferencesJID += jid; + if(i.attribute("autojoin") == "true") + { + XMPP::Jid x_jid(jid); + QString nick=x_jid.resource(); + if(nick.isEmpty()) + nick=m_account->myself()->nickName(); + + if(password.isEmpty()) + m_account->client()->joinGroupChat(x_jid.host() , x_jid.user() , nick ); + else + m_account->client()->joinGroupChat(x_jid.host() , x_jid.user() , nick , password); + } + } + } + } + } +} + + +void JabberBookmarks::insertGroupChat(const XMPP::Jid &jid) +{ + if(m_conferencesJID.contains(jid.full()) || !m_account->isConnected()) + { + return; + } + + QDomElement storage_e=m_storage.documentElement(); + if(storage_e.isNull()) + { + storage_e=m_storage.createElement("storage"); + m_storage.appendChild(storage_e); + storage_e.setAttribute("xmlns","storage:bookmarks"); + } + + QDomElement conference=m_storage.createElement("conference"); + storage_e.appendChild(conference); + conference.setAttribute("jid",jid.userHost()); + QDomElement nick=m_storage.createElement("nick"); + conference.appendChild(nick); + nick.appendChild(m_storage.createTextNode(jid.resource())); + QDomElement name=m_storage.createElement("name"); + conference.appendChild(name); + name.appendChild(m_storage.createTextNode(jid.full())); + + + XMPP::JT_PrivateStorage * task = new XMPP::JT_PrivateStorage ( m_account->client()->rootTask ()); + task->set( storage_e ); + task->go ( true ); + + m_conferencesJID += jid.full(); +} + +KAction * JabberBookmarks::bookmarksAction(QObject *parent) +{ + KSelectAction *groupchatBM = new KSelectAction( i18n("Groupchat bookmark") , "jabber_group" , 0 , parent , "actionBookMark" ); + groupchatBM->setItems(m_conferencesJID); + QObject::connect(groupchatBM, SIGNAL(activated (const QString&)) , this, SLOT(slotJoinChatBookmark(const QString&))); + return groupchatBM; +} + +void JabberBookmarks::slotJoinChatBookmark( const QString & _jid ) +{ + if(!m_account->isConnected()) + return; + XMPP::Jid jid(_jid); + m_account->client()->joinGroupChat( jid.host() , jid.user() , jid.resource() ); +} + + + +#include "jabberbookmarks.moc" + diff --git a/kopete/protocols/jabber/jabberbookmarks.h b/kopete/protocols/jabber/jabberbookmarks.h new file mode 100644 index 00000000..826d15e2 --- /dev/null +++ b/kopete/protocols/jabber/jabberbookmarks.h @@ -0,0 +1,68 @@ + /* + + Copyright (c) 2006 by Olivier Goffart + + Kopete (c) 2006 by the Kopete developers + + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* + */ + +#ifndef JABBERBOOKMARKS_H +#define JABBERBOOKMARKS_H + +#include +#include +#include + +namespace XMPP { class Jid; } +class JabberAccount; +class JabberProtocol; + +class KAction; + +/** + * This is a class that hanlde the bookmark collection (JEP-0048) + * There is one instance of that class by accounts. + * @author Olivier Goffart + */ +class JabberBookmarks : public QObject +{ + Q_OBJECT + public: + /** + * Constructor + */ + JabberBookmarks(JabberAccount *parent); + ~JabberBookmarks(){} + + /** + * update or create en entry with the given jid. + * the jid ressource is the nickname + */ + void insertGroupChat(const XMPP::Jid &jid); + + /** + * return an action that will be added in the jabber popup menu + */ + KAction *bookmarksAction(QObject * parent); + private slots: + void accountConnected(); + void slotReceivedBookmarks(); + void slotJoinChatBookmark(const QString&); + + + private: + JabberAccount *m_account; + QDomDocument m_storage; + QStringList m_conferencesJID; +}; + +#endif diff --git a/kopete/protocols/jabber/jabberbytestream.cpp b/kopete/protocols/jabber/jabberbytestream.cpp new file mode 100644 index 00000000..2f0f5c80 --- /dev/null +++ b/kopete/protocols/jabber/jabberbytestream.cpp @@ -0,0 +1,156 @@ + +/*************************************************************************** + jabberbytestream.cpp - Byte Stream for Jabber + ------------------- + begin : Wed Jul 7 2004 + copyright : (C) 2004 by Till Gerken + + Kopete (C) 2004 Kopete developers + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation; either version 2.1 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +#include +#include +#include "jabberbytestream.h" +#include +#include +#include "jabberprotocol.h" + +JabberByteStream::JabberByteStream ( QObject *parent, const char */*name*/ ) + : ByteStream ( parent ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Instantiating new Jabber byte stream." << endl; + + // reset close tracking flag + mClosing = false; + + mSocket = new KNetwork::KBufferedSocket; + + // make sure we get a signal whenever there's data to be read + mSocket->enableRead ( true ); + + // connect signals and slots + QObject::connect ( mSocket, SIGNAL ( gotError ( int ) ), this, SLOT ( slotError ( int ) ) ); + QObject::connect ( mSocket, SIGNAL ( connected ( const KResolverEntry& ) ), this, SLOT ( slotConnected () ) ); + QObject::connect ( mSocket, SIGNAL ( closed () ), this, SLOT ( slotConnectionClosed () ) ); + QObject::connect ( mSocket, SIGNAL ( readyRead () ), this, SLOT ( slotReadyRead () ) ); + QObject::connect ( mSocket, SIGNAL ( bytesWritten ( int ) ), this, SLOT ( slotBytesWritten ( int ) ) ); + +} + +bool JabberByteStream::connect ( QString host, QString service ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Connecting to " << host << ", service " << service << endl; + + mClosing = false; + + return socket()->connect ( host, service ); + +} + +bool JabberByteStream::isOpen () const +{ + + // determine if socket is open + return socket()->isOpen (); + +} + +void JabberByteStream::close () +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Closing stream." << endl; + + // close the socket and set flag that we are closing it ourselves + mClosing = true; + socket()->close(); + +} + +int JabberByteStream::tryWrite () +{ + + // send all data from the buffers to the socket + QByteArray writeData = takeWrite(); + socket()->writeBlock ( writeData.data (), writeData.size () ); + + return writeData.size (); + +} + +KNetwork::KBufferedSocket *JabberByteStream::socket () const +{ + + return mSocket; + +} + +JabberByteStream::~JabberByteStream () +{ + + delete mSocket; + +} + +void JabberByteStream::slotConnected () +{ + + emit connected (); + +} + +void JabberByteStream::slotConnectionClosed () +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Socket has been closed." << endl; + + // depending on who closed the socket, emit different signals + if ( !mClosing ) + { + emit connectionClosed (); + } + else + { + emit delayedCloseFinished (); + } + + mClosing = false; + +} + +void JabberByteStream::slotReadyRead () +{ + + // stuff all available data into our buffers + QByteArray readBuffer ( socket()->bytesAvailable () ); + + socket()->readBlock ( readBuffer.data (), readBuffer.size () ); + + appendRead ( readBuffer ); + + emit readyRead (); + +} + +void JabberByteStream::slotBytesWritten ( int bytes ) +{ + + emit bytesWritten ( bytes ); + +} + +void JabberByteStream::slotError ( int code ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Socket error " << code << endl; + + emit error ( code ); + +} + +#include "jabberbytestream.moc" diff --git a/kopete/protocols/jabber/jabberbytestream.h b/kopete/protocols/jabber/jabberbytestream.h new file mode 100644 index 00000000..97e1ceeb --- /dev/null +++ b/kopete/protocols/jabber/jabberbytestream.h @@ -0,0 +1,65 @@ + +/*************************************************************************** + jabberbytestream.h - Byte Stream for Jabber + ------------------- + begin : Wed Jul 7 2004 + copyright : (C) 2004 by Till Gerken + + Kopete (C) 2004 Kopete developers + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation; either version 2.1 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef JABBERBYTESTREAM_H +#define JABBERBYTESTREAM_H + +#include +#include + + +/** +@author Kopete Developers +*/ +class JabberByteStream : public ByteStream +{ + +Q_OBJECT + +public: + JabberByteStream ( QObject *parent = 0, const char *name = 0 ); + + ~JabberByteStream (); + + bool connect ( QString host, QString service ); + virtual bool isOpen () const; + virtual void close (); + + KNetwork::KBufferedSocket *socket () const; + +signals: + void connected (); + +protected: + virtual int tryWrite (); + +private slots: + void slotConnected (); + void slotConnectionClosed (); + void slotReadyRead (); + void slotBytesWritten ( int ); + void slotError ( int ); + +private: + KNetwork::KBufferedSocket *mSocket; + bool mClosing; + +}; + +#endif diff --git a/kopete/protocols/jabber/jabbercapabilitiesmanager.cpp b/kopete/protocols/jabber/jabbercapabilitiesmanager.cpp new file mode 100644 index 00000000..e570d241 --- /dev/null +++ b/kopete/protocols/jabber/jabbercapabilitiesmanager.cpp @@ -0,0 +1,656 @@ + /* + jabbercapabilitiesmanager.cpp - Manage entity capabilities(JEP-0115). + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + Imported from caps.cpp from Psi: + Copyright (C) 2005 Remko Troncon + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ +#include "jabbercapabilitiesmanager.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "jabberaccount.h" +#include "jabberprotocol.h" + +using namespace XMPP; + +//BEGIN Capabilities +JabberCapabilitiesManager::Capabilities::Capabilities() +{} + +JabberCapabilitiesManager::Capabilities::Capabilities(const QString& node, const QString& version, const QString& extensions) + : m_node(node), m_version(version), m_extensions(extensions) +{} + +const QString& JabberCapabilitiesManager::Capabilities::node() const +{ + return m_node; +} + +const QString& JabberCapabilitiesManager::Capabilities::version() const +{ + return m_version; +} + +const QString& JabberCapabilitiesManager::Capabilities::extensions() const +{ + return m_extensions; +} + +JabberCapabilitiesManager::CapabilitiesList JabberCapabilitiesManager::Capabilities::flatten() const +{ + CapabilitiesList capsList; + capsList.append( Capabilities(node(), version(), version()) ); + + QStringList extensionList = QStringList::split(" ",extensions()); + QStringList::ConstIterator it, itEnd = extensionList.constEnd(); + for(it = extensionList.constBegin(); it != itEnd; ++it) + { + capsList.append( Capabilities(node(),version(),*it) ); + } + + return capsList; +} + +bool JabberCapabilitiesManager::Capabilities::operator==(const Capabilities &other) const +{ + return (node() == other.node() && version() == other.version() && extensions() == other.extensions()); +} + +bool JabberCapabilitiesManager::Capabilities::operator!=(const Capabilities &other) const +{ + return !((*this) == other); +} + +bool JabberCapabilitiesManager::Capabilities::operator<(const Capabilities &other) const +{ + return (node() != other.node() ? node() < other.node() : + (version() != other.version() ? version() < other.version() : + extensions() < other.extensions())); +} +//END Capabilities + +//BEGIN CapabilitiesInformation +JabberCapabilitiesManager::CapabilitiesInformation::CapabilitiesInformation() + : m_discovered(false), m_pendingRequests(0) +{ + updateLastSeen(); +} + +const QStringList& JabberCapabilitiesManager::CapabilitiesInformation::features() const +{ + return m_features; +} + +const DiscoItem::Identities& JabberCapabilitiesManager::CapabilitiesInformation::identities() const +{ + return m_identities; +} + +QStringList JabberCapabilitiesManager::CapabilitiesInformation::jids() const +{ + QStringList jids; + + QValueList >::ConstIterator it = m_jids.constBegin(), itEnd = m_jids.constEnd(); + for( ; it != itEnd; ++it) + { + QString jid( (*it).first ); + if( !jids.contains(jid) ) + jids.push_back(jid); + } + + return jids; +} + +bool JabberCapabilitiesManager::CapabilitiesInformation::discovered() const +{ + return m_discovered; +} + +int JabberCapabilitiesManager::CapabilitiesInformation::pendingRequests() const +{ + return m_pendingRequests; +} + +void JabberCapabilitiesManager::CapabilitiesInformation::reset() +{ + m_features.clear(); + m_identities.clear(); + m_discovered = false; +} + +void JabberCapabilitiesManager::CapabilitiesInformation::removeAccount(JabberAccount *account) +{ + QValueList >::Iterator it = m_jids.begin(); + while( it != m_jids.end() ) + { + if( (*it).second == account) + { + QValueList >::Iterator otherIt = it; + it++; + m_jids.remove(otherIt); + } + else + { + it++; + } + } +} + +void JabberCapabilitiesManager::CapabilitiesInformation::addJid(const Jid& jid, JabberAccount* account) +{ + QPair jidAccountPair(jid.full(),account); + + if( !m_jids.contains(jidAccountPair) ) + { + m_jids.push_back(jidAccountPair); + updateLastSeen(); + } +} + +void JabberCapabilitiesManager::CapabilitiesInformation::removeJid(const Jid& jid) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Unregistering " << QString(jid.full()).replace('%',"%%") << endl; + + QValueList >::Iterator it = m_jids.begin(); + while( it != m_jids.end() ) + { + if( (*it).first == jid.full() ) + { + QValueList >::Iterator otherIt = it; + it++; + m_jids.remove(otherIt); + } + else + { + it++; + } + } +} + +QPair JabberCapabilitiesManager::CapabilitiesInformation::nextJid(const Jid& jid, const Task* t) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Looking for next JID" << endl; + + QValueList >::ConstIterator it = m_jids.constBegin(), itEnd = m_jids.constEnd(); + for( ; it != itEnd; ++it) + { + if( (*it).first == jid.full() && (*it).second->client()->rootTask() == t) + { + it++; + if (it == itEnd) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "No more JIDs" << endl; + + return QPair(Jid(),0L); + } + else if( (*it).second->isConnected() ) + { + //qDebug("caps.cpp: Account isn't active"); + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Account isn't connected." << endl; + + return QPair( (*it).first,(*it).second ); + } + } + } + return QPair(Jid(),0L); +} + +void JabberCapabilitiesManager::CapabilitiesInformation::setDiscovered(bool value) +{ + m_discovered = value; +} + +void JabberCapabilitiesManager::CapabilitiesInformation::setPendingRequests(int pendingRequests) +{ + m_pendingRequests = pendingRequests; +} + +void JabberCapabilitiesManager::CapabilitiesInformation::setIdentities(const DiscoItem::Identities& identities) +{ + m_identities = identities; +} + +void JabberCapabilitiesManager::CapabilitiesInformation::setFeatures(const QStringList& featureList) +{ + m_features = featureList; +} + +void JabberCapabilitiesManager::CapabilitiesInformation::updateLastSeen() +{ + m_lastSeen = QDate::currentDate(); +} + +QDomElement JabberCapabilitiesManager::CapabilitiesInformation::toXml(QDomDocument *doc) const +{ + QDomElement info = doc->createElement("info"); + //info.setAttribute("last-seen",lastSeen_.toString(Qt::ISODate)); + + // Identities + DiscoItem::Identities::ConstIterator discoIt = m_identities.constBegin(), discoItEnd = m_identities.constEnd(); + for( ; discoIt != discoItEnd; ++discoIt ) + { + QDomElement identity = doc->createElement("identity"); + identity.setAttribute("category",(*discoIt).category); + identity.setAttribute("name",(*discoIt).name); + identity.setAttribute("type",(*discoIt).type); + info.appendChild(identity); + } + + // Features + QStringList::ConstIterator featuresIt = m_features.constBegin(), featuresItEnd = m_features.constEnd(); + for( ; featuresIt != featuresItEnd; ++featuresIt ) + { + QDomElement feature = doc->createElement("feature"); + feature.setAttribute("node",*featuresIt); + info.appendChild(feature); + } + + return info; +} + +void JabberCapabilitiesManager::CapabilitiesInformation::fromXml(const QDomElement &element) +{ + if( element.tagName() != "info") + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Invalid info element" << endl; + return; + } + + //if (!e.attribute("last-seen").isEmpty()) + // lastSeen_ = QDate::fromString(e.attribute("last-seen"),Qt::ISODate); + + for(QDomNode node = element.firstChild(); !node.isNull(); node = node.nextSibling()) + { + QDomElement infoElement = node.toElement(); + if( infoElement.isNull() ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Null element" << endl; + continue; + } + + if( infoElement.tagName() == "identity") + { + DiscoItem::Identity id; + id.category = infoElement.attribute("category"); + id.name = infoElement.attribute("name"); + id.type = infoElement.attribute("type"); + m_identities += id; + } + else if( infoElement.tagName() == "feature" ) + { + m_features += infoElement.attribute("node"); + } + else + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Unknown element" << endl; + } + + m_discovered = true; + } +} +//END CapabilitiesInformation + +//BEGIN Private(d-ptr) +class JabberCapabilitiesManager::Private +{ +public: + Private() + {} + + // Map a full jid to a capabilities + QMap jidCapabilitiesMap; + // Map a capabilities to its detail information + QMap capabilitiesInformationMap; +}; +//END Private(d-ptr) + +JabberCapabilitiesManager::JabberCapabilitiesManager() + : d(new Private) +{ +} + +JabberCapabilitiesManager::~JabberCapabilitiesManager() +{ + saveInformation(); + delete d; +} + +void JabberCapabilitiesManager::removeAccount(JabberAccount *account) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing account " << account->accountId() << endl; + + QValueList info = d->capabilitiesInformationMap.values(); + + QValueList::Iterator it, itEnd = info.end(); + for(it = info.begin(); it != info.end(); ++it) + { + (*it).removeAccount(account); + } +} + +void JabberCapabilitiesManager::updateCapabilities(JabberAccount *account, const XMPP::Jid &jid, const XMPP::Status &status ) +{ + if( !account->client() || !account->client()->rootTask() ) + return; + + + // Do don't anything if the jid correspond to the account's JabberClient jid. + // false means that we don't check for resources. + if( jid.compare(account->client()->jid(), false) ) + return; + + QString node = status.capsNode(), version = status.capsVersion(), extensions = status.capsExt(); + Capabilities capabilities( node, version, extensions ); + + // Check if the capabilities was really updated(i.e the content is different) + if( d->jidCapabilitiesMap[jid.full()] != capabilities) + { + // Unregister from all old caps nodes + // FIXME: We should only unregister & register from changed nodes + CapabilitiesList oldCaps = d->jidCapabilitiesMap[jid.full()].flatten(); + CapabilitiesList::Iterator oldCapsIt = oldCaps.begin(), oldCapsItEnd = oldCaps.end(); + for( ; oldCapsIt != oldCapsItEnd; ++oldCapsIt) + { + if( (*oldCapsIt) != Capabilities() ) + { + d->capabilitiesInformationMap[*oldCapsIt].removeJid(jid); + } + } + + // Check if the jid has caps in his presence message. + if( !status.capsNode().isEmpty() && !status.capsVersion().isEmpty() ) + { + // Register with all new caps nodes + d->jidCapabilitiesMap[jid.full()] = capabilities; + CapabilitiesList caps = capabilities.flatten(); + CapabilitiesList::Iterator newCapsIt = caps.begin(), newCapsItEnd = caps.end(); + for( ; newCapsIt != newCapsItEnd; ++newCapsIt ) + { + d->capabilitiesInformationMap[*newCapsIt].addJid(jid,account); + } + + emit capabilitiesChanged(jid); + + // Register new caps and check if we need to discover features + newCapsIt = caps.begin(); + for( ; newCapsIt != newCapsItEnd; ++newCapsIt ) + { + if( !d->capabilitiesInformationMap[*newCapsIt].discovered() && d->capabilitiesInformationMap[*newCapsIt].pendingRequests() == 0 ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << QString("Sending disco request to %1, node=%2").arg(QString(jid.full()).replace('%',"%%")).arg(node + "#" + (*newCapsIt).extensions()) << endl; + + d->capabilitiesInformationMap[*newCapsIt].setPendingRequests(1); + requestDiscoInfo(account, jid, node + "#" + (*newCapsIt).extensions()); + } + } + } + else + { + // Remove all caps specifications + kdDebug(JABBER_DEBUG_GLOBAL) << QString("Illegal caps info from %1: node=%2, ver=%3").arg(QString(jid.full()).replace('%',"%%")).arg(node).arg(version) << endl; + + d->jidCapabilitiesMap.remove( jid.full() ); + } + } + else + { + // Add to the list of jids + CapabilitiesList caps = capabilities.flatten(); + CapabilitiesList::Iterator capsIt = caps.begin(), capsItEnd = caps.end(); + for( ; capsIt != capsItEnd; ++capsIt) + { + d->capabilitiesInformationMap[*capsIt].addJid(jid,account); + } + } +} + +void JabberCapabilitiesManager::requestDiscoInfo(JabberAccount *account, const Jid& jid, const QString& node) +{ + if( !account->client()->rootTask() ) + return; + + JT_DiscoInfo *discoInfo = new JT_DiscoInfo(account->client()->rootTask()); + connect(discoInfo, SIGNAL(finished()), SLOT(discoRequestFinished())); + discoInfo->get(jid, node); + //pending_++; + //timer_.start(REQUEST_TIMEOUT,true); + discoInfo->go(true); +} + +void JabberCapabilitiesManager::discoRequestFinished() +{ + JT_DiscoInfo *discoInfo = (JT_DiscoInfo*)sender(); + if (!discoInfo) + return; + + DiscoItem item = discoInfo->item(); + Jid jid = discoInfo->jid(); + kdDebug(JABBER_DEBUG_GLOBAL) << QString("Disco response from %1, node=%2, success=%3").arg(QString(jid.full()).replace('%',"%%")).arg(discoInfo->node()).arg(discoInfo->success()) << endl; + + QStringList tokens = QStringList::split("#",discoInfo->node()); + + // Update features + Q_ASSERT(tokens.count() == 2); + QString node = tokens[0]; + QString extensions = tokens[1]; + + Capabilities jidCapabilities = d->jidCapabilitiesMap[jid.full()]; + if( jidCapabilities.node() == node ) + { + Capabilities capabilities(node, jidCapabilities.version(), extensions); + + if( discoInfo->success() ) + { + // Save identities & features + d->capabilitiesInformationMap[capabilities].setIdentities(item.identities()); + d->capabilitiesInformationMap[capabilities].setFeatures(item.features().list()); + d->capabilitiesInformationMap[capabilities].setPendingRequests(0); + d->capabilitiesInformationMap[capabilities].setDiscovered(true); + + // Save(Cache) information + saveInformation(); + + // Notify affected jids. + QStringList jids = d->capabilitiesInformationMap[capabilities].jids(); + QStringList::ConstIterator jidsIt = jids.constBegin(), jidsItEnd = jids.constEnd(); + for( ; jidsIt != jidsItEnd; ++jidsItEnd ) + { + emit capabilitiesChanged(*jidsIt); + } + } + else + { + QPair jidAccountPair = d->capabilitiesInformationMap[capabilities].nextJid(jid,discoInfo->parent()); + if( jidAccountPair.second ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << QString("Falling back on %1.").arg(QString(jidAccountPair.first.full()).replace('%',"%%")) << endl; + requestDiscoInfo( jidAccountPair.second, jidAccountPair.first, discoInfo->node() ); + } + else + { + kdDebug(JABBER_DEBUG_GLOBAL) << "No valid disco request avalable." << endl; + d->capabilitiesInformationMap[capabilities].setPendingRequests(0); + } + } + } + else + kdDebug(JABBER_DEBUG_GLOBAL) << QString("Current client node '%1' does not match response '%2'").arg(jidCapabilities.node()).arg(node) << endl; + + //for (unsigned int i = 0; i < item.features().list().count(); i++) + // printf(" Feature: %s\n",item.features().list()[i].latin1()); + + // Check pending requests +// pending_ = (pending_ > 0 ? pending_-1 : 0); +// if (!pending_) { +// timer_.stop(); +// updatePendingJIDs(); +// } +} + +void JabberCapabilitiesManager::loadCachedInformation() +{ + QString capsFileName; + capsFileName = locateLocal("appdata", QString::fromUtf8("jabber-capabilities-cache.xml")); + + // Load settings + QDomDocument doc; + QFile cacheFile(capsFileName); + if( !cacheFile.open(IO_ReadOnly) ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Could not open the Capabilities cache from disk." << endl; + return; + } + if( !doc.setContent(&cacheFile) ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Could not set the Capabilities cache from file." << endl; + return; + } + cacheFile.close(); + + QDomElement caps = doc.documentElement(); + if( caps.tagName() != "capabilities" ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Invalid capabilities element." << endl; + return; + } + + QDomNode node; + for(node = caps.firstChild(); !node.isNull(); node = node.nextSibling()) + { + QDomElement element = node.toElement(); + if( element.isNull() ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Found a null element." << endl; + continue; + } + + if( element.tagName() == "info" ) + { + CapabilitiesInformation info; + info.fromXml(element); + Capabilities entityCaps( element.attribute("node"),element.attribute("ver"),element.attribute("ext") ); + d->capabilitiesInformationMap[entityCaps] = info; + } + else + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Unknow element" << endl; + } + } +} + +bool JabberCapabilitiesManager::capabilitiesEnabled(const Jid &jid) const +{ + return d->jidCapabilitiesMap.contains( jid.full() ); +} + +XMPP::Features JabberCapabilitiesManager::features(const Jid& jid) const +{ + QStringList featuresList; + + if( capabilitiesEnabled(jid) ) + { + CapabilitiesList capabilitiesList = d->jidCapabilitiesMap[jid.full()].flatten(); + CapabilitiesList::ConstIterator capsIt = capabilitiesList.constBegin(), capsItEnd = capabilitiesList.constEnd(); + for( ; capsIt != capsItEnd; ++capsIt) + { + featuresList += d->capabilitiesInformationMap[*capsIt].features(); + } + } + + return Features(featuresList); +} + +QString JabberCapabilitiesManager::clientName(const Jid& jid) const +{ + if( capabilitiesEnabled(jid) ) + { + Capabilities caps = d->jidCapabilitiesMap[jid.full()]; + QString name = d->capabilitiesInformationMap[Capabilities(caps.node(),caps.version(),caps.version())].identities().first().name; + + // Try to be intelligent about the name + /*if (name.isEmpty()) { + name = cs.node(); + if (name.startsWith("http://")) + name = name.right(name.length() - 7); + + if (name.startsWith("www.")) + name = name.right(name.length() - 4); + + int cut_pos = name.find("."); + if (cut_pos != -1) { + name = name.left(cut_pos); + } + }*/ + + return name; + } + else + { + return QString(); + } +} + +QString JabberCapabilitiesManager::clientVersion(const Jid& jid) const +{ + return (capabilitiesEnabled(jid) ? d->jidCapabilitiesMap[jid.full()].version() : QString()); +} + +void JabberCapabilitiesManager::saveInformation() +{ + QString capsFileName; + capsFileName = locateLocal("appdata", QString::fromUtf8("jabber-capabilities-cache.xml")); + + // Generate XML + QDomDocument doc; + QDomElement capabilities = doc.createElement("capabilities"); + doc.appendChild(capabilities); + QMap::ConstIterator it = d->capabilitiesInformationMap.constBegin(), itEnd = d->capabilitiesInformationMap.constEnd(); + for( ; it != itEnd; ++it ) + { + QDomElement info = it.data().toXml(&doc); + info.setAttribute("node",it.key().node()); + info.setAttribute("ver",it.key().version()); + info.setAttribute("ext",it.key().extensions()); + capabilities.appendChild(info); + } + + // Save + QFile capsFile(capsFileName); + if( !capsFile.open(IO_WriteOnly) ) + { + kdDebug(JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Error while opening Capabilities cache file." << endl; + return; + } + + QTextStream textStream; + textStream.setDevice(&capsFile); + textStream.setEncoding(QTextStream::UnicodeUTF8); + textStream << doc.toString(); + textStream.unsetDevice(); + capsFile.close(); +} + +#include "jabbercapabilitiesmanager.moc" diff --git a/kopete/protocols/jabber/jabbercapabilitiesmanager.h b/kopete/protocols/jabber/jabbercapabilitiesmanager.h new file mode 100644 index 00000000..3010479f --- /dev/null +++ b/kopete/protocols/jabber/jabbercapabilitiesmanager.h @@ -0,0 +1,211 @@ + /* + jabbercapabilitiesmanager.h - Manage entity capabilities(JEP-0115) pool. + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + Imported from caps.cpp from Psi: + Copyright (C) 2005 Remko Troncon + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ +#ifndef JABBERCAPABILITIESMANAGER_H +#define JABBERCAPABILITIESMANAGER_H + +#include +#include +#include + +using namespace XMPP; + +class JabberAccount; + +/** + * @brief Manage Jabber entity capabilities (JEP-0115) + * @author Michaël Larouche + * @author Remko Troncon + */ +class JabberCapabilitiesManager : public QObject +{ + Q_OBJECT +public: + /** + * Construct + */ + JabberCapabilitiesManager(); + ~JabberCapabilitiesManager(); + + /** + * Load cached information from local file. + */ + void loadCachedInformation(); + + /** + * Check if the jid support Entity capabitilies. + * @param jid JID to check. + * @return true if the jid support entity capabitilies. + */ + bool capabilitiesEnabled(const Jid& jid) const; + + /** + * Remove account from manager. + */ + void removeAccount(JabberAccount *account); + + /** + * Return the features supported for the JID. + */ + XMPP::Features features(const Jid& jid) const; + /** + * Return the client name for the current JID. + */ + QString clientName(const Jid& jid) const; + /** + * Return the client version for the current JID. + */ + QString clientVersion(const Jid& jid) const; + +signals: + void capabilitiesChanged(const XMPP::Jid &jid); + +public slots: + /** + * Update if necessary the capabities for the JID passed in args. + * Caps are received in Presence messages so that's why we are + * passing a XMPP::Status object. + * + * @param jid JID that capabilities was updated. + * @param status The XMPP::Status that contain the caps. + */ + void updateCapabilities(JabberAccount *account, const XMPP::Jid &jid, const XMPP::Status &status); + +private slots: + /** + * @brief Called when a reply to disco#info request was received. + * If the result was succesful, the resulting features are recorded in the + * features database for the requested node, and all the affected jids are + * put in the queue for update notification. + * If the result was unsuccesful, another jid with the same capabilities is + * selected and sent a disco#info query. + */ + void discoRequestFinished(); + +private: + /** + * @brief Sends a disco#info request to a given node of a jid through an account. + * When the request is finished, the discoRequestFinished() slot is called. + * + * @param account The account through which to send the disco request. + * @param jid The target entity's JID + * @param node The target disco#info node + */ + void requestDiscoInfo(JabberAccount *account, const Jid& jid, const QString& node); + + /** + * Save capabilities information to disk. + */ + void saveInformation(); + + class Capabilities; + typedef QValueList CapabilitiesList; + /** + * @brief A class representing an entity capability specification. + * An entity capability is a combination of a node, a version, and a set of + * extensions. + */ + class Capabilities + { + public: + /** + * Default constructor. + */ + Capabilities(); + /** + * Define capabilities. + * @param node the node + * @param version the version + * @param extensions the list of extensions (separated by spaces) + */ + Capabilities(const QString &node, const QString &version, const QString &extensions); + /** + * Returns the node of the capabilities specification. + */ + const QString& node() const; + /** + * @brief Returns the version of the capabilities specification. + */ + const QString& version() const; + /** + * @brief Returns the extensions of the capabilities specification. + */ + const QString& extensions() const; + /** + * \brief Flattens the caps specification into the set of 'simple' specifications. + * A 'simple' specification is a specification with exactly one extension, + * or with the version number as the extension. + * + * Example: A caps specification with node=http://psi-im.org, version=0.10, + * and ext='achat vchat' would be expanded into the following list of specs: + * node=http://psi-im.org, ver=0.10, ext=0.10 + * node=http://psi-im.org, ver=0.10, ext=achat + * node=http://psi-im.org, ver=0.10, ext=vchat + */ + CapabilitiesList flatten() const; + + bool operator==(const Capabilities&) const; + bool operator!=(const Capabilities&) const; + bool operator<(const Capabilities&) const; + + private: + QString m_node, m_version, m_extensions; + }; + + class CapabilitiesInformation + { + public: + CapabilitiesInformation(); + const QStringList& features() const; + const DiscoItem::Identities& identities() const; + QStringList jids() const; + bool discovered() const; + int pendingRequests() const; + + void reset(); + void removeAccount(JabberAccount* acc); + void removeJid(const Jid&); + void addJid(const Jid&, JabberAccount*); + QPair nextJid(const Jid&, const Task*); + + void setDiscovered(bool); + void setPendingRequests(int); + void setIdentities(const DiscoItem::Identities&); + void setFeatures(const QStringList&); + + QDomElement toXml(QDomDocument *) const; + void fromXml(const QDomElement&); + + protected: + void updateLastSeen(); + + private: + bool m_discovered; + int m_pendingRequests; + QStringList m_features; + DiscoItem::Identities m_identities; + QValueList > m_jids; + QDate m_lastSeen; + }; + + class Private; + Private *d; +}; + +#endif diff --git a/kopete/protocols/jabber/jabberchatsession.cpp b/kopete/protocols/jabber/jabberchatsession.cpp new file mode 100644 index 00000000..faa6f950 --- /dev/null +++ b/kopete/protocols/jabber/jabberchatsession.cpp @@ -0,0 +1,357 @@ +/* + jabberchatsession.cpp - Jabber Chat Session + + Copyright (c) 2004 by Till Gerken + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "jabberchatsession.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "kopetechatsessionmanager.h" +#include "kopetemessage.h" +#include "kopeteviewplugin.h" +#include "kopeteview.h" +#include "jabberprotocol.h" +#include "jabberaccount.h" +#include "jabberclient.h" +#include "jabbercontact.h" +#include "jabberresource.h" +#include "jabberresourcepool.h" +#include "kioslave/jabberdisco.h" + + +JabberChatSession::JabberChatSession ( JabberProtocol *protocol, const JabberBaseContact *user, + Kopete::ContactPtrList others, const QString &resource, const char *name ) + : Kopete::ChatSession ( user, others, protocol, name ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "New message manager for " << user->contactId () << endl; + + // make sure Kopete knows about this instance + Kopete::ChatSessionManager::self()->registerChatSession ( this ); + + connect ( this, SIGNAL ( messageSent ( Kopete::Message &, Kopete::ChatSession * ) ), + this, SLOT ( slotMessageSent ( Kopete::Message &, Kopete::ChatSession * ) ) ); + + connect ( this, SIGNAL ( myselfTyping ( bool ) ), this, SLOT ( slotSendTypingNotification ( bool ) ) ); + + connect ( this, SIGNAL ( onlineStatusChanged(Kopete::Contact*, const Kopete::OnlineStatus&, const Kopete::OnlineStatus& ) ), this, SLOT ( slotUpdateDisplayName () ) ); + + // check if the user ID contains a hardwired resource, + // we'll have to use that one in that case + XMPP::Jid jid = user->rosterItem().jid() ; + + mResource = jid.resource().isEmpty () ? resource : jid.resource (); + slotUpdateDisplayName (); + +#ifdef SUPPORT_JINGLE + KAction *jabber_voicecall = new KAction( i18n("Voice call" ), "voicecall", 0, members().getFirst(), SLOT(voiceCall ()), actionCollection(), "jabber_voicecall" ); + + setInstance(protocol->instance()); + jabber_voicecall->setEnabled( false ); + + + Kopete::ContactPtrList chatMembers = members (); + if ( chatMembers.first () ) + { + // Check if the current contact support Voice calls, also honour lock by default. + // FIXME: we should use the active ressource + JabberResource *bestResource = account()->resourcePool()-> bestJabberResource( static_cast(chatMembers.first())->rosterItem().jid() ); + if( bestResource && bestResource->features().canVoice() ) + { + jabber_voicecall->setEnabled( true ); + } + } + +#endif + + new KAction( i18n( "Send File" ), "attach", 0, this, SLOT( slotSendFile() ), actionCollection(), "jabberSendFile" ); + + setXMLFile("jabberchatui.rc"); + +} + +JabberChatSession::~JabberChatSession( ) +{ + JabberAccount * a = dynamic_cast(Kopete::ChatSession::account ()); + if( !a ) //When closing kopete, the account is partially destroyed already, dynamic_cast return 0 + return; + if ( a->configGroup()->readBoolEntry ("SendEvents", true) && + a->configGroup()->readBoolEntry ("SendGoneEvent", true) ) + sendNotification( XMPP::GoneEvent ); +} + + +void JabberChatSession::slotUpdateDisplayName () +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << endl; + + Kopete::ContactPtrList chatMembers = members (); + + // make sure we do have members in the chat + if ( !chatMembers.first () ) + return; + + XMPP::Jid jid = static_cast(chatMembers.first())->rosterItem().jid(); + + if ( !mResource.isEmpty () ) + jid.setResource ( mResource ); + + QString statusText = i18n("a contact's online status in parenthesis.", " (%1)") + .arg( chatMembers.first()->onlineStatus().description() ); + if ( jid.resource().isEmpty () ) + setDisplayName ( chatMembers.first()->metaContact()->displayName () + statusText ); + else + setDisplayName ( chatMembers.first()->metaContact()->displayName () + "/" + jid.resource () + statusText ); + +} + +const JabberBaseContact *JabberChatSession::user () const +{ + + return static_cast(Kopete::ChatSession::myself()); + +} + +JabberAccount *JabberChatSession::account () const +{ + + return static_cast(Kopete::ChatSession::account ()); + +} + +const QString &JabberChatSession::resource () const +{ + + return mResource; + +} + +void JabberChatSession::appendMessage ( Kopete::Message &msg, const QString &fromResource ) +{ + + mResource = fromResource; + + slotUpdateDisplayName (); + Kopete::ChatSession::appendMessage ( msg ); + + // We send the notifications for Delivered and Displayed events. More granular management + // (ie.: send Displayed event when it is really displayed) + // of these events would require changes in the chatwindow API. + + if ( account()->configGroup()->readBoolEntry ("SendEvents", true) ) + { + if ( account()->configGroup()->readBoolEntry ("SendDeliveredEvent", true) ) + { + sendNotification( XMPP::DeliveredEvent ); + } + + if ( account()->configGroup()->readBoolEntry ("SendDisplayedEvent", true) ) + { + sendNotification( XMPP::DisplayedEvent ); + } + } +} + +void JabberChatSession::sendNotification( XMPP::MsgEvent event ) +{ + if ( !account()->isConnected () ) + return; + + JabberContact *contact; + QPtrListIterator listIterator ( members () ); + + while ( ( contact = dynamic_cast( listIterator.current () ) ) != 0 ) + { + ++listIterator; + if ( contact->isContactRequestingEvent( event ) ) + { + // create JID for the recipient + XMPP::Jid toJid = contact->rosterItem().jid(); + + // set resource properly if it has been selected already + if ( !resource().isEmpty () ) + toJid.setResource ( resource () ); + + XMPP::Message message; + + message.setFrom ( account()->client()->jid() ); + message.setTo ( toJid ); + message.setEventId ( contact->lastReceivedMessageId () ); + // store composing event depending on state + message.addEvent ( event ); + + if (view() && view()->plugin()->pluginId() == "kopete_emailwindow" ) + { + message.setType ( "normal" ); + } + else + { + message.setType ( "chat" ); + } + + + // send message + account()->client()->sendMessage ( message ); + } + } +} + +void JabberChatSession::slotSendTypingNotification ( bool typing ) +{ + if ( !account()->configGroup()->readBoolEntry ("SendEvents", true) + || !account()->configGroup()->readBoolEntry("SendComposingEvent", true) ) + return; + + // create JID for us as sender + XMPP::Jid fromJid = static_cast(myself())->rosterItem().jid(); + fromJid.setResource ( account()->configGroup()->readEntry( "Resource", QString::null ) ); + + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Sending out typing notification (" << typing << ") to all chat members." << endl; + + typing ? sendNotification( ComposingEvent ) : sendNotification( CancelEvent ); +} + +void JabberChatSession::slotMessageSent ( Kopete::Message &message, Kopete::ChatSession * ) +{ + + if( account()->isConnected () ) + { + XMPP::Message jabberMessage; + JabberBaseContact *recipient = static_cast(message.to().first()); + + jabberMessage.setFrom ( account()->client()->jid() ); + + + XMPP::Jid toJid = recipient->rosterItem().jid(); + + if( !resource().isEmpty () ) + toJid.setResource ( resource() ); + + jabberMessage.setTo ( toJid ); + + jabberMessage.setSubject ( message.subject () ); + jabberMessage.setTimeStamp ( message.timestamp () ); + + if ( message.plainBody().find ( "-----BEGIN PGP MESSAGE-----" ) != -1 ) + { + /* + * This message is encrypted, so we need to set + * a fake body indicating that this is an encrypted + * message (for clients not implementing this + * functionality) and then generate the encrypted + * payload out of the old message body. + */ + + // please don't translate the following string + jabberMessage.setBody ( i18n ( "This message is encrypted." ) ); + + QString encryptedBody = message.plainBody (); + + // remove PGP header and footer from message + encryptedBody.truncate ( encryptedBody.length () - QString("-----END PGP MESSAGE-----").length () - 2 ); + encryptedBody = encryptedBody.right ( encryptedBody.length () - encryptedBody.find ( "\n\n" ) - 2 ); + + // assign payload to message + jabberMessage.setXEncrypted ( encryptedBody ); + } + else + { + // this message is not encrypted + jabberMessage.setBody ( message.plainBody ()); + if (message.format() == Kopete::Message::RichText) + { + JabberResource *bestResource = account()->resourcePool()->bestJabberResource(toJid); + if( bestResource && bestResource->features().canXHTML() ) + { + QString xhtmlBody = message.escapedBody(); + + // According to JEP-0071 8.9 it is only RECOMMANDED to replace \n with
+ // which mean that some implementation (gaim 2 beta) may still think that \n are linebreak. + // and considered the fact that KTextEditor generate a well indented XHTML, we need to remove all \n from it + // see Bug 121627 + // Anyway, theses client that do like that are *WRONG* considreded the example of jep-71 where there are lot of + // linebreak that are not interpreted. - Olivier 2006-31-03 + xhtmlBody.replace("\n",""); + + //  is not a valid XML entity + xhtmlBody.replace(" " , " "); + + xhtmlBody="

"+ xhtmlBody +"

"; + jabberMessage.setXHTMLBody ( xhtmlBody ); + } + } + } + + // determine type of the widget and set message type accordingly + // "kopete_emailwindow" is the default email Kopete::ViewPlugin. If other email plugins + // become available, either jabber will have to provide its own selector or libkopete will need + // a better way of categorising view plugins. + + // FIXME: the view() is a speedy way to solve BUG:108389. A better solution is to be found + // but I don't want to introduce a new bug during the bug hunt ;-). + if (view() && view()->plugin()->pluginId() == "kopete_emailwindow" ) + { + jabberMessage.setType ( "normal" ); + } + else + { + jabberMessage.setType ( "chat" ); + } + + // add request for all notifications + jabberMessage.addEvent( OfflineEvent ); + jabberMessage.addEvent( ComposingEvent ); + jabberMessage.addEvent( DeliveredEvent ); + jabberMessage.addEvent( DisplayedEvent ); + + + // send the message + account()->client()->sendMessage ( jabberMessage ); + + // append the message to the manager + Kopete::ChatSession::appendMessage ( message ); + + // tell the manager that we sent successfully + messageSucceeded (); + } + else + { + account()->errorConnectFirst (); + + // FIXME: there is no messageFailed() yet, + // but we need to stop the animation etc. + messageSucceeded (); + } + +} + + void JabberChatSession::slotSendFile() + { + QPtrListcontacts = members(); + static_cast(contacts.first())->sendFile(); + } + +#include "jabberchatsession.moc" + +// vim: set noet ts=4 sts=4 sw=4: +// kate: tab-width 4; replace-tabs off; space-indent off; diff --git a/kopete/protocols/jabber/jabberchatsession.h b/kopete/protocols/jabber/jabberchatsession.h new file mode 100644 index 00000000..66b4c63b --- /dev/null +++ b/kopete/protocols/jabber/jabberchatsession.h @@ -0,0 +1,103 @@ +/* + jabbermessagemanager.h - Jabber Message Manager + + Copyright (c) 2004 by Till Gerken + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef JABBERCHATSESSION_H +#define JABBERCHATSESSION_H + +#include "kopetechatsession.h" + +#include "im.h" + +class JabberProtocol; +class JabberAccount; +class JabberBaseContact; +namespace Kopete { class Message; } +class QString; + + +/** + * @author Till Gerken + */ +class JabberChatSession : public Kopete::ChatSession +{ + Q_OBJECT + +public: + JabberChatSession ( JabberProtocol *protocol, const JabberBaseContact *user, + Kopete::ContactPtrList others, const QString &resource = "", + const char *name = 0 ); + + ~JabberChatSession(); + + /** + * @brief Get the local user in the session + * @return the local user in the session, same as account()->myself() + */ + const JabberBaseContact *user () const; + + /** + * @brief get the account + * @return the account + */ + JabberAccount *account() const ; + + /** + * @brief Return the resource this manager is currently associated with. + * @return currently associated resource + */ + const QString &resource () const; + +public slots: + /** + * Show a message to the chatwindow, or append it to the queue. + * This is the function protocols HAVE TO call for both incoming and outgoing messages + * if the message must be showed in the chatwindow + * + * This is an overloaded version of the original implementation which + * also accepts a resource the message originates from. The message manager + * will set its own resource to the resource the message was received from. + * See @ref JabberBaseContact::manager() about how to deal with instantiating + * new message managers for messages not originating from the same resource + * a manager already exists for. + */ + void appendMessage ( Kopete::Message &msg, const QString &fromResource ); + +private slots: + void slotSendTypingNotification ( bool typing ); + void slotMessageSent ( Kopete::Message &message, Kopete::ChatSession *kmm ); + + /** + * Re-generate the display name + */ + void slotUpdateDisplayName (); + + void slotSendFile(); + +private: + /** + * Send a notification (XMPP::MsgEvent) to the members of the chatsession. + * SlotSendTypingNotification uses it. + */ + void sendNotification( XMPP::MsgEvent event ); + + QString mResource; +}; + +#endif + +// vim: set noet ts=4 sts=4 tw=4: + diff --git a/kopete/protocols/jabber/jabberchatui.rc b/kopete/protocols/jabber/jabberchatui.rc new file mode 100644 index 00000000..9db9abe2 --- /dev/null +++ b/kopete/protocols/jabber/jabberchatui.rc @@ -0,0 +1,19 @@ + + + + + &Chat + + + + + + + + + + + + + + \ No newline at end of file diff --git a/kopete/protocols/jabber/jabberclient.cpp b/kopete/protocols/jabber/jabberclient.cpp new file mode 100644 index 00000000..b8e05d48 --- /dev/null +++ b/kopete/protocols/jabber/jabberclient.cpp @@ -0,0 +1,1137 @@ + +/*************************************************************************** + jabberclient.cpp - Generic Jabber Client Class + ------------------- + begin : Sat May 25 2005 + copyright : (C) 2005 by Till Gerken + (C) 2006 by Michaël Larouche + + Kopete (C) 2001-2006 Kopete developers + . + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "jabberclient.h" + +#include +#include + +#include +#include +#include +#include + +#include "jabberconnector.h" + +#define JABBER_PENALTY_TIME 2 + +class JabberClient::Private +{ +public: + Private() + : jabberClient(0L), jabberClientStream(0L), jabberClientConnector(0L), jabberTLS(0L), jabberTLSHandler(0L) + {} + ~Private() + { + if ( jabberClient ) + { + jabberClient->close (); + } + + delete jabberClient; + delete jabberClientStream; + delete jabberClientConnector; + delete jabberTLSHandler; + delete jabberTLS; + } + + // connection details + XMPP::Jid jid; + QString password; + + // XMPP backend + XMPP::Client *jabberClient; + XMPP::ClientStream *jabberClientStream; + JabberConnector *jabberClientConnector; + QCA::TLS *jabberTLS; + XMPP::QCATLSHandler *jabberTLSHandler; + + // ignore TLS warnings + bool ignoreTLSWarnings; + + // current S5B server instance + static XMPP::S5BServer *s5bServer; + // address list being handled by the S5B server instance + static QStringList s5bAddressList; + // port of S5B server + static int s5bServerPort; + + // local IP address + QString localAddress; + + // whether TLS (or direct SSL in case of the old protocol) should be used + bool forceTLS; + + // whether direct SSL connections should be used + bool useSSL; + + // use XMPP 1.0 or the older protocol version + bool useXMPP09; + + // whether SSL support should be probed in case the old protocol is used + bool probeSSL; + + // override the default server name and port (only pre-XMPP 1.0) + bool overrideHost; + QString server; + int port; + + // allow transmission of plaintext passwords + bool allowPlainTextPassword; + + // enable file transfers + bool fileTransfersEnabled; + + // current penalty time + int currentPenaltyTime; + + // client information + QString clientName, clientVersion, osName; + + // timezone information + QString timeZoneName; + int timeZoneOffset; + + // Caps(JEP-0115: Entity Capabilities) information + QString capsNode, capsVersion; + DiscoItem::Identity discoIdentity; +}; + +XMPP::S5BServer *JabberClient::Private::s5bServer = 0L; +QStringList JabberClient::Private::s5bAddressList; +int JabberClient::Private::s5bServerPort = 8010; + +JabberClient::JabberClient () +{ + d = new Private(); + + cleanUp (); + + // initiate penalty timer + QTimer::singleShot ( JABBER_PENALTY_TIME * 1000, this, SLOT ( slotUpdatePenaltyTime () ) ); + +} + +JabberClient::~JabberClient () +{ + delete d; +} + +void JabberClient::cleanUp () +{ + if ( d->jabberClient ) + { + d->jabberClient->close (); + } + + delete d->jabberClient; + delete d->jabberClientStream; + delete d->jabberClientConnector; + delete d->jabberTLSHandler; + delete d->jabberTLS; + + d->jabberClient = 0L; + d->jabberClientStream = 0L; + d->jabberClientConnector = 0L; + d->jabberTLSHandler = 0L; + d->jabberTLS = 0L; + + d->currentPenaltyTime = 0; + + d->jid = XMPP::Jid (); + d->password = QString::null; + + setForceTLS ( false ); + setUseSSL ( false ); + setUseXMPP09 ( false ); + setProbeSSL ( false ); + + setOverrideHost ( false ); + + setAllowPlainTextPassword ( true ); + + setFileTransfersEnabled ( false ); + setS5BServerPort ( 8010 ); + + setClientName ( QString::null ); + setClientVersion ( QString::null ); + setOSName ( QString::null ); + + setTimeZone ( "UTC", 0 ); + + setIgnoreTLSWarnings ( false ); + +} + +void JabberClient::slotUpdatePenaltyTime () +{ + + if ( d->currentPenaltyTime >= JABBER_PENALTY_TIME ) + d->currentPenaltyTime -= JABBER_PENALTY_TIME; + else + d->currentPenaltyTime = 0; + + QTimer::singleShot ( JABBER_PENALTY_TIME * 1000, this, SLOT ( slotUpdatePenaltyTime () ) ); + +} + +void JabberClient::setIgnoreTLSWarnings ( bool flag ) +{ + + d->ignoreTLSWarnings = flag; + +} + +bool JabberClient::ignoreTLSWarnings () +{ + + return d->ignoreTLSWarnings; + +} + +bool JabberClient::setS5BServerPort ( int port ) +{ + + d->s5bServerPort = port; + + if ( fileTransfersEnabled () ) + { + return s5bServer()->start ( port ); + } + + return true; + +} + +int JabberClient::s5bServerPort () const +{ + + return d->s5bServerPort; + +} + +XMPP::S5BServer *JabberClient::s5bServer () +{ + + if ( !d->s5bServer ) + { + d->s5bServer = new XMPP::S5BServer (); + QObject::connect ( d->s5bServer, SIGNAL ( destroyed () ), this, SLOT ( slotS5BServerGone () ) ); + + /* + * Try to start the server at the default port here. + * We have no way of notifying the caller of an error. + * However, since the caller will usually also + * use setS5BServerPort() to ensure the correct + * port, we can return an error code there. + */ + if ( fileTransfersEnabled () ) + { + s5bServer()->start ( d->s5bServerPort ); + } + } + + return d->s5bServer; + +} + +void JabberClient::slotS5BServerGone () +{ + + d->s5bServer = 0L; + + if ( d->jabberClient ) + d->jabberClient->s5bManager()->setServer( 0L ); + +} + +void JabberClient::addS5BServerAddress ( const QString &address ) +{ + QStringList newList; + + d->s5bAddressList.append ( address ); + + // now filter the list without dupes + for ( QStringList::Iterator it = d->s5bAddressList.begin (); it != d->s5bAddressList.end (); ++it ) + { + if ( !newList.contains ( *it ) ) + newList.append ( *it ); + } + + s5bServer()->setHostList ( newList ); + +} + +void JabberClient::removeS5BServerAddress ( const QString &address ) +{ + QStringList newList; + + QStringList::iterator it = d->s5bAddressList.find ( address ); + if ( it != d->s5bAddressList.end () ) + { + d->s5bAddressList.remove ( it ); + } + + if ( d->s5bAddressList.isEmpty () ) + { + delete d->s5bServer; + d->s5bServer = 0L; + } + else + { + // now filter the list without dupes + for ( QStringList::Iterator it = d->s5bAddressList.begin (); it != d->s5bAddressList.end (); ++it ) + { + if ( !newList.contains ( *it ) ) + newList.append ( *it ); + } + + s5bServer()->setHostList ( newList ); + } + +} + +void JabberClient::setForceTLS ( bool flag ) +{ + + d->forceTLS = flag; + +} + +bool JabberClient::forceTLS () const +{ + + return d->forceTLS; + +} + +void JabberClient::setUseSSL ( bool flag ) +{ + + d->useSSL = flag; + +} + +bool JabberClient::useSSL () const +{ + + return d->useSSL; + +} + +void JabberClient::setUseXMPP09 ( bool flag ) +{ + + d->useXMPP09 = flag; + +} + +bool JabberClient::useXMPP09 () const +{ + + return d->useXMPP09; + +} + +void JabberClient::setProbeSSL ( bool flag ) +{ + + d->probeSSL = flag; + +} + +bool JabberClient::probeSSL () const +{ + + return d->probeSSL; + +} + +void JabberClient::setOverrideHost ( bool flag, const QString &server, int port ) +{ + + d->overrideHost = flag; + d->server = server; + d->port = port; + +} + +bool JabberClient::overrideHost () const +{ + + return d->overrideHost; + +} + +void JabberClient::setAllowPlainTextPassword ( bool flag ) +{ + + d->allowPlainTextPassword = flag; + +} + +bool JabberClient::allowPlainTextPassword () const +{ + + return d->allowPlainTextPassword; + +} + +void JabberClient::setFileTransfersEnabled ( bool flag, const QString &localAddress ) +{ + + d->fileTransfersEnabled = flag; + d->localAddress = localAddress; + +} + +QString JabberClient::localAddress () const +{ + + return d->localAddress; + +} + +bool JabberClient::fileTransfersEnabled () const +{ + + return d->fileTransfersEnabled; + +} + +void JabberClient::setClientName ( const QString &clientName ) +{ + + d->clientName = clientName; + +} + +QString JabberClient::clientName () const +{ + + return d->clientName; + +} + +void JabberClient::setClientVersion ( const QString &clientVersion ) +{ + + d->clientVersion = clientVersion; + +} + +QString JabberClient::clientVersion () const +{ + + return d->clientVersion; + +} + +void JabberClient::setOSName ( const QString &osName ) +{ + + d->osName = osName; + +} + +QString JabberClient::osName () const +{ + + return d->osName; + +} + +void JabberClient::setCapsNode( const QString &capsNode ) +{ + d->capsNode = capsNode; +} + +QString JabberClient::capsNode() const +{ + return d->capsNode; +} + +void JabberClient::setCapsVersion( const QString &capsVersion ) +{ + d->capsVersion = capsVersion; +} + +QString JabberClient::capsVersion() const +{ + return d->capsVersion; +} + +QString JabberClient::capsExt() const +{ + if(d->jabberClient) + { + return d->jabberClient->capsExt(); + } + + return QString(); +} +void JabberClient::setDiscoIdentity( DiscoItem::Identity identity ) +{ + d->discoIdentity = identity; +} + +DiscoItem::Identity JabberClient::discoIdentity() const +{ + return d->discoIdentity; +} + +void JabberClient::setTimeZone ( const QString &timeZoneName, int timeZoneOffset ) +{ + + d->timeZoneName = timeZoneName; + d->timeZoneOffset = timeZoneOffset; + +} + +QString JabberClient::timeZoneName () const +{ + + return d->timeZoneName; + +} + +int JabberClient::timeZoneOffset () const +{ + + return d->timeZoneOffset; + +} + +int JabberClient::getPenaltyTime () +{ + + int currentTime = d->currentPenaltyTime; + + d->currentPenaltyTime += JABBER_PENALTY_TIME; + + return currentTime; + +} + +XMPP::Client *JabberClient::client () const +{ + + return d->jabberClient; + +} + +XMPP::ClientStream *JabberClient::clientStream () const +{ + + return d->jabberClientStream; + +} + +JabberConnector *JabberClient::clientConnector () const +{ + + return d->jabberClientConnector; + +} + +XMPP::Task *JabberClient::rootTask () const +{ + + if ( client () ) + { + return client()->rootTask (); + } + else + { + return 0l; + } + +} + +XMPP::FileTransferManager *JabberClient::fileTransferManager () const +{ + + if ( client () ) + { + return client()->fileTransferManager (); + } + else + { + return 0L; + } + +} + +XMPP::Jid JabberClient::jid () const +{ + + return d->jid; + +} + +JabberClient::ErrorCode JabberClient::connect ( const XMPP::Jid &jid, const QString &password, bool auth ) +{ + /* + * Close any existing connection. + */ + if ( d->jabberClient ) + { + d->jabberClient->close (); + } + + d->jid = jid; + d->password = password; + + /* + * Return an error if we should force TLS but it's not available. + */ + if ( ( forceTLS () || useSSL () || probeSSL () ) && !QCA::isSupported ( QCA::CAP_TLS ) ) + { + return NoTLS; + } + + /* + * Instantiate connector, responsible for dealing with the socket. + * This class uses KDE's socket code, which in turn makes use of + * the global proxy settings. + */ + d->jabberClientConnector = new JabberConnector; + + d->jabberClientConnector->setOptSSL ( useSSL () ); + + if ( useXMPP09 () ) + { + if ( overrideHost () ) + { + d->jabberClientConnector->setOptHostPort ( d->server, d->port ); + } + + d->jabberClientConnector->setOptProbe ( probeSSL () ); + + } + + /* + * Setup authentication layer + */ + if ( QCA::isSupported ( QCA::CAP_TLS ) ) + { + d->jabberTLS = new QCA::TLS; + d->jabberTLSHandler = new XMPP::QCATLSHandler ( d->jabberTLS ); + + { + using namespace XMPP; + QObject::connect ( d->jabberTLSHandler, SIGNAL ( tlsHandshaken() ), this, SLOT ( slotTLSHandshaken () ) ); + } + + QPtrList certStore; + d->jabberTLS->setCertificateStore ( certStore ); + } + + /* + * Instantiate client stream which handles the network communication by referring + * to a connector (proxying etc.) and a TLS handler (security layer) + */ + d->jabberClientStream = new XMPP::ClientStream ( d->jabberClientConnector, d->jabberTLSHandler ); + + { + using namespace XMPP; + QObject::connect ( d->jabberClientStream, SIGNAL ( needAuthParams(bool, bool, bool) ), + this, SLOT ( slotCSNeedAuthParams (bool, bool, bool) ) ); + QObject::connect ( d->jabberClientStream, SIGNAL ( authenticated () ), + this, SLOT ( slotCSAuthenticated () ) ); + QObject::connect ( d->jabberClientStream, SIGNAL ( connectionClosed () ), + this, SLOT ( slotCSDisconnected () ) ); + QObject::connect ( d->jabberClientStream, SIGNAL ( delayedCloseFinished () ), + this, SLOT ( slotCSDisconnected () ) ); + QObject::connect ( d->jabberClientStream, SIGNAL ( warning (int) ), + this, SLOT ( slotCSWarning (int) ) ); + QObject::connect ( d->jabberClientStream, SIGNAL ( error (int) ), + this, SLOT ( slotCSError (int) ) ); + } + + d->jabberClientStream->setOldOnly ( useXMPP09 () ); + + /* + * Initiate anti-idle timer (will be triggered every 55 seconds). + */ + d->jabberClientStream->setNoopTime ( 55000 ); + + /* + * Allow plaintext password authentication or not? + */ + d->jabberClientStream->setAllowPlain( allowPlainTextPassword () ); + + /* + * Setup client layer. + */ + d->jabberClient = new XMPP::Client ( this ); + + /* + * Enable file transfer (IP and server will be set after connection + * has been established. + */ + if ( fileTransfersEnabled () ) + { + d->jabberClient->setFileTransferEnabled ( true ); + + { + using namespace XMPP; + QObject::connect ( d->jabberClient->fileTransferManager(), SIGNAL ( incomingReady() ), + this, SLOT ( slotIncomingFileTransfer () ) ); + } + } + + /* This should only be done here to connect the signals, otherwise it is a + * bad idea. + */ + { + using namespace XMPP; + QObject::connect ( d->jabberClient, SIGNAL ( subscription (const Jid &, const QString &) ), + this, SLOT ( slotSubscription (const Jid &, const QString &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( rosterRequestFinished ( bool, int, const QString & ) ), + this, SLOT ( slotRosterRequestFinished ( bool, int, const QString & ) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( rosterItemAdded (const RosterItem &) ), + this, SLOT ( slotNewContact (const RosterItem &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( rosterItemUpdated (const RosterItem &) ), + this, SLOT ( slotContactUpdated (const RosterItem &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( rosterItemRemoved (const RosterItem &) ), + this, SLOT ( slotContactDeleted (const RosterItem &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( resourceAvailable (const Jid &, const Resource &) ), + this, SLOT ( slotResourceAvailable (const Jid &, const Resource &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( resourceUnavailable (const Jid &, const Resource &) ), + this, SLOT ( slotResourceUnavailable (const Jid &, const Resource &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( messageReceived (const Message &) ), + this, SLOT ( slotReceivedMessage (const Message &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( groupChatJoined (const Jid &) ), + this, SLOT ( slotGroupChatJoined (const Jid &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( groupChatLeft (const Jid &) ), + this, SLOT ( slotGroupChatLeft (const Jid &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( groupChatPresence (const Jid &, const Status &) ), + this, SLOT ( slotGroupChatPresence (const Jid &, const Status &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( groupChatError (const Jid &, int, const QString &) ), + this, SLOT ( slotGroupChatError (const Jid &, int, const QString &) ) ); + //QObject::connect ( d->jabberClient, SIGNAL (debugText (const QString &) ), + // this, SLOT ( slotPsiDebug (const QString &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( xmlIncoming(const QString& ) ), + this, SLOT ( slotIncomingXML (const QString &) ) ); + QObject::connect ( d->jabberClient, SIGNAL ( xmlOutgoing(const QString& ) ), + this, SLOT ( slotOutgoingXML (const QString &) ) ); + } + + d->jabberClient->setClientName ( clientName () ); + d->jabberClient->setClientVersion ( clientVersion () ); + d->jabberClient->setOSName ( osName () ); + + // Set caps information + d->jabberClient->setCapsNode( capsNode() ); + d->jabberClient->setCapsVersion( capsVersion() ); + + // Set Disco Identity + d->jabberClient->setIdentity( discoIdentity() ); + + d->jabberClient->setTimeZone ( timeZoneName (), timeZoneOffset () ); + + d->jabberClient->connectToServer ( d->jabberClientStream, jid, auth ); + + return Ok; + +} + +void JabberClient::disconnect () +{ + + if ( d->jabberClient ) + { + d->jabberClient->close (); + } + else + { + cleanUp (); + } + +} + +void JabberClient::disconnect( XMPP::Status &reason ) +{ + if ( d->jabberClient ) + { + if ( d->jabberClientStream->isActive() ) + { + XMPP::JT_Presence *pres = new JT_Presence(rootTask()); + reason.setIsAvailable( false ); + pres->pres( reason ); + pres->go(); + + d->jabberClientStream->close(); + d->jabberClient->close(); + } + } + else + { + cleanUp(); + } +} + +bool JabberClient::isConnected () const +{ + + if ( d->jabberClient ) + { + return d->jabberClient->isActive (); + } + + return false; + +} + +void JabberClient::joinGroupChat ( const QString &host, const QString &room, const QString &nick ) +{ + + client()->groupChatJoin ( host, room, nick ); + +} + +void JabberClient::joinGroupChat ( const QString &host, const QString &room, const QString &nick, const QString &password ) +{ + + client()->groupChatJoin ( host, room, nick, password ); + +} + +void JabberClient::leaveGroupChat ( const QString &host, const QString &room ) +{ + + client()->groupChatLeave ( host, room ); + +} + +void JabberClient::setGroupChatStatus( const QString & host, const QString & room, const XMPP::Status & status ) +{ + client()->groupChatSetStatus( host, room, status); +} + +void JabberClient::changeGroupChatNick( const QString & host, const QString & room, const QString & nick, const XMPP::Status & status ) +{ + client()->groupChatChangeNick( host, room, nick, status ); +} + + +void JabberClient::sendMessage ( const XMPP::Message &message ) +{ + + client()->sendMessage ( message ); + +} + +void JabberClient::send ( const QString &packet ) +{ + + client()->send ( packet ); + +} + +void JabberClient::requestRoster () +{ + + client()->rosterRequest (); + +} + +void JabberClient::slotPsiDebug ( const QString & _msg ) +{ + QString msg = _msg; + + msg = msg.replace( QRegExp( "[^<]*\n" ), "[Filtered]\n" ); + msg = msg.replace( QRegExp( "[^<]*\n" ), "[Filtered]\n" ); + + emit debugMessage ( "Psi: " + msg ); + +} + +void JabberClient::slotIncomingXML ( const QString & _msg ) +{ + QString msg = _msg; + + msg = msg.replace( QRegExp( "[^<]*\n" ), "[Filtered]\n" ); + msg = msg.replace( QRegExp( "[^<]*\n" ), "[Filtered]\n" ); + + emit debugMessage ( "XML IN: " + msg ); + +} + +void JabberClient::slotOutgoingXML ( const QString & _msg ) +{ + QString msg = _msg; + + msg = msg.replace( QRegExp( "[^<]*\n" ), "[Filtered]\n" ); + msg = msg.replace( QRegExp( "[^<]*\n" ), "[Filtered]\n" ); + + emit debugMessage ( "XML OUT: " + msg ); + +} + +void JabberClient::slotTLSHandshaken () +{ + + emit debugMessage ( "TLS handshake done, testing certificate validity..." ); + + // FIXME: in the future, this should be handled by KDE, not QCA + int validityResult = d->jabberTLS->certificateValidityResult (); + + if ( validityResult == QCA::TLS::Valid ) + { + emit debugMessage ( "Certificate is valid, continuing." ); + + // valid certificate, continue + d->jabberTLSHandler->continueAfterHandshake (); + } + else + { + emit debugMessage ( "Certificate is not valid, asking user what to do next." ); + + // certificate is not valid, query the user + if ( ignoreTLSWarnings () ) + { + emit debugMessage ( "We are supposed to ignore TLS warnings, continuing." ); + d->jabberTLSHandler->continueAfterHandshake (); + } + + emit tlsWarning ( validityResult ); + } + +} + +void JabberClient::continueAfterTLSWarning () +{ + + if ( d->jabberTLSHandler ) + { + d->jabberTLSHandler->continueAfterHandshake (); + } + +} + +void JabberClient::slotCSNeedAuthParams ( bool user, bool pass, bool realm ) +{ + emit debugMessage ( "Sending auth credentials..." ); + + if ( user ) + { + d->jabberClientStream->setUsername ( jid().node () ); + } + + if ( pass ) + { + d->jabberClientStream->setPassword ( d->password ); + } + + if ( realm ) + { + d->jabberClientStream->setRealm ( jid().domain () ); + } + + d->jabberClientStream->continueAfterParams (); + +} + +void JabberClient::slotCSAuthenticated () +{ + emit debugMessage ( "Connected to Jabber server." ); + + /* + * Determine local IP address. + * FIXME: This is ugly! + */ + if ( localAddress().isEmpty () ) + { + // code for Iris-type bytestreams + ByteStream *irisByteStream = d->jabberClientConnector->stream(); + if ( irisByteStream->inherits ( "BSocket" ) || irisByteStream->inherits ( "XMPP::BSocket" ) ) + { + d->localAddress = ( (BSocket *)irisByteStream )->address().toString (); + } + + // code for the KDE-type bytestream + JabberByteStream *kdeByteStream = dynamic_cast(d->jabberClientConnector->stream()); + if ( kdeByteStream ) + { + d->localAddress = kdeByteStream->socket()->localAddress().nodeName (); + } + } + + if ( fileTransfersEnabled () ) + { + // setup file transfer + addS5BServerAddress ( localAddress () ); + d->jabberClient->s5bManager()->setServer ( s5bServer () ); + } + + // start the client operation + d->jabberClient->start ( jid().domain (), jid().node (), d->password, jid().resource () ); + + emit connected (); +} + +void JabberClient::slotCSDisconnected () +{ + + /* FIXME: + * We should delete the XMPP::Client instance here, + * but timers etc prevent us from doing so. (Psi does + * not like to be deleted from a slot). + */ + + emit debugMessage ( "Disconnected, freeing up file transfer port..." ); + + // delete local address from S5B server + removeS5BServerAddress ( localAddress () ); + + emit csDisconnected (); + +} + +void JabberClient::slotCSWarning ( int warning ) +{ + + emit debugMessage ( "Client stream warning." ); + + /* + * FIXME: process all other warnings + */ + switch ( warning ) + { + //case XMPP::ClientStream::WarnOldVersion: + case XMPP::ClientStream::WarnNoTLS: + if ( forceTLS () ) + { + disconnect (); + emit error ( NoTLS ); + return; + } + break; + } + + d->jabberClientStream->continueAfterWarning (); + +} + +void JabberClient::slotCSError ( int error ) +{ + + emit debugMessage ( "Client stream error." ); + + emit csError ( error ); + +} + +void JabberClient::slotRosterRequestFinished ( bool success, int /*statusCode*/, const QString &/*statusString*/ ) +{ + + emit rosterRequestFinished ( success ); + +} + +void JabberClient::slotIncomingFileTransfer () +{ + + emit incomingFileTransfer (); + +} + +void JabberClient::slotNewContact ( const XMPP::RosterItem &item ) +{ + + emit newContact ( item ); + +} + +void JabberClient::slotContactDeleted ( const RosterItem &item ) +{ + + emit contactDeleted ( item ); + +} + +void JabberClient::slotContactUpdated ( const RosterItem &item ) +{ + + emit contactUpdated ( item ); + +} + +void JabberClient::slotResourceAvailable ( const Jid &jid, const Resource &resource ) +{ + + emit resourceAvailable ( jid, resource ); + +} + +void JabberClient::slotResourceUnavailable ( const Jid &jid, const Resource &resource ) +{ + + emit resourceUnavailable ( jid, resource ); + +} + +void JabberClient::slotReceivedMessage ( const Message &message ) +{ + + emit messageReceived ( message ); + +} + +void JabberClient::slotGroupChatJoined ( const Jid &jid ) +{ + + emit groupChatJoined ( jid ); + +} + +void JabberClient::slotGroupChatLeft ( const Jid &jid ) +{ + + emit groupChatLeft ( jid ); + +} + +void JabberClient::slotGroupChatPresence ( const Jid &jid, const Status &status) +{ + + emit groupChatPresence ( jid, status ); + +} + +void JabberClient::slotGroupChatError ( const Jid &jid, int error, const QString &reason) +{ + + emit groupChatError ( jid, error, reason ); + +} + +void JabberClient::slotSubscription ( const Jid &jid, const QString &type ) +{ + + emit subscription ( jid, type ); + +} + + +#include "jabberclient.moc" diff --git a/kopete/protocols/jabber/jabberclient.h b/kopete/protocols/jabber/jabberclient.h new file mode 100644 index 00000000..7cd33e02 --- /dev/null +++ b/kopete/protocols/jabber/jabberclient.h @@ -0,0 +1,600 @@ + +/*************************************************************************** + jabberclient.h - Generic Jabber Client Class + ------------------- + begin : Sat May 25 2005 + copyright : (C) 2005 by Till Gerken + (C) 2006 by Michaël Larouche + + Kopete (C) 2001-2006 Kopete developers + . + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef JABBERCLIENT_H +#define JABBERCLIENT_H + +#include + +// include these because of namespace reasons +#include +#include +#include + +using namespace XMPP; + +class JabberConnector; + +/** + * This class provides an interface to the Iris subsystem. The goal is to + * abstract the Iris layer and manage it via a single, simple to use class. + * By default, @ref JabberClient will attempt to establish a connection + * using XMPP 1.0. This means that apart from the JID and password, no + * further details are necessary to connect. The server and port will be + * determined using a SRV lookup. If TLS is possible (meaning, the TLS + * plugin is available and the server supports TLS) it will automatically + * be used. Otherwise, a non-encrypted connection will be established. + * If XMPP 1.0 is not possible, the connection will fall back to the old + * protocol. By default, this connection is not encrypted. You can, however, + * use @ref setUseSSL to immediately attempt an SSL connection. This is + * most useful if you want to establish an SSL connection to a non-standard + * port, in which case you will also have to use @ref setOverrideHost. In case + * XMPP 1.0 does not work, an automatic attempt to connect to the standard port + * 5223 with SSL can be made with @ref setProbeSSL. If the attempt is not + * sucessful, the connection will fall back to an unencrypted attempt + * at port 5222. + * @brief Provides a Jabber client + * @author Till Gerken + */ +class JabberClient : public QObject +{ + +Q_OBJECT + +public: + /** + * Error codes indicating problems during operation. + */ + enum ErrorCode + { + Ok, /** No error. */ + InvalidPassword, /** Password used to connect to the server was incorrect. */ + AlreadyConnected, /** A new connection was attempted while the previous one hasn't been closed. */ + NoTLS, /** Use of TLS has been forced (see @ref forceTLS) but TLS is not available, either server- or client-side. */ + InvalidPasswordForMUC = 401, /** A password is require to enter on this Multi-User Chat */ + NicknameConflict = 409, /** There is already someone with that nick connected to the Multi-User Chat */ + BannedFromThisMUC = 403, /** You can't join this Multi-User Chat because you were bannished */ + MaxUsersReachedForThisMuc = 503 /** You can't join this Multi-User Chat because it is full */ + }; + + JabberClient(); + ~JabberClient(); + + /** + * Connect to a Jabber server. + * @param jid JID to connect to. + * @param password Password to authenticate with. + * @param auth True if authentication should be done, false if not. + */ + ErrorCode connect ( const XMPP::Jid &jid, const QString &password, bool auth = true ); + + /** + * Disconnect from Jabber server. + */ + void disconnect (); + + /** + * Disconnect from Jabber server with reason + * @param reason The reason for disconnecting + */ + void disconnect (XMPP::Status &reason); + + /** + * Returns if this instance is connected to a server. + */ + bool isConnected () const; + + /** + * Returns the JID associated with this instance. + */ + XMPP::Jid jid () const; + + /** + * Set flag to ignore TLS warnings. If TLS + * warnings are not ignored, the class will emit + * @ref tlsWarning and wait for the user to + * call @ref continueAfterTLSWarning or + * @ref disconnect. Default is false. + */ + void setIgnoreTLSWarnings ( bool flag ); + /** + * Return if TLS warnings are being ignored. + */ + bool ignoreTLSWarnings (); + + /** + * Continue after a @ref tlsWarning signal. + */ + void continueAfterTLSWarning (); + + /** + * Set the port on which the S5B server should listen. + * This is only taken into account if @ref setFileTransfersEnabled + * is set to true. + * @return True if port could be bound, false if not. + */ + bool setS5BServerPort ( int port ); + /** + * Returns the port the S5B server listens on. + */ + int s5bServerPort () const; + + /** + * Force the use of TLS. If TLS connections are forced, + * unencrypted connections will not be established. + * Default is false. + */ + void setForceTLS ( bool flag ); + /** + * Returns if TLS connections are forced. + */ + bool forceTLS () const; + + /** + * Force direct SSL connection, also for the + * handshake. This is only useful if you know + * the server supports it or you want to use + * a non-standard port, in which case @ref setOverrideHost + * will be useful. Default is false. + */ + void setUseSSL ( bool flag ); + /** + * Returns if an SSL connection attempt should be made. + */ + bool useSSL () const; + + /** + * Use only the old protocol (pre-XMPP 1.0). This should only + * be used with servers not supporting XMPP 1.0 or with servers + * that have a broken login procedure. Default is false. If + * a connection attempt is not possible, Iris will automatically + * fall back to the old protocol. + */ + void setUseXMPP09 ( bool flag ); + /** + * Returns if the old protocol should be used. + */ + bool useXMPP09 () const; + + /** + * Probe port 5223 if an SSL connection is possible. If + * a connection is not possible, an unencrypted connection + * will be attempted at port 5222. This is only meaningful + * if @ref useXMPP09 is true. Default is false. + */ + void setProbeSSL ( bool flag ); + /** + * Returns if SSL support will be probed. + */ + bool probeSSL () const; + + /** + * Override the name and port of the server to connect to. + * This only has an effect if the old protocol (@ref useXMPP09) + * has been enabled. Default is false. + */ + void setOverrideHost ( bool flag, const QString &server = "", int port = 5222 ); + /** + * Returns if the server name and port are overridden. + */ + bool overrideHost () const; + + /** + * Allow the transmission of a plain text password. If digested + * passwords are supported by the server, they will still be preferred. + * Defaults to true. + */ + void setAllowPlainTextPassword ( bool flag ); + /** + * Returns if plain text passwords are allowed. + */ + bool allowPlainTextPassword () const; + + /** + * Enable file transfers. Default is false. + * @param flag Whether to enable file transfers. + * @param localAddress Local address to receive file transfers at. Will be determined automatically if not specified. + */ + void setFileTransfersEnabled ( bool flag, const QString &localAddress = QString::null ); + + /** + * Returns the address of the local interface. + */ + QString localAddress () const; + + /** + * Returns if file transfers are enabled. + */ + bool fileTransfersEnabled () const; + + /** + * Set client name. + */ + void setClientName ( const QString &clientName ); + /** + * Return client name. + */ + QString clientName () const; + + /** + * Set client version. + */ + void setClientVersion ( const QString &clientVersion ); + /** + * Return client version. + */ + QString clientVersion () const; + + /** + * Set operating system name. + */ + void setOSName ( const QString &osName ); + /** + * Return operating system name. + */ + QString osName () const; + + /** + * Set the caps(JEP-0115: Entity capabilities) node name. + * @param node Node name. + */ + void setCapsNode( const QString &capsNode ); + /** + * Return the caps node name for this client. + * @return the caps node name. + */ + QString capsNode() const; + + /** + * Set the caps(JEP-0115: Entity capabilities) node version. + * @param capsVersion the node version. + */ + void setCapsVersion( const QString &capsVersion ); + /** + * Return the caps version for this client. + * @return the caps version. + */ + QString capsVersion() const; + + /** + * Return the caps extension list for this client. + * @return A string containing all extensions separated by space. + */ + QString capsExt() const; + + /** + * Set the disco Identity information for this client. + * Create a Disco identity like this: + * @code + * DiscoItem::Identity identity; + * identity.category = "client"; + * identity.type = "pc"; + * identity.name = "Kopete"; + * @endcode + * + * @param identity DiscoItem::Identity for the client. + */ + void setDiscoIdentity(DiscoItem::Identity identity); + /** + * Get the disco Identity information for this client. + * @return the DiscoItem::Identity for this client. + */ + DiscoItem::Identity discoIdentity() const; + + /** + * Set timezone information. Default is UTC. + */ + void setTimeZone ( const QString &timeZoneName, int timeZoneOffset ); + /** + * Return timezone name. + */ + QString timeZoneName () const; + /** + * Return timezone offset. + */ + int timeZoneOffset () const; + + /** + * This method can be used to implement a penalty + * system when a lot of queries need to be sent to the + * server. Using the time returned by this method, + * the caller can determine a delay until the next + * operation in the queue can be carried out. + * @brief Return current penalty time in seconds. + */ + int getPenaltyTime (); + + /** + * Return the XMPP client instance. + */ + XMPP::Client *client () const; + + /** + * Return client stream instance. + */ + XMPP::ClientStream *clientStream () const; + + /** + * Return client connector instance. + */ + JabberConnector *clientConnector () const; + + /** + * Get the root task for this connection. + * You need this instance for every task + * you want to start. + */ + XMPP::Task *rootTask () const; + + /** + * Returns the file transfer manager + * instance that deals with current file + * transfers. + */ + XMPP::FileTransferManager *fileTransferManager () const; + + /** + * Join a group chat. + * @param host Node to join the room at. + * @param room Name of room to join. + * @param nick Nick name you want to join with. + */ + void joinGroupChat ( const QString &host, const QString &room, const QString &nick ); + + /** + * Join a group chat that require a password. + * @param host Node to join the room at. + * @param room Name of room to join. + * @param nick Nick name you want to join with. + * @param password The password to join the room. + */ + void joinGroupChat ( const QString &host, const QString &room, const QString &nick, const QString &password ); + + /** + * Leave a group chat. + * @param host Node to leave room at. + * @param room Name of room to leave. + */ + void leaveGroupChat ( const QString &host, const QString &room ); + + /** + * change the status of a group chat + */ + void setGroupChatStatus(const QString &host, const QString &room, const XMPP::Status &); + /** + * change the nick in a group chat + */ + void changeGroupChatNick(const QString &host, const QString &room, const QString &nick, const XMPP::Status &status =XMPP::Status()); + + /** + * Send a message. + */ + void sendMessage ( const XMPP::Message &message ); + + /** + * Send raw packet to the server. + */ + void send ( const QString &packet ); + + /** + * Request the roster from the Jabber server. + */ + void requestRoster (); + +signals: + /** + * Connected successfully. + */ + void connected (); + + /** + * Client stream authenticated. This + * signal is emitted when the socket + * connection has been successfully + * established, before sending the login + * packet. + */ + void csAuthenticated (); + + /** + * Client stream error. + */ + void csError ( int error ); + + /** + * Client stream was disconnected. + */ + void csDisconnected (); + + /** + * TLS problem encountered. + */ + void tlsWarning ( int validityResult ); + + /** + * A new file transfer needs to be handled. + * The file transfer can be dealt with by + * querying the file transfer manager from + * @ref client. + */ + void incomingFileTransfer (); + + /** + * Fatal error has been encountered, + * further operations are not possible. + */ + void error ( JabberClient::ErrorCode code ); + + /** + * Roster has been transmitted and processed. + */ + void rosterRequestFinished ( bool success ); + + /** + * A new contact appeared on the roster. + */ + void newContact ( const XMPP::RosterItem &item ); + + /** + * A contact has been removed from the roster. + */ + void contactDeleted ( const XMPP::RosterItem &item ); + + /** + * A roster item has changed. + */ + void contactUpdated ( const XMPP::RosterItem &item ); + + /** + * New resource is available for a contact. + */ + void resourceAvailable ( const XMPP::Jid &jid, const XMPP::Resource &resource ); + + /** + * An existing resource has been removed. + */ + void resourceUnavailable ( const XMPP::Jid &jid, const XMPP::Resource &resource ); + + /** + * A new message has been received. + */ + void messageReceived ( const XMPP::Message &message ); + + /** + * Group chat has been joined. + */ + void groupChatJoined ( const XMPP::Jid &jid ); + + /** + * Group chat has been left. + */ + void groupChatLeft ( const XMPP::Jid &jid ); + + /** + * A presence to a group chat has been signalled. + */ + void groupChatPresence ( const XMPP::Jid &jid, const XMPP::Status &status ); + + /** + * An error was encountered joining or processing a group chat. + */ + void groupChatError ( const XMPP::Jid &jid, int error, const QString &reason ); + + /** + * New subscription request. + */ + void subscription ( const XMPP::Jid &jid, const QString &type ); + + /** + * Dispatches a debug message. Debug messages + * include incoming and outgoing XML packets + * as well as internal status messages. + */ + void debugMessage ( const QString &message ); + +private: + class Private; + Private *d; + + /** + * Delete all member classes and reset the class to a predefined state. + */ + void cleanUp (); + + /** + * Return current instance of the S5B server. + */ + XMPP::S5BServer *s5bServer (); + /** + * Add an address that the S5B server should handle. + */ + void addS5BServerAddress ( const QString &address ); + /** + * Remove an address that the S5B server currently handles. + */ + void removeS5BServerAddress ( const QString &address ); + +private slots: + /* S5B server object has been destroyed. */ + void slotS5BServerGone (); + + /* update the penalty timer */ + void slotUpdatePenaltyTime (); + + /* Login if the connection was OK. */ + void slotCSNeedAuthParams (bool user, bool pass, bool realm); + + /* Called from Psi: tells us when we're logged in OK. */ + void slotCSAuthenticated (); + + /* Called from Psi: tells us when we've been disconnected from the server. */ + void slotCSDisconnected (); + + /* Called from Psi: alerts us to a protocol warning. */ + void slotCSWarning (int); + + /* Called from Psi: alerts us to a protocol error. */ + void slotCSError (int); + + /* Called from Psi: report certificate status */ + void slotTLSHandshaken (); + + /* Called from Psi: roster request finished */ + void slotRosterRequestFinished ( bool success, int statusCode, const QString &statusString ); + + /* Called from Psi: incoming file transfer */ + void slotIncomingFileTransfer (); + + /* A new item appeared in our roster */ + void slotNewContact (const RosterItem &); + + /* An item has been deleted from our roster. */ + void slotContactDeleted (const RosterItem &); + + /* Update a contact's details. */ + void slotContactUpdated (const RosterItem &); + + /* Someone on our contact list had (another) resource come online. */ + void slotResourceAvailable (const Jid &, const Resource &); + + /* Someone on our contact list had a resource go offline. */ + void slotResourceUnavailable (const Jid &, const Resource &); + + /* Incoming message. */ + void slotReceivedMessage (const Message &); + + /* Called from Psi: debug messages from the backend. */ + void slotPsiDebug (const QString & msg); + void slotIncomingXML (const QString &msg); + void slotOutgoingXML (const QString &msg); + + /* Slots for handling group chats. */ + void slotGroupChatJoined (const Jid & jid); + void slotGroupChatLeft (const Jid & jid); + void slotGroupChatPresence (const Jid & jid, const Status & status); + void slotGroupChatError (const Jid & jid, int error, const QString & reason); + + /* Incoming subscription request. */ + void slotSubscription (const Jid & jid, const QString & type); + +}; + +#endif diff --git a/kopete/protocols/jabber/jabberconnector.cpp b/kopete/protocols/jabber/jabberconnector.cpp new file mode 100644 index 00000000..2359dd69 --- /dev/null +++ b/kopete/protocols/jabber/jabberconnector.cpp @@ -0,0 +1,132 @@ + +/*************************************************************************** + jabberconnector.cpp - Socket Connector for Jabber + ------------------- + begin : Wed Jul 7 2004 + copyright : (C) 2004 by Till Gerken + + Kopete (C) 2004 Kopete developers + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation; either version 2.1 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +#include +#include "jabberconnector.h" +#include "jabberbytestream.h" +#include "jabberprotocol.h" + +JabberConnector::JabberConnector ( QObject *parent, const char */*name*/ ) + : XMPP::Connector ( parent ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "New Jabber connector." << endl; + + mErrorCode = KNetwork::KSocketBase::NoError; + + mByteStream = new JabberByteStream ( this ); + + connect ( mByteStream, SIGNAL ( connected () ), this, SLOT ( slotConnected () ) ); + connect ( mByteStream, SIGNAL ( error ( int ) ), this, SLOT ( slotError ( int ) ) ); + +} + +JabberConnector::~JabberConnector () +{ + + delete mByteStream; + +} + +void JabberConnector::connectToServer ( const QString &server ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Initiating connection to " << server << endl; + + /* + * FIXME: we should use a SRV lookup to determine the + * actual server to connect to. As this is currently + * not supported yet, we're using setOptHostPort(). + * For XMPP 1.0, we need to enable this! + */ + + mErrorCode = KNetwork::KSocketBase::NoError; + + if ( !mByteStream->connect ( mHost, QString::number ( mPort ) ) ) + { + // Houston, we have a problem + mErrorCode = mByteStream->socket()->error (); + emit error (); + } + +} + +void JabberConnector::slotConnected () +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "We are connected." << endl; + + // FIXME: setPeerAddress() is something different, find out correct usage later + //KInetSocketAddress inetAddress = mStreamSocket->address().asInet().makeIPv6 (); + //setPeerAddress ( QHostAddress ( inetAddress.ipAddress().addr () ), inetAddress.port () ); + + emit connected (); + +} + +void JabberConnector::slotError ( int code ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Error detected: " << code << endl; + + mErrorCode = code; + emit error (); + +} + +int JabberConnector::errorCode () +{ + + return mErrorCode; + +} + +ByteStream *JabberConnector::stream () const +{ + + return mByteStream; + +} + +void JabberConnector::done () +{ + + mByteStream->close (); + +} + +void JabberConnector::setOptHostPort ( const QString &host, Q_UINT16 port ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Manually specifying host " << host << " and port " << port << endl; + + mHost = host; + mPort = port; + +} + +void JabberConnector::setOptSSL ( bool ssl ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Setting SSL to " << ssl << endl; + + setUseSSL ( ssl ); + +} + +void JabberConnector::setOptProbe ( bool ) +{ + // FIXME: Implement this. +} + +#include "jabberconnector.moc" diff --git a/kopete/protocols/jabber/jabberconnector.h b/kopete/protocols/jabber/jabberconnector.h new file mode 100644 index 00000000..6fbc4167 --- /dev/null +++ b/kopete/protocols/jabber/jabberconnector.h @@ -0,0 +1,65 @@ + +/*************************************************************************** + jabberconnector.cpp - Socket Connector for Jabber + ------------------- + begin : Wed Jul 7 2004 + copyright : (C) 2004 by Till Gerken + + Kopete (C) 2004 Kopete developers + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation; either version 2.1 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef JABBERCONNECTOR_H +#define JABBERCONNECTOR_H + +#include +#include "jabberbytestream.h" + +class ByteStream; +class KResolverEntry; + +/** +@author Till Gerken +*/ +class JabberConnector : public XMPP::Connector +{ + +Q_OBJECT + +public: + JabberConnector ( QObject *parent = 0, const char *name = 0 ); + + ~JabberConnector (); + + void connectToServer ( const QString &server ); + ByteStream *stream () const; + void done (); + + void setOptHostPort ( const QString &host, Q_UINT16 port ); + void setOptSSL ( bool ); + void setOptProbe ( bool ); + + int errorCode (); + +private slots: + void slotConnected (); + void slotError ( int ); + +private: + QString mHost; + Q_UINT16 mPort; + int mErrorCode; + + JabberByteStream *mByteStream; + +}; + +#endif diff --git a/kopete/protocols/jabber/jabbercontact.cpp b/kopete/protocols/jabber/jabbercontact.cpp new file mode 100644 index 00000000..c8589e1e --- /dev/null +++ b/kopete/protocols/jabber/jabbercontact.cpp @@ -0,0 +1,1328 @@ + /* + * jabbercontact.cpp - Regular Kopete Jabber protocol contact + * + * Copyright (c) 2002-2004 by Till Gerken + * Copyright (c) 2006 by Olivier Goffart + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * * + * ************************************************************************* + */ + +#include "jabbercontact.h" + +#include "xmpp_tasks.h" +#include "im.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kopetecontactlist.h" +#include "kopetegroup.h" +#include "kopeteuiglobal.h" +#include "kopetechatsessionmanager.h" +#include "kopeteaccountmanager.h" +#include "jabberprotocol.h" +#include "jabberaccount.h" +#include "jabberclient.h" +#include "jabberchatsession.h" +#include "jabberresource.h" +#include "jabberresourcepool.h" +#include "jabberfiletransfer.h" +#include "jabbertransport.h" +#include "dlgjabbervcard.h" + +#ifdef SUPPORT_JINGLE +// #include "jinglesessionmanager.h" +// #include "jinglevoicesession.h" +#include "jinglevoicesessiondialog.h" +#endif + +/** + * JabberContact constructor + */ +JabberContact::JabberContact (const XMPP::RosterItem &rosterItem, Kopete::Account *_account, Kopete::MetaContact * mc, const QString &legacyId) + : JabberBaseContact ( rosterItem, _account, mc, legacyId) , mDiscoDone(false), m_syncTimer(0L) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << contactId() << " is created - " << this << endl; + // this contact is able to transfer files + setFileCapable ( true ); + + /* + * Catch when we're going online for the first time to + * update our properties from a vCard. (properties are + * not available during startup, so we need to read + * them later - this also serves as a random update + * feature) + * Note: The only time account->myself() could be a + * NULL pointer is if this contact here is the myself() + * instance itself. Since in that case we wouldn't + * get updates at all, we need to treat that as a + * special case. + */ + + mVCardUpdateInProgress = false; + + if ( !account()->myself () ) + { + // this contact is a regular contact + connect ( this, + SIGNAL ( onlineStatusChanged ( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + this, SLOT ( slotCheckVCard () ) ); + } + else + { + // this contact is the myself instance + connect ( account()->myself (), + SIGNAL ( onlineStatusChanged ( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + this, SLOT ( slotCheckVCard () ) ); + + connect ( account()->myself (), + SIGNAL ( onlineStatusChanged ( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), + this, SLOT ( slotCheckLastActivity ( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ) ); + + /* + * Trigger update once if we're already connected for contacts + * that are being added while we are online. + */ + if ( account()->myself()->onlineStatus().isDefinitelyOnline() ) + { + slotGetTimedVCard (); + } + } + + mRequestOfflineEvent = false; + mRequestDisplayedEvent = false; + mRequestDeliveredEvent = false; + mRequestComposingEvent = false; + mRequestGoneEvent = false; +} + + + +JabberContact::~JabberContact() +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << contactId() << " is destroyed - " << this << endl; +} + +QPtrList *JabberContact::customContextMenuActions () +{ + + QPtrList *actionCollection = new QPtrList(); + + KActionMenu *actionAuthorization = new KActionMenu ( i18n ("Authorization"), "connect_established", this, "jabber_authorization"); + + KAction *resendAuthAction, *requestAuthAction, *removeAuthAction; + + resendAuthAction = new KAction (i18n ("(Re)send Authorization To"), "mail_forward", 0, + this, SLOT (slotSendAuth ()), actionAuthorization, "actionSendAuth"); + resendAuthAction->setEnabled( mRosterItem.subscription().type() == XMPP::Subscription::To || mRosterItem.subscription().type() == XMPP::Subscription::None ); + actionAuthorization->insert(resendAuthAction); + + requestAuthAction = new KAction (i18n ("(Re)request Authorization From"), "mail_reply", 0, + this, SLOT (slotRequestAuth ()), actionAuthorization, "actionRequestAuth"); + requestAuthAction->setEnabled( mRosterItem.subscription().type() == XMPP::Subscription::From || mRosterItem.subscription().type() == XMPP::Subscription::None ); + actionAuthorization->insert(requestAuthAction); + + removeAuthAction = new KAction (i18n ("Remove Authorization From"), "mail_delete", 0, + this, SLOT (slotRemoveAuth ()), actionAuthorization, "actionRemoveAuth"); + removeAuthAction->setEnabled( mRosterItem.subscription().type() == XMPP::Subscription::Both || mRosterItem.subscription().type() == XMPP::Subscription::From ); + actionAuthorization->insert(removeAuthAction); + + KActionMenu *actionSetAvailability = new KActionMenu (i18n ("Set Availability"), "kopeteavailable", this, "jabber_online"); + + actionSetAvailability->insert(new KAction (i18n ("Online"), protocol()->JabberKOSOnline.iconFor(this), + 0, this, SLOT (slotStatusOnline ()), actionSetAvailability, "actionOnline")); + actionSetAvailability->insert(new KAction (i18n ("Free to Chat"), protocol()->JabberKOSChatty.iconFor(this), + 0, this, SLOT (slotStatusChatty ()), actionSetAvailability, "actionChatty")); + actionSetAvailability->insert(new KAction (i18n ("Away"), protocol()->JabberKOSAway.iconFor(this), + 0, this, SLOT (slotStatusAway ()), actionSetAvailability, "actionAway")); + actionSetAvailability->insert(new KAction (i18n ("Extended Away"), protocol()->JabberKOSXA.iconFor(this), + 0, this, SLOT (slotStatusXA ()), actionSetAvailability, "actionXA")); + actionSetAvailability->insert(new KAction (i18n ("Do Not Disturb"), protocol()->JabberKOSDND.iconFor(this), + 0, this, SLOT (slotStatusDND ()), actionSetAvailability, "actionDND")); + actionSetAvailability->insert(new KAction (i18n ("Invisible"), protocol()->JabberKOSInvisible.iconFor(this), + 0, this, SLOT (slotStatusInvisible ()), actionSetAvailability, "actionInvisible")); + + KActionMenu *actionSelectResource = new KActionMenu (i18n ("Select Resource"), "connect_no", this, "actionSelectResource"); + + // if the contact is online, display the resources we have for it, + // otherwise disable the menu + if (onlineStatus ().status () == Kopete::OnlineStatus::Offline) + { + actionSelectResource->setEnabled ( false ); + } + else + { + QStringList items; + XMPP::ResourceList availableResources; + + int activeItem = 0, i = 1; + const XMPP::Resource lockedResource = account()->resourcePool()->lockedResource ( mRosterItem.jid () ); + + // put default resource first + items.append (i18n ("Automatic (best/default resource)")); + + account()->resourcePool()->findResources ( mRosterItem.jid (), availableResources ); + + XMPP::ResourceList::const_iterator resourcesEnd = availableResources.end (); + for ( XMPP::ResourceList::const_iterator it = availableResources.begin(); it != resourcesEnd; ++it, i++) + { + items.append ( (*it).name() ); + + if ( (*it).name() == lockedResource.name() ) + activeItem = i; + } + + // now go through the string list and add the resources with their icons + i = 0; + QStringList::const_iterator itemsEnd = items.end (); + for(QStringList::const_iterator it = items.begin(); it != itemsEnd; ++it) + { + if( i == activeItem ) + { + actionSelectResource->insert ( new KAction( ( *it ), "button_ok", 0, this, SLOT( slotSelectResource() ), + actionSelectResource, QString::number( i ).latin1() ) ); + } + else + { + /* + * Select icon, using bestResource() without lock for the automatic entry + * and the resources' respective status icons for the rest. + */ + QIconSet iconSet ( !i ? + protocol()->resourceToKOS ( account()->resourcePool()->bestResource ( mRosterItem.jid(), false ) ).iconFor ( account () ) : protocol()->resourceToKOS ( *availableResources.find(*it) ).iconFor ( account () )); + + actionSelectResource->insert ( new KAction( ( *it ), iconSet, 0, this, SLOT( slotSelectResource() ), + actionSelectResource, QString::number( i ).latin1() ) ); + } + + i++; + } + + } + + actionCollection->append( actionAuthorization ); + actionCollection->append( actionSetAvailability ); + actionCollection->append( actionSelectResource ); + + +#ifdef SUPPORT_JINGLE + KAction *actionVoiceCall = new KAction (i18n ("Voice call"), "voicecall", 0, this, SLOT (voiceCall ()), this, "jabber_voicecall"); + actionVoiceCall->setEnabled( false ); + + actionCollection->append( actionVoiceCall ); + + // Check if the current contact support Voice calls, also honour lock by default. + JabberResource *bestResource = account()->resourcePool()->bestJabberResource( mRosterItem.jid() ); + if( bestResource && bestResource->features().canVoice() ) + { + actionVoiceCall->setEnabled( true ); + } +#endif + + return actionCollection; +} + +void JabberContact::handleIncomingMessage (const XMPP::Message & message) +{ + QString viewPlugin; + Kopete::Message *newMessage = 0L; + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Received Message Type:" << message.type () << endl; + + // fetch message manager + JabberChatSession *mManager = manager ( message.from().resource (), Kopete::Contact::CanCreate ); + + // evaluate notifications + if ( message.type () != "error" ) + { + if (!message.invite().isEmpty()) + { + QString room=message.invite(); + QString originalBody=message.body().isEmpty() ? QString() : + i18n( "The original message is : \" %1 \"
" ).arg(QStyleSheet::escape(message.body())); + QString mes=i18n("%1 invited you to join the conference %2
%3
" + "If you want to accept and join, just enter your nickname and press ok
" + "If you want to decline, press cancel
") + .arg(message.from().full(), room , originalBody); + + bool ok=false; + QString futureNewNickName = KInputDialog::getText( i18n( "Invited to a conference - Jabber Plugin" ), + mes, QString() , &ok , (mManager ? dynamic_cast(mManager->view(false)) : 0) ); + if ( !ok || !account()->isConnected() || futureNewNickName.isEmpty() ) + return; + + XMPP::Jid roomjid(room); + account()->client()->joinGroupChat( roomjid.host() , roomjid.user() , futureNewNickName ); + return; + } + else if (message.body().isEmpty()) + // Then here could be event notifications + { + if (message.containsEvent ( XMPP::CancelEvent ) ) + mManager->receivedTypingMsg ( this, false ); + else if (message.containsEvent ( XMPP::ComposingEvent ) ) + mManager->receivedTypingMsg ( this, true ); + else if (message.containsEvent ( XMPP::DisplayedEvent ) ) + mManager->receivedEventNotification ( i18n("Message has been displayed") ); + else if (message.containsEvent ( XMPP::DeliveredEvent ) ) + mManager->receivedEventNotification ( i18n("Message has been delivered") ); + else if (message.containsEvent ( XMPP::OfflineEvent ) ) + { + mManager->receivedEventNotification( i18n("Message stored on the server, contact offline") ); + } + else if (message.containsEvent ( XMPP::GoneEvent ) ) + { + if(mManager->view( Kopete::Contact::CannotCreate )) + { //show an internal message if the user has not already closed his window + Kopete::Message m=Kopete::Message ( this, mManager->members(), + i18n("%1 has ended their participation in the chat session.").arg(metaContact()->displayName()), + Kopete::Message::Internal ); + m.setImportance(Kopete::Message::Low); + mManager->view()->appendMessage ( m ); //use KopeteView::AppendMessage to bypass notifications + } + } + } + else + // Then here could be event notification requests + { + mRequestComposingEvent = message.containsEvent ( XMPP::ComposingEvent ); + mRequestOfflineEvent = message.containsEvent ( XMPP::OfflineEvent ); + mRequestDeliveredEvent = message.containsEvent ( XMPP::DeliveredEvent ); + mRequestDisplayedEvent = message.containsEvent ( XMPP::DisplayedEvent); + mRequestGoneEvent= message.containsEvent ( XMPP::GoneEvent); + } + } + + /** + * Don't display empty messages, these were most likely just carrying + * event notifications or other payload. + */ + if ( message.body().isEmpty () && message.urlList().isEmpty () && message.xHTMLBody().isEmpty() && !message.xencrypted() ) + return; + + // determine message type + if (message.type () == "chat") + viewPlugin = "kopete_chatwindow"; + else + viewPlugin = "kopete_emailwindow"; + + Kopete::ContactPtrList contactList; + contactList.append ( account()->myself () ); + + // check for errors + if ( message.type () == "error" ) + { + newMessage = new Kopete::Message( message.timeStamp (), this, contactList, + i18n("Your message could not be delivered: \"%1\", Reason: \"%2\""). + arg ( message.body () ).arg ( message.error().text ), + message.subject(), Kopete::Message::Inbound, Kopete::Message::PlainText, viewPlugin ); + } + else + { + // store message id for outgoing notifications + mLastReceivedMessageId = message.id (); + + // retrieve and reformat body + QString body = message.body (); + QString xHTMLBody; + if( !message.xencrypted().isEmpty () ) + { + body = QString ("-----BEGIN PGP MESSAGE-----\n\n") + message.xencrypted () + QString ("\n-----END PGP MESSAGE-----\n"); + } + else + { + xHTMLBody = message.xHTMLBody (); + } + + // convert XMPP::Message into Kopete::Message + if (!xHTMLBody.isEmpty()) { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Received a xHTML message" << endl; + newMessage = new Kopete::Message ( message.timeStamp (), this, contactList, xHTMLBody, + message.subject (), Kopete::Message::Inbound, + Kopete::Message::RichText, viewPlugin ); + } + else if ( !body.isEmpty () ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Received a plain text message" << endl; + newMessage = new Kopete::Message ( message.timeStamp (), this, contactList, body, + message.subject (), Kopete::Message::Inbound, + Kopete::Message::PlainText, viewPlugin ); + } + } + + // append message to (eventually new) manager and preselect the originating resource + if ( newMessage ) + { + mManager->appendMessage ( *newMessage, message.from().resource () ); + + delete newMessage; + } + + // append URLs as separate messages + if ( !message.urlList().isEmpty () ) + { + /* + * We need to copy it here because Iris returns a copy + * and we can't work with a returned copy in a for() loop. + */ + XMPP::UrlList urlList = message.urlList(); + + for ( XMPP::UrlList::const_iterator it = urlList.begin (); it != urlList.end (); ++it ) + { + QString description = (*it).desc().isEmpty() ? (*it).url() : QStyleSheet::escape ( (*it).desc() ); + QString url = (*it).url (); + + newMessage = new Kopete::Message ( message.timeStamp (), this, contactList, + QString ( "%2" ).arg ( url, description ), + message.subject (), Kopete::Message::Inbound, + Kopete::Message::RichText, viewPlugin ); + + mManager->appendMessage ( *newMessage, message.from().resource () ); + + delete newMessage; + } + } + +} + +void JabberContact::slotCheckVCard () +{ + QDateTime cacheDate; + Kopete::ContactProperty cacheDateString = property ( protocol()->propVCardCacheTimeStamp ); + + // don't do anything while we are offline + if ( !account()->myself()->onlineStatus().isDefinitelyOnline () ) + { + return; + } + + if(!mDiscoDone) + { + if(transport()) //no need to disco if this is a legacy contact + mDiscoDone = true; + else if(!rosterItem().jid().node().isEmpty()) + mDiscoDone = true; //contact with an @ are not transport for sure + else + { + mDiscoDone = true; //or it will happen twice, we don't want that. + //disco to see if it's not a transport + XMPP::JT_DiscoInfo *jt = new XMPP::JT_DiscoInfo(account()->client()->rootTask()); + QObject::connect(jt, SIGNAL(finished()),this, SLOT(slotDiscoFinished())); + jt->get(rosterItem().jid(), QString()); + jt->go(true); + } + } + + + // avoid warning if key does not exist in configuration file + if ( cacheDateString.isNull () ) + cacheDate = QDateTime::currentDateTime().addDays ( -2 ); + else + cacheDate = QDateTime::fromString ( cacheDateString.value().toString (), Qt::ISODate ); + + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Cached vCard data for " << contactId () << " from " << cacheDate.toString () << endl; + + if ( !mVCardUpdateInProgress && ( cacheDate.addDays ( 1 ) < QDateTime::currentDateTime () ) ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Scheduling update." << endl; + + mVCardUpdateInProgress = true; + + // current data is older than 24 hours, request a new one + QTimer::singleShot ( account()->client()->getPenaltyTime () * 1000, this, SLOT ( slotGetTimedVCard () ) ); + } + +} + +void JabberContact::slotGetTimedVCard () +{ + mVCardUpdateInProgress = false; + + // check if we are still connected - eventually we lost our connection in the meantime + if ( !account()->myself()->onlineStatus().isDefinitelyOnline () ) + { + // we are not connected, discard this update + return; + } + + if(!mDiscoDone) + { + if(transport()) //no need to disco if this is a legacy contact + mDiscoDone = true; + else if(!rosterItem().jid().node().isEmpty()) + mDiscoDone = true; //contact with an @ are not transport for sure + else + { + //disco to see if it's not a transport + XMPP::JT_DiscoInfo *jt = new XMPP::JT_DiscoInfo(account()->client()->rootTask()); + QObject::connect(jt, SIGNAL(finished()),this, SLOT(slotDiscoFinished())); + jt->get(rosterItem().jid(), QString()); + jt->go(true); + } + } + + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Requesting vCard for " << contactId () << " from update timer." << endl; + + mVCardUpdateInProgress = true; + + // request vCard + XMPP::JT_VCard *task = new XMPP::JT_VCard ( account()->client()->rootTask () ); + // signal to ourselves when the vCard data arrived + QObject::connect ( task, SIGNAL ( finished () ), this, SLOT ( slotGotVCard () ) ); + task->get ( mRosterItem.jid () ); + task->go ( true ); + +} + +void JabberContact::slotGotVCard () +{ + + XMPP::JT_VCard * vCard = (XMPP::JT_VCard *) sender (); + + // update timestamp of last vCard retrieval + if ( metaContact() && !metaContact()->isTemporary () ) + { + setProperty ( protocol()->propVCardCacheTimeStamp, QDateTime::currentDateTime().toString ( Qt::ISODate ) ); + } + + mVCardUpdateInProgress = false; + + if ( !vCard->success() ) + { + /* + * A vCard for the user does not exist or the + * request was unsuccessful or incomplete. + * The timestamp was already updated when + * requesting the vCard, so it's safe to + * just do nothing here. + */ + return; + } + + setPropertiesFromVCard ( vCard->vcard () ); + +} + +void JabberContact::slotCheckLastActivity ( Kopete::Contact *, const Kopete::OnlineStatus &newStatus, const Kopete::OnlineStatus &oldStatus ) +{ + + /* + * Checking the last activity only makes sense if a contact is offline. + * So, this check should only be done in the following cases: + * - Kopete goes online for the first time and this contact is offline, or + * - Kopete is already online and this contact went offline. + * + * Since Kopete already takes care of maintaining the lastSeen property + * if the contact changes its state while we are online, we don't need + * to query its activity after we are already connected. + */ + + if ( onlineStatus().isDefinitelyOnline () ) + { + // Kopete already deals with lastSeen if the contact is online + return; + } + + if ( ( oldStatus.status () == Kopete::OnlineStatus::Connecting ) && newStatus.isDefinitelyOnline () ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Scheduling request for last activity for " << mRosterItem.jid().bare () << endl; + + QTimer::singleShot ( account()->client()->getPenaltyTime () * 1000, this, SLOT ( slotGetTimedLastActivity () ) ); + } + +} + +void JabberContact::slotGetTimedLastActivity () +{ + /* + * We have been called from @ref slotCheckLastActivity. + * We could have lost our connection in the meantime, + * so make sure we are online. Additionally, the contact + * itself could have gone online, so make sure it is + * still offline. (otherwise the last seen property is + * maintained by Kopete) + */ + + if ( onlineStatus().isDefinitelyOnline () ) + { + // Kopete already deals with setting lastSeen if the contact is online + return; + } + + if ( account()->myself()->onlineStatus().isDefinitelyOnline () ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Requesting last activity from timer for " << mRosterItem.jid().bare () << endl; + + XMPP::JT_GetLastActivity *task = new XMPP::JT_GetLastActivity ( account()->client()->rootTask () ); + QObject::connect ( task, SIGNAL ( finished () ), this, SLOT ( slotGotLastActivity () ) ); + task->get ( mRosterItem.jid () ); + task->go ( true ); + } + +} + +void JabberContact::slotGotLastActivity () +{ + XMPP::JT_GetLastActivity *task = (XMPP::JT_GetLastActivity *) sender (); + + if ( task->success () ) + { + setProperty ( protocol()->propLastSeen, QDateTime::currentDateTime().addSecs ( -task->seconds () ) ); + if( !task->message().isEmpty() ) + { + setProperty( protocol()->propAwayMessage, task->message() ); + } + } + +} + +void JabberContact::slotSendVCard() +{ + XMPP::VCard vCard; + XMPP::VCard::AddressList addressList; + XMPP::VCard::EmailList emailList; + XMPP::VCard::PhoneList phoneList; + + if ( !account()->isConnected () ) + { + account()->errorConnectFirst (); + return; + } + + // General information + vCard.setNickName (property(protocol()->propNickName).value().toString()); + vCard.setFullName (property(protocol()->propFullName).value().toString()); + vCard.setJid (property(protocol()->propJid).value().toString()); + vCard.setBdayStr (property(protocol()->propBirthday).value().toString()); + vCard.setTimezone (property(protocol()->propTimezone).value().toString()); + vCard.setUrl (property(protocol()->propHomepage).value().toString()); + + // home address tab + XMPP::VCard::Address homeAddress; + + homeAddress.home = true; + homeAddress.street = property(protocol()->propHomeStreet).value().toString(); + homeAddress.extaddr = property(protocol()->propHomeExtAddr).value().toString(); + homeAddress.pobox = property(protocol()->propHomePOBox).value().toString(); + homeAddress.locality = property(protocol()->propHomeCity).value().toString(); + homeAddress.pcode = property(protocol()->propHomePostalCode).value().toString(); + homeAddress.country = property(protocol()->propHomeCountry).value().toString(); + + // work address tab + XMPP::VCard::Address workAddress; + + workAddress.work = true; + workAddress.street = property(protocol()->propWorkStreet).value().toString(); + workAddress.extaddr = property(protocol()->propWorkExtAddr).value().toString(); + workAddress.pobox = property(protocol()->propWorkPOBox).value().toString(); + workAddress.locality = property(protocol()->propWorkCity).value().toString(); + workAddress.pcode = property(protocol()->propWorkPostalCode).value().toString(); + workAddress.country = property(protocol()->propWorkCountry).value().toString(); + + addressList.append(homeAddress); + addressList.append(workAddress); + + vCard.setAddressList(addressList); + + // home email + XMPP::VCard::Email homeEmail; + + homeEmail.home = true; + homeEmail.userid = property(protocol()->propEmailAddress).value().toString(); + + // work email + XMPP::VCard::Email workEmail; + + workEmail.work = true; + workEmail.userid = property(protocol()->propWorkEmailAddress).value().toString(); + + emailList.append(homeEmail); + emailList.append(workEmail); + + vCard.setEmailList(emailList); + + // work information tab + XMPP::VCard::Org org; + org.name = property(protocol()->propCompanyName).value().toString(); + org.unit = QStringList::split(",", property(protocol()->propCompanyDepartement).value().toString()); + vCard.setOrg(org); + vCard.setTitle (property(protocol()->propCompanyPosition).value().toString()); + vCard.setRole (property(protocol()->propCompanyRole).value().toString()); + + // phone numbers tab + XMPP::VCard::Phone phoneHome; + phoneHome.home = true; + phoneHome.number = property(protocol()->propPrivatePhone).value().toString(); + + XMPP::VCard::Phone phoneWork; + phoneWork.work = true; + phoneWork.number = property(protocol()->propWorkPhone).value().toString(); + + XMPP::VCard::Phone phoneFax; + phoneFax.fax = true; + phoneFax.number = property(protocol()->propPhoneFax).value().toString(); + + XMPP::VCard::Phone phoneCell; + phoneCell.cell = true; + phoneCell.number = property(protocol()->propPrivateMobilePhone).value().toString(); + + phoneList.append(phoneHome); + phoneList.append(phoneWork); + phoneList.append(phoneFax); + phoneList.append(phoneCell); + + vCard.setPhoneList(phoneList); + + // about tab + vCard.setDesc(property(protocol()->propAbout).value().toString()); + + // Set contact photo as a binary value (if he has set a photo) + if( hasProperty( protocol()->propPhoto.key() ) ) + { + QString photoPath = property( protocol()->propPhoto ).value().toString(); + QImage image( photoPath ); + QByteArray ba; + QBuffer buffer( ba ); + buffer.open( IO_WriteOnly ); + image.save( &buffer, "PNG" ); + vCard.setPhoto( ba ); + } + + vCard.setVersion("3.0"); + vCard.setProdId("Kopete"); + + XMPP::JT_VCard *task = new XMPP::JT_VCard (account()->client()->rootTask ()); + // signal to ourselves when the vCard data arrived + QObject::connect (task, SIGNAL (finished ()), this, SLOT (slotSentVCard ())); + task->set (vCard); + task->go (true); +} + +void JabberContact::setPhoto( const QString &photoPath ) +{ + QImage contactPhoto(photoPath); + QString newPhotoPath = photoPath; + if(contactPhoto.width() > 96 || contactPhoto.height() > 96) + { + // Save image to a new location if the image isn't the correct format. + QString newLocation( locateLocal( "appdata", "jabberphotos/"+ KURL(photoPath).fileName().lower() ) ); + + // Scale and crop the picture. + contactPhoto = contactPhoto.smoothScale( 96, 96, QImage::ScaleMin ); + // crop image if not square + if(contactPhoto.width() < contactPhoto.height()) + contactPhoto = contactPhoto.copy((contactPhoto.width()-contactPhoto.height())/2, 0, 96, 96); + else if (contactPhoto.width() > contactPhoto.height()) + contactPhoto = contactPhoto.copy(0, (contactPhoto.height()-contactPhoto.width())/2, 96, 96); + + // Use the cropped/scaled image now. + if(!contactPhoto.save(newLocation, "PNG")) + newPhotoPath = QString::null; + else + newPhotoPath = newLocation; + } + else if (contactPhoto.width() < 32 || contactPhoto.height() < 32) + { + // Save image to a new location if the image isn't the correct format. + QString newLocation( locateLocal( "appdata", "jabberphotos/"+ KURL(photoPath).fileName().lower() ) ); + + // Scale and crop the picture. + contactPhoto = contactPhoto.smoothScale( 32, 32, QImage::ScaleMin ); + // crop image if not square + if(contactPhoto.width() < contactPhoto.height()) + contactPhoto = contactPhoto.copy((contactPhoto.width()-contactPhoto.height())/2, 0, 32, 32); + else if (contactPhoto.width() > contactPhoto.height()) + contactPhoto = contactPhoto.copy(0, (contactPhoto.height()-contactPhoto.width())/2, 32, 32); + + // Use the cropped/scaled image now. + if(!contactPhoto.save(newLocation, "PNG")) + newPhotoPath = QString::null; + else + newPhotoPath = newLocation; + } + else if (contactPhoto.width() != contactPhoto.height()) + { + // Save image to a new location if the image isn't the correct format. + QString newLocation( locateLocal( "appdata", "jabberphotos/"+ KURL(photoPath).fileName().lower() ) ); + + if(contactPhoto.width() < contactPhoto.height()) + contactPhoto = contactPhoto.copy((contactPhoto.width()-contactPhoto.height())/2, 0, contactPhoto.height(), contactPhoto.height()); + else if (contactPhoto.width() > contactPhoto.height()) + contactPhoto = contactPhoto.copy(0, (contactPhoto.height()-contactPhoto.width())/2, contactPhoto.height(), contactPhoto.height()); + + // Use the cropped/scaled image now. + if(!contactPhoto.save(newLocation, "PNG")) + newPhotoPath = QString::null; + else + newPhotoPath = newLocation; + } + + setProperty( protocol()->propPhoto, newPhotoPath ); +} + +void JabberContact::slotSentVCard () +{ + +} + +void JabberContact::slotChatSessionDeleted ( QObject *sender ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Message manager deleted, collecting the pieces..." << endl; + + JabberChatSession *manager = static_cast(sender); + + mManagers.remove ( mManagers.find ( manager ) ); + +} + +JabberChatSession *JabberContact::manager ( Kopete::ContactPtrList chatMembers, Kopete::Contact::CanCreateFlags canCreate ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "called, canCreate: " << canCreate << endl; + + Kopete::ChatSession *_manager = Kopete::ChatSessionManager::self()->findChatSession ( account()->myself(), chatMembers, protocol() ); + JabberChatSession *manager = dynamic_cast( _manager ); + + /* + * If we didn't find a message manager for this contact, + * instantiate a new one if we are allowed to. (otherwise return 0) + */ + if ( !manager && canCreate ) + { + XMPP::Jid jid = rosterItem().jid(); + + /* + * If we have no hardwired JID, set any eventually + * locked resource as preselected resource. + * If there is no locked resource, the resource field + * will stay empty. + */ + if ( jid.resource().isEmpty () ) + jid.setResource ( account()->resourcePool()->lockedResource ( jid ).name () ); + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "No manager found, creating a new one with resource '" << jid.resource () << "'" << endl; + + manager = new JabberChatSession ( protocol(), static_cast(account()->myself()), chatMembers, jid.resource () ); + connect ( manager, SIGNAL ( destroyed ( QObject * ) ), this, SLOT ( slotChatSessionDeleted ( QObject * ) ) ); + mManagers.append ( manager ); + } + + return manager; + +} + +Kopete::ChatSession *JabberContact::manager ( Kopete::Contact::CanCreateFlags canCreate ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "called, canCreate: " << canCreate << endl; + + Kopete::ContactPtrList chatMembers; + chatMembers.append ( this ); + + return manager ( chatMembers, canCreate ); + +} + +JabberChatSession *JabberContact::manager ( const QString &resource, Kopete::Contact::CanCreateFlags canCreate ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "called, canCreate: " << canCreate << ", Resource: '" << resource << "'" << endl; + + /* + * First of all, see if we already have a manager matching + * the requested resource or if there are any managers with + * an empty resource. + */ + if ( !resource.isEmpty () ) + { + for ( JabberChatSession *mManager = mManagers.first (); mManager; mManager = mManagers.next () ) + { + if ( mManager->resource().isEmpty () || ( mManager->resource () == resource ) ) + { + // we found a matching manager, return this one + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Found an existing message manager for this resource." << endl; + return mManager; + } + } + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "No manager found for this resource, creating a new one." << endl; + + /* + * If we have come this far, we were either supposed to create + * a manager with a preselected resource but have found + * no available manager. (not even one with an empty resource) + * This means, we will have to create a new one with a + * preselected resource. + */ + Kopete::ContactPtrList chatmembers; + chatmembers.append ( this ); + JabberChatSession *manager = new JabberChatSession ( protocol(), + static_cast(account()->myself()), + chatmembers, resource ); + connect ( manager, SIGNAL ( destroyed ( QObject * ) ), this, SLOT ( slotChatSessionDeleted ( QObject * ) ) ); + mManagers.append ( manager ); + + return manager; + } + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Resource is empty, grabbing first available manager." << endl; + + /* + * The resource is empty, so just return first available manager. + */ + return dynamic_cast( manager ( canCreate ) ); + +} + +void JabberContact::deleteContact () +{ + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing user " << contactId () << endl; + + if ( !account()->isConnected () ) + { + account()->errorConnectFirst (); + return; + } + + /* + * Follow the recommendation of + * JEP-0162: Best Practices for Roster and Subscription Management + * http://www.jabber.org/jeps/jep-0162.html#removal + */ + + bool remove_from_roster=false; + + if( mRosterItem.subscription().type() == XMPP::Subscription::Both || mRosterItem.subscription().type() == XMPP::Subscription::From ) + { + int result = KMessageBox::questionYesNoCancel (Kopete::UI::Global::mainWidget(), + i18n ( "Do you also want to remove the authorization from user %1 to see your status?" ). + arg ( mRosterItem.jid().bare () ), i18n ("Notification"), + KStdGuiItem::del (), i18n("Keep"), "JabberRemoveAuthorizationOnDelete" ); + if(result == KMessageBox::Yes ) + remove_from_roster = true; + else if( result == KMessageBox::Cancel) + return; + } + else if( mRosterItem.subscription().type() == XMPP::Subscription::None || mRosterItem.subscription().type() == XMPP::Subscription::To ) + remove_from_roster = true; + + if( remove_from_roster ) + { + XMPP::JT_Roster * rosterTask = new XMPP::JT_Roster ( account()->client()->rootTask () ); + rosterTask->remove ( mRosterItem.jid () ); + rosterTask->go ( true ); + } + else + { + sendSubscription("unsubscribe"); + + XMPP::JT_Roster * rosterTask = new XMPP::JT_Roster ( account()->client()->rootTask () ); + rosterTask->set ( mRosterItem.jid (), QString() , QStringList() ); + rosterTask->go (true); + } + +} + +void JabberContact::sync ( unsigned int ) +{ + // if we are offline or this is a temporary contact or we should not synch, don't bother + if ( dontSync () || !account()->isConnected () || metaContact()->isTemporary () || metaContact() == Kopete::ContactList::self()->myself() ) + return; + + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << contactId () /*<< " - " <start(2*1000,true); + /* + the sync operation is delayed, because when we are doing a move to group operation, + kopete first add the contact to the group, then removes it. + Theses two operations should anyway be done in only one pass. + + if there is two jabber contact in one metacontact, this may result in an infinite change of + groups between theses two contacts, and the server is being flooded. + */ +} + +void JabberContact::slotDelayedSync( ) +{ + m_syncTimer->deleteLater(); + m_syncTimer=0L; + // if we are offline or this is a temporary contact or we should not synch, don't bother + if ( dontSync () || !account()->isConnected () || metaContact()->isTemporary () ) + return; + + bool changed=metaContact()->displayName() != mRosterItem.name(); + + + QStringList groups; + Kopete::GroupList groupList = metaContact ()->groups (); + + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Synchronizing contact " << contactId () << endl; + + for ( Kopete::Group * g = groupList.first (); g; g = groupList.next () ) + { + if ( g->type () != Kopete::Group::TopLevel ) + groups += g->displayName (); + } + + if(mRosterItem.groups() != groups) + { + changed=true; + mRosterItem.setGroups ( groups ); + } + + if(!changed) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "contact has not changed, abort sync" << endl; + return; + } + + XMPP::JT_Roster * rosterTask = new XMPP::JT_Roster ( account()->client()->rootTask () ); + + rosterTask->set ( mRosterItem.jid (), metaContact()->displayName (), mRosterItem.groups () ); + rosterTask->go (true); + +} + +void JabberContact::sendFile ( const KURL &sourceURL, const QString &/*fileName*/, uint /*fileSize*/ ) +{ + QString filePath; + + // if the file location is null, then get it from a file open dialog + if ( !sourceURL.isValid () ) + filePath = KFileDialog::getOpenFileName( QString::null , "*", 0L, i18n ( "Kopete File Transfer" ) ); + else + filePath = sourceURL.path(-1); + + QFile file ( filePath ); + + if ( file.exists () ) + { + // send the file + new JabberFileTransfer ( account (), this, filePath ); + } + +} + + +void JabberContact::slotSendAuth () +{ + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "(Re)send auth " << contactId () << endl; + + sendSubscription ("subscribed"); + +} + +void JabberContact::slotRequestAuth () +{ + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "(Re)request auth " << contactId () << endl; + + sendSubscription ("subscribe"); + +} + +void JabberContact::slotRemoveAuth () +{ + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Remove auth " << contactId () << endl; + + sendSubscription ("unsubscribed"); + +} + +void JabberContact::sendSubscription ( const QString& subType ) +{ + + if ( !account()->isConnected () ) + { + account()->errorConnectFirst (); + return; + } + + XMPP::JT_Presence * task = new XMPP::JT_Presence ( account()->client()->rootTask () ); + + task->sub ( mRosterItem.jid().full (), subType ); + task->go ( true ); + +} + +void JabberContact::slotSelectResource () +{ + int currentItem = QString ( static_cast( sender() )->name () ).toUInt (); + + /* + * Warn the user if there is already an active chat window. + * The resource selection will only apply for newly opened + * windows. + */ + if ( manager ( Kopete::Contact::CannotCreate ) != 0 ) + { + KMessageBox::queuedMessageBox ( Kopete::UI::Global::mainWidget (), + KMessageBox::Information, + i18n ("You have preselected a resource for contact %1, " + "but you still have open chat windows for this contact. " + "The preselected resource will only apply to newly opened " + "chat windows.").arg ( contactId () ), + i18n ("Jabber Resource Selector") ); + } + + if (currentItem == 0) + { + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing active resource, trusting bestResource()." << endl; + + account()->resourcePool()->removeLock ( rosterItem().jid() ); + } + else + { + QString selectedResource = static_cast(sender())->text(); + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Moving to resource " << selectedResource << endl; + + account()->resourcePool()->lockToResource ( rosterItem().jid() , XMPP::Resource ( selectedResource ) ); + } + +} + +void JabberContact::sendPresence ( const XMPP::Status status ) +{ + + if ( !account()->isConnected () ) + { + account()->errorConnectFirst (); + return; + } + + XMPP::Status newStatus = status; + + // honour our priority + if(newStatus.isAvailable()) + newStatus.setPriority ( account()->configGroup()->readNumEntry ( "Priority", 5 ) ); + + XMPP::JT_Presence * task = new XMPP::JT_Presence ( account()->client()->rootTask () ); + + task->pres ( bestAddress (), newStatus); + task->go ( true ); + +} + + +void JabberContact::slotStatusOnline () +{ + + XMPP::Status status; + status.setShow(""); + + sendPresence ( status ); + +} + +void JabberContact::slotStatusChatty () +{ + + XMPP::Status status; + status.setShow ("chat"); + + sendPresence ( status ); + +} + +void JabberContact::slotStatusAway () +{ + + XMPP::Status status; + status.setShow ("away"); + + sendPresence ( status ); + +} + +void JabberContact::slotStatusXA () +{ + + XMPP::Status status; + status.setShow ("xa"); + + sendPresence ( status ); + +} + +void JabberContact::slotStatusDND () +{ + + XMPP::Status status; + status.setShow ("dnd"); + + sendPresence ( status ); + + +} + +void JabberContact::slotStatusInvisible () +{ + + XMPP::Status status; + status.setIsAvailable( false ); + + sendPresence ( status ); + +} + +bool JabberContact::isContactRequestingEvent( XMPP::MsgEvent event ) +{ + if ( event == OfflineEvent ) + return mRequestOfflineEvent; + else if ( event == DeliveredEvent ) + return mRequestDeliveredEvent; + else if ( event == DisplayedEvent ) + return mRequestDisplayedEvent; + else if ( event == ComposingEvent ) + return mRequestComposingEvent; + else if ( event == CancelEvent ) + return mRequestComposingEvent; + else if ( event == GoneEvent ) + return mRequestGoneEvent; + else + return false; +} + +QString JabberContact::lastReceivedMessageId () const +{ + return mLastReceivedMessageId; +} + +void JabberContact::voiceCall( ) +{ +#ifdef SUPPORT_JINGLE + Jid jid = mRosterItem.jid(); + + // It's honour lock by default. + JabberResource *bestResource = account()->resourcePool()->bestJabberResource( jid ); + if( bestResource ) + { + if( jid.resource().isEmpty() ) + { + // If the jid resource is empty, get the JID from best resource for this contact. + jid = bestResource->jid(); + } + + // Check if the voice caller exist and the current resource support voice. + if( account()->voiceCaller() && bestResource->features().canVoice() ) + { + JingleVoiceSessionDialog *voiceDialog = new JingleVoiceSessionDialog( jid, account()->voiceCaller() ); + voiceDialog->show(); + voiceDialog->start(); + } +#if 0 + if( account()->sessionManager() && bestResource->features().canVoice() ) + { + JingleVoiceSession *session = static_cast(account()->sessionManager()->createSession("http://www.google.com/session/phone", jid)); + + JingleVoiceSessionDialog *voiceDialog = new JingleVoiceSessionDialog(session); + voiceDialog->show(); + voiceDialog->start(); + } +#endif + } + else + { + // Shouldn't never go there. + } +#endif +} + +void JabberContact::slotDiscoFinished( ) +{ + mDiscoDone = true; + JT_DiscoInfo *jt = (JT_DiscoInfo *)sender(); + + bool is_transport=false; + QString tr_type; + + if ( jt->success() ) + { + QValueList identities = jt->item().identities(); + QValueList::Iterator it; + for ( it = identities.begin(); it != identities.end(); ++it ) + { + XMPP::DiscoItem::Identity ident=*it; + if(ident.category == "gateway") + { + is_transport=true; + tr_type=ident.type; + //name=ident.name; + + break; //(we currently only support gateway) + } + else if (ident.category == "service") + { + //The ApaSMSAgent is reporting itself as service (instead of gateway) which is broken. + //we anyway support it. See bug 127811 + if(ident.type == "sms") + { + is_transport=true; + tr_type=ident.type; + } + } + } + } + + if(is_transport && !transport()) + { //ok, we are not a contact, we are a transport.... + + XMPP::RosterItem ri = rosterItem(); + Kopete::MetaContact *mc=metaContact(); + JabberAccount *parentAccount=account(); + Kopete::OnlineStatus status=onlineStatus(); + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << ri.jid().full() << " is not a contact but a gateway - " << this << endl; + + if( Kopete::AccountManager::self()->findAccount( protocol()->pluginId() , account()->accountId() + "/" + ri.jid().bare() ) ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "oops, transport already exists, abort operation " << endl; + return; + } + + delete this; //we are not a contact i said ! + + if(mc->contacts().count() == 0) + Kopete::ContactList::self()->removeMetaContact( mc ); + + //we need to create the transport when 'this' is already deleted, so transport->myself() will not conflict with it + JabberTransport *transport = new JabberTransport( parentAccount , ri , tr_type ); + if(!Kopete::AccountManager::self()->registerAccount( transport )) + return; + transport->myself()->setOnlineStatus( status ); //push back the online status + return; + } +} + + + +#include "jabbercontact.moc" diff --git a/kopete/protocols/jabber/jabbercontact.h b/kopete/protocols/jabber/jabbercontact.h new file mode 100644 index 00000000..a7a3b024 --- /dev/null +++ b/kopete/protocols/jabber/jabbercontact.h @@ -0,0 +1,266 @@ + /* + * jabbercontact.cpp - Regular Kopete Jabber protocol contact + * + * Copyright (c) 2002-2004 by Till Gerken + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * * + * ************************************************************************* + */ + +#ifndef JABBERCONTACT_H +#define JABBERCONTACT_H + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "jabberbasecontact.h" +#include "xmpp_vcard.h" + +#include "kopetechatsession.h" // needed for silly Kopete::ContactPtrList + +class JabberChatSession; +class QTimer; + +class JabberContact : public JabberBaseContact +{ + +Q_OBJECT + +public: + + JabberContact (const XMPP::RosterItem &rosterItem, + Kopete::Account *account, Kopete::MetaContact * mc, const QString &legacyId = QString()); + + ~JabberContact(); + + /** + * Create custom context menu items for the contact + * FIXME: implement manager version here? + */ + QPtrList *customContextMenuActions (); + + /** + * Start a rename request. + */ + void rename ( const QString &newName ); + + /** + * Deal with an incoming message for this contact. + */ + void handleIncomingMessage ( const XMPP::Message &message ); + + /** + * Create a message manager for this contact. + * This variant is a pure single-contact version and + * not suitable for groupchat, as it only looks for + * managers with ourselves in the contact list. + */ + Kopete::ChatSession *manager ( Kopete::Contact::CanCreateFlags ); + + + bool isContactRequestingEvent( XMPP::MsgEvent event ); + + QString lastReceivedMessageId () const; + +public slots: + + /** + * Remove this contact from the roster + */ + void deleteContact (); + + /** + * Sync Groups with server + * + * operations are alctually performed in sloDelayedSync() + */ + void sync(unsigned int); + + /** + * This is the JabberContact level slot for sending files. + * + * @param sourceURL The actual KURL of the file you are sending + * @param fileName (Optional) An alternate name for the file - what the + * receiver will see + * @param fileSize (Optional) Size of the file being sent. Used when sending + * a nondeterminate file size (such as over a socket) + */ + virtual void sendFile( const KURL &sourceURL = KURL(), + const QString &fileName = QString::null, uint fileSize = 0L ); + + /** + * Update the vCard on the server. + * @todo is that still used ? + */ + void slotSendVCard(); + + /** + * Set contact photo. + * @param path Path to the photo. + */ + void setPhoto(const QString &photoPath); + + /** + * this will start a voice call to the contact + */ + void voiceCall(); + +private slots: + + /** + * Send type="subscribed" to contact + */ + void slotSendAuth (); + + /** + * Send type="subscribe" to contact + */ + void slotRequestAuth (); + + /** + * Send type="unsubscribed" to contact + */ + void slotRemoveAuth (); + + /** + * Change this contact's status + */ + void slotStatusOnline (); + void slotStatusChatty (); + void slotStatusAway (); + void slotStatusXA (); + void slotStatusDND (); + void slotStatusInvisible (); + + /** + * Select a new resource for the contact + */ + void slotSelectResource (); + + void slotChatSessionDeleted ( QObject *sender ); + + /** + * Check if cached vCard is recent. + * Triggered as soon as Kopete changes its online state. + */ + void slotCheckVCard (); + + /** + * Triggered from a timer, requests the vCard. + * Timer is initiated by @ref slotCheckVCard. + */ + void slotGetTimedVCard (); + + /** + * Passes vCard on to parsing function. + */ + void slotGotVCard (); + + /** + * Get information about last activity of the contact. + * Triggered as soon as Kopete goes online or the contact goes offline. + */ + void slotCheckLastActivity ( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ); + + /** + * Triggered from a timer, requests last activity information. + * Timer is initiated by @ref slotCheckLastActivity. + */ + void slotGetTimedLastActivity (); + + /** + * Updates activity information. + */ + void slotGotLastActivity (); + + /** + * Display a error message if the vCard sent was unsuccesful. + */ + void slotSentVCard(); + + /** + * The service discovery on that contact is finished + */ + void slotDiscoFinished(); + + /** + * actually perform operations of sync() with a delay. + * slot received by the syncTimer. + */ + void slotDelayedSync(); +private: + + /** + * Create a message manager for this contact. + * This variant is a pure single-contact version and + * not suitable for groupchat, as it only looks for + * managers with ourselves in the contact list. + * Additionally to the version above, this one adds + * a resource constraint that has to be matched by + * the manager. If a new manager is created, the given + * resource is preselected. + */ + JabberChatSession *manager ( const QString &resource, Kopete::Contact::CanCreateFlags ); + + /** + * Create a message manager for this contact. + * This version is suitable for group chat as it + * looks for a message manager with a given + * list of contacts as members. + */ + JabberChatSession *manager ( Kopete::ContactPtrList chatMembers, Kopete::Contact::CanCreateFlags ); + + /** + * Sends subscription messages. + */ + void sendSubscription (const QString& subType); + + /** + * Sends a presence packet to this contact + */ + void sendPresence ( const XMPP::Status status ); + + /** + * This variable keeps a list of message managers. + * It is required to locate message managers by + * resource name, if one account is interacting + * with several resources of the same contact + * at the same time. Note that this does *not* + * apply to group chats, so this variable + * only contains classes of type JabberChatSession. + * The casts in manager() and slotChatSessionDeleted() + * are thus legal. + */ + QPtrList mManagers; + + /** + * Indicates whether the vCard is currently + * being updated or not. + */ + bool mVCardUpdateInProgress :1; + + bool mRequestComposingEvent :1; + bool mRequestOfflineEvent :1; + bool mRequestDisplayedEvent :1; + bool mRequestDeliveredEvent :1; + bool mRequestGoneEvent :1; + /** + * tell if the disco#info has been done for this contact. + */ + bool mDiscoDone :1; + + QString mLastReceivedMessageId; + QTimer *m_syncTimer; + +}; + +#endif diff --git a/kopete/protocols/jabber/jabbercontactpool.cpp b/kopete/protocols/jabber/jabbercontactpool.cpp new file mode 100644 index 00000000..736c6045 --- /dev/null +++ b/kopete/protocols/jabber/jabbercontactpool.cpp @@ -0,0 +1,355 @@ + /* + * jabbercontactpool.cpp + * + * Copyright (c) 2004 by Till Gerken + * Copyright (c) 2006 by Olivier Goffart + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * * + * ************************************************************************* + */ + +#include "jabbercontactpool.h" + +#include +#include +#include +#include +#include +#include "kopeteuiglobal.h" +#include "jabberprotocol.h" +#include "jabberbasecontact.h" +#include "jabbercontact.h" +#include "jabbergroupcontact.h" +#include "jabbergroupmembercontact.h" +#include "jabberresourcepool.h" +#include "jabberaccount.h" +#include "jabbertransport.h" + +JabberContactPool::JabberContactPool ( JabberAccount *account ) +{ + + // automatically delete all contacts in the pool upon removal + mPool.setAutoDelete (true); + + mAccount = account; + +} + +JabberContactPool::~JabberContactPool () +{ +} + +JabberContactPoolItem *JabberContactPool::findPoolItem ( const XMPP::RosterItem &contact ) +{ + + // see if the contact already exists + for(JabberContactPoolItem *mContactItem = mPool.first (); mContactItem; mContactItem = mPool.next ()) + { + if ( mContactItem->contact()->rosterItem().jid().full().lower() == contact.jid().full().lower() ) + { + return mContactItem; + } + } + + return 0; + +} + +JabberContact *JabberContactPool::addContact ( const XMPP::RosterItem &contact, Kopete::MetaContact *metaContact, bool dirty ) +{ + // see if the contact already exists + JabberContactPoolItem *mContactItem = findPoolItem ( contact ); + if ( mContactItem) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Updating existing contact " << contact.jid().full() << " - " << mContactItem->contact() << endl; + + // It exists, update it. + mContactItem->contact()->updateContact ( contact ); + mContactItem->setDirty ( dirty ); + + JabberContact *retval = dynamic_cast(mContactItem->contact ()); + + if ( !retval ) + { + KMessageBox::error ( Kopete::UI::Global::mainWidget (), + "Fatal error in the Jabber contact pool. Please restart Kopete and submit a debug log " + "of your session to http://bugs.kde.org.", + "Fatal Jabber Error" ); + } + + return retval; + } + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Adding new contact " << contact.jid().full() << endl; + + JabberTransport *transport=0l; + QString legacyId; + //find if the contact should be added to a transport. + if(mAccount->transports().contains(contact.jid().domain())) + { + transport=mAccount->transports()[contact.jid().domain()]; + legacyId=transport->legacyId( contact.jid() ); + } + + // create new contact instance and add it to the dictionary + JabberContact *newContact = new JabberContact ( contact, transport ? (Kopete::Account*)transport : (Kopete::Account*)mAccount, metaContact , legacyId ); + JabberContactPoolItem *newContactItem = new JabberContactPoolItem ( newContact ); + connect ( newContact, SIGNAL ( contactDestroyed ( Kopete::Contact * ) ), this, SLOT ( slotContactDestroyed ( Kopete::Contact * ) ) ); + newContactItem->setDirty ( dirty ); + mPool.append ( newContactItem ); + + return newContact; + +} + +JabberBaseContact *JabberContactPool::addGroupContact ( const XMPP::RosterItem &contact, bool roomContact, Kopete::MetaContact *metaContact, bool dirty ) +{ + + XMPP::RosterItem mContact ( roomContact ? contact.jid().userHost () : contact.jid().full() ); + + // see if the contact already exists + JabberContactPoolItem *mContactItem = findPoolItem ( mContact ); + if ( mContactItem) + { + if(mContactItem->contact()->inherits(roomContact ? + (const char*)("JabberGroupContact") : (const char*)("JabberGroupMemberContact") ) ) + { + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Updating existing contact " << mContact.jid().full() << endl; + + // It exists, update it. + mContactItem->contact()->updateContact ( mContact ); + mContactItem->setDirty ( dirty ); + + //we must tell to the originating function that no new contact has been added + return 0L;//mContactItem->contact (); + } + else + { + //this happen if we receive a MUC invitaiton: when the invitaiton is received, it's from the muc itself + //and then kopete will create a temporary contact for it. but it will not be a good contact. + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Bad contact will be removed and re-added " << mContact.jid().full() << endl; + Kopete::MetaContact *old_mc=mContactItem->contact()->metaContact(); + delete mContactItem->contact(); + mContactItem = 0L; + if(old_mc->contacts().isEmpty() && old_mc!=metaContact) + { + Kopete::ContactList::self()->removeMetaContact( old_mc ); + } + + } + + } + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Adding new contact " << mContact.jid().full() << endl; + + // create new contact instance and add it to the dictionary + JabberBaseContact *newContact; + + if ( roomContact ) + newContact = new JabberGroupContact ( contact, mAccount, metaContact ); + else + newContact = new JabberGroupMemberContact ( contact, mAccount, metaContact ); + + JabberContactPoolItem *newContactItem = new JabberContactPoolItem ( newContact ); + + connect ( newContact, SIGNAL ( contactDestroyed ( Kopete::Contact * ) ), this, SLOT ( slotContactDestroyed ( Kopete::Contact * ) ) ); + + newContactItem->setDirty ( dirty ); + mPool.append ( newContactItem ); + + return newContact; + +} + +void JabberContactPool::removeContact ( const XMPP::Jid &jid ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing contact " << jid.full() << endl; + + for(JabberContactPoolItem *mContactItem = mPool.first (); mContactItem; mContactItem = mPool.next ()) + { + if ( mContactItem->contact()->rosterItem().jid().full().lower() == jid.full().lower() ) + { + /* + * The following deletion will cause slotContactDestroyed() + * to be called, which will clean the up the list. + */ + if(mContactItem->contact()) + { + Kopete::MetaContact *mc=mContactItem->contact()->metaContact(); + delete mContactItem->contact (); + if(mc && mc->contacts().isEmpty()) + { + Kopete::ContactList::self()->removeMetaContact(mc) ; + } + } + return; + } + } + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "WARNING: No match found!" << endl; + +} + +void JabberContactPool::slotContactDestroyed ( Kopete::Contact *contact ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Contact deleted, collecting the pieces..." << endl; + + JabberBaseContact *jabberContact = static_cast( contact ); + //WARNING this ptr is not usable, we are in the Kopete::Contact destructor + + // remove contact from the pool + for(JabberContactPoolItem *mContactItem = mPool.first (); mContactItem; mContactItem = mPool.next ()) + { + if ( mContactItem->contact() == jabberContact ) + { + mPool.remove (); + break; + } + } + + // delete all resources for it + if(contact->account()==(Kopete::Account*)(mAccount)) + mAccount->resourcePool()->removeAllResources ( XMPP::Jid ( contact->contactId() ) ); + else + { + //this is a legacy contact. we have no way to get the real Jid at this point, we can only guess it. + QString contactId= contact->contactId().replace('@','%') + "@" + contact->account()->myself()->contactId(); + mAccount->resourcePool()->removeAllResources ( XMPP::Jid ( contactId ) ) ; + } + +} + +void JabberContactPool::clear () +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Clearing the contact pool." << endl; + + for(JabberContactPoolItem *mContactItem = mPool.first (); mContactItem; mContactItem = mPool.next ()) + { + /* + * The following deletion will cause slotContactDestroyed() + * to be called, which will clean the up the list. + * NOTE: this is a very inefficient way to clear the list + */ + delete mContactItem->contact (); + } + +} + +void JabberContactPool::setDirty ( const XMPP::Jid &jid, bool dirty ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Setting flag for " << jid.full() << " to " << dirty << endl; + + for(JabberContactPoolItem *mContactItem = mPool.first (); mContactItem; mContactItem = mPool.next ()) + { + if ( mContactItem->contact()->rosterItem().jid().full().lower() == jid.full().lower() ) + { + mContactItem->setDirty ( dirty ); + return; + } + } + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "WARNING: No match found!" << endl; + +} + +void JabberContactPool::cleanUp () +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Cleaning dirty items from contact pool." << endl; + + for(JabberContactPoolItem *mContactItem = mPool.first (); mContactItem; mContactItem = mPool.next ()) + { + if ( mContactItem->dirty () ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing dirty contact " << mContactItem->contact()->contactId () << endl; + + /* + * The following deletion will cause slotContactDestroyed() + * to be called, which will clean the up the list. + */ + delete mContactItem->contact (); + } + } + +} + +JabberBaseContact *JabberContactPool::findExactMatch ( const XMPP::Jid &jid ) +{ + + for(JabberContactPoolItem *mContactItem = mPool.first (); mContactItem; mContactItem = mPool.next ()) + { + if ( mContactItem->contact()->rosterItem().jid().full().lower () == jid.full().lower () ) + { + return mContactItem->contact (); + } + } + + return 0L; + +} + +JabberBaseContact *JabberContactPool::findRelevantRecipient ( const XMPP::Jid &jid ) +{ + + for(JabberContactPoolItem *mContactItem = mPool.first (); mContactItem; mContactItem = mPool.next ()) + { + if ( mContactItem->contact()->rosterItem().jid().full().lower () == jid.userHost().lower () ) + { + return mContactItem->contact (); + } + } + + return 0L; + +} + +QPtrList JabberContactPool::findRelevantSources ( const XMPP::Jid &jid ) +{ + QPtrList list; + + for(JabberContactPoolItem *mContactItem = mPool.first (); mContactItem; mContactItem = mPool.next ()) + { + if ( mContactItem->contact()->rosterItem().jid().userHost().lower () == jid.userHost().lower () ) + { + list.append ( mContactItem->contact () ); + } + } + + return list; + +} + +JabberContactPoolItem::JabberContactPoolItem ( JabberBaseContact *contact ) +{ + mDirty = true; + mContact = contact; +} + +JabberContactPoolItem::~JabberContactPoolItem () +{ +} + +void JabberContactPoolItem::setDirty ( bool dirty ) +{ + mDirty = dirty; +} + +bool JabberContactPoolItem::dirty () +{ + return mDirty; +} + +JabberBaseContact *JabberContactPoolItem::contact () +{ + return mContact; +} + +#include "jabbercontactpool.moc" diff --git a/kopete/protocols/jabber/jabbercontactpool.h b/kopete/protocols/jabber/jabbercontactpool.h new file mode 100644 index 00000000..6582f64c --- /dev/null +++ b/kopete/protocols/jabber/jabbercontactpool.h @@ -0,0 +1,124 @@ + /* + * jabbercontactpool.h + * + * Copyright (c) 2004 by Till Gerken + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * * + * ************************************************************************* + */ + +#ifndef JABBERCONTACTPOOL_H +#define JABBERCONTACTPOOL_H + +#include +#include + +namespace Kopete { class MetaContact; } +namespace Kopete { class Contact; } +class JabberContactPoolItem; +class JabberBaseContact; +class JabberContact; +class JabberGroupContact; +class JabberAccount; +class JabberTransport; + +/** + * @author Till Gerken + */ +class JabberContactPool : public QObject +{ + +Q_OBJECT + +public: + /** + * Default constructor + */ + JabberContactPool ( JabberAccount *account ); + + /** + * Default destructor + */ + ~JabberContactPool(); + + /** + * Add a contact to the pool + */ + JabberContact *addContact ( const XMPP::RosterItem &contact, Kopete::MetaContact *metaContact, bool dirty = true ); + JabberBaseContact *addGroupContact ( const XMPP::RosterItem &contact, bool roomContact, Kopete::MetaContact *metaContact, bool dirty = true ); + + /** + * Remove a contact from the pool + */ + void removeContact ( const XMPP::Jid &jid ); + + /** + * Remove all contacts from the pool + */ + void clear (); + + /** + * Sets the "dirty" flag for a certain contact + */ + void setDirty ( const XMPP::Jid &jid, bool dirty ); + + /** + * Remove all dirty elements from the pool + * (used after connecting to delete removed items from the roster) + */ + void cleanUp (); + + /** + * Find an exact match in the pool by full JID. + */ + JabberBaseContact *findExactMatch ( const XMPP::Jid &jid ); + + /** + * Find a relevant recipient for a given JID. + * This will match user@domain for a given user@domain/resource, + * but NOT user@domain/resource for a given user@domain. + */ + JabberBaseContact *findRelevantRecipient ( const XMPP::Jid &jid ); + + /** + * Find relevant sources for a given JID. + * This will match user@domain/resource for a given user@domain. + */ + QPtrList findRelevantSources ( const XMPP::Jid &jid ); + +private slots: + void slotContactDestroyed ( Kopete::Contact *contact ); + +private: + JabberContactPoolItem *findPoolItem ( const XMPP::RosterItem &contact ); + + QPtrList mPool; + JabberAccount *mAccount; + +}; + +class JabberContactPoolItem : QObject +{ +Q_OBJECT +public: + JabberContactPoolItem ( JabberBaseContact *contact ); + ~JabberContactPoolItem (); + + void setDirty ( bool dirty ); + bool dirty (); + JabberBaseContact *contact (); + +private: + bool mDirty; + JabberBaseContact *mContact; +}; + +#endif diff --git a/kopete/protocols/jabber/jabberfiletransfer.cpp b/kopete/protocols/jabber/jabberfiletransfer.cpp new file mode 100644 index 00000000..fde5b105 --- /dev/null +++ b/kopete/protocols/jabber/jabberfiletransfer.cpp @@ -0,0 +1,326 @@ + /* + * jabberfiletransfer.cpp + * + * Copyright (c) 2004 by Till Gerken + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * * + * ************************************************************************* + */ + +#include +#include +#include +#include "jabberfiletransfer.h" +#include +#include +#include +#include "kopeteuiglobal.h" +#include "kopetemetacontact.h" +#include "kopetecontactlist.h" +#include "kopetetransfermanager.h" +#include "jabberaccount.h" +#include "jabberprotocol.h" +#include "jabberclient.h" +#include "jabbercontactpool.h" +#include "jabberbasecontact.h" +#include "jabbercontact.h" + +JabberFileTransfer::JabberFileTransfer ( JabberAccount *account, XMPP::FileTransfer *incomingTransfer ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "New incoming transfer from " << incomingTransfer->peer().full () << ", filename " << incomingTransfer->fileName () << ", size " << QString::number ( incomingTransfer->fileSize () ) << endl; + + mAccount = account; + mXMPPTransfer = incomingTransfer; + + // try to locate an exact match in our pool first + JabberBaseContact *contact = mAccount->contactPool()->findExactMatch ( mXMPPTransfer->peer () ); + + if ( !contact ) + { + // we have no exact match, try a broader search + contact = mAccount->contactPool()->findRelevantRecipient ( mXMPPTransfer->peer () ); + } + + if ( !contact ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "No matching local contact found, creating a new one." << endl; + + Kopete::MetaContact *metaContact = new Kopete::MetaContact (); + + metaContact->setTemporary (true); + + contact = mAccount->contactPool()->addContact ( mXMPPTransfer->peer (), metaContact, false ); + + Kopete::ContactList::self ()->addMetaContact ( metaContact ); + } + + connect ( Kopete::TransferManager::transferManager (), SIGNAL ( accepted ( Kopete::Transfer *, const QString & ) ), + this, SLOT ( slotIncomingTransferAccepted ( Kopete::Transfer *, const QString & ) ) ); + connect ( Kopete::TransferManager::transferManager (), SIGNAL ( refused ( const Kopete::FileTransferInfo & ) ), + this, SLOT ( slotTransferRefused ( const Kopete::FileTransferInfo & ) ) ); + + initializeVariables (); + + mTransferId = Kopete::TransferManager::transferManager()->askIncomingTransfer ( contact, + mXMPPTransfer->fileName (), + mXMPPTransfer->fileSize (), + mXMPPTransfer->description () ); + +} + +JabberFileTransfer::JabberFileTransfer ( JabberAccount *account, JabberBaseContact *contact, const QString &file ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "New outgoing transfer for " << contact->contactId() << ": " << file << endl; + + mAccount = account; + mLocalFile.setName ( file ); + mLocalFile.open ( IO_ReadOnly ); + + mKopeteTransfer = Kopete::TransferManager::transferManager()->addTransfer ( contact, + mLocalFile.name (), + mLocalFile.size (), + contact->contactId (), + Kopete::FileTransferInfo::Outgoing ); + + connect ( mKopeteTransfer, SIGNAL ( result ( KIO::Job * ) ), this, SLOT ( slotTransferResult () ) ); + + mXMPPTransfer = mAccount->client()->fileTransferManager()->createTransfer (); + + initializeVariables (); + + connect ( mXMPPTransfer, SIGNAL ( connected () ), this, SLOT ( slotOutgoingConnected () ) ); + connect ( mXMPPTransfer, SIGNAL ( bytesWritten ( int ) ), this, SLOT ( slotOutgoingBytesWritten ( int ) ) ); + connect ( mXMPPTransfer, SIGNAL ( error ( int ) ), this, SLOT ( slotTransferError ( int ) ) ); + + mXMPPTransfer->sendFile ( XMPP::Jid ( contact->fullAddress () ), KURL(file).fileName (), mLocalFile.size (), "" ); + +} + +JabberFileTransfer::~JabberFileTransfer () +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Destroying Jabber file transfer object." << endl; + + mLocalFile.close (); + + mXMPPTransfer->close (); + delete mXMPPTransfer; + +} + +void JabberFileTransfer::initializeVariables () +{ + + mTransferId = -1; + mBytesTransferred = 0; + mBytesToTransfer = 0; + mXMPPTransfer->setProxy ( XMPP::Jid ( mAccount->configGroup()->readEntry ( "ProxyJID" ) ) ); + +} + +void JabberFileTransfer::slotIncomingTransferAccepted ( Kopete::Transfer *transfer, const QString &fileName ) +{ + + if ( (long)transfer->info().transferId () != mTransferId ) + return; + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Accepting transfer for " << mXMPPTransfer->peer().full () << endl; + + mKopeteTransfer = transfer; + mLocalFile.setName ( fileName ); + + bool couldOpen = false; + Q_LLONG offset = 0; + Q_LLONG length = 0; + + mBytesTransferred = 0; + mBytesToTransfer = mXMPPTransfer->fileSize (); + + if ( mXMPPTransfer->rangeSupported () && mLocalFile.exists () ) + { + KGuiItem resumeButton ( i18n ( "&Resume" ) ); + KGuiItem overwriteButton ( i18n ( "Over&write" ) ); + + switch ( KMessageBox::questionYesNoCancel ( Kopete::UI::Global::mainWidget (), + i18n ( "The file %1 already exists, do you want to resume or overwrite it?" ).arg ( fileName ), + i18n ( "File Exists: %1" ).arg ( fileName ), + resumeButton, overwriteButton ) ) + { + case KMessageBox::Yes: // resume + couldOpen = mLocalFile.open ( IO_ReadWrite ); + if ( couldOpen ) + { + offset = mLocalFile.size (); + length = mXMPPTransfer->fileSize () - offset; + mBytesTransferred = offset; + mBytesToTransfer = length; + mLocalFile.at ( mLocalFile.size () ); + } + break; + + case KMessageBox::No: // overwrite + couldOpen = mLocalFile.open ( IO_WriteOnly ); + break; + + default: // cancel + deleteLater (); + return; + } + } + else + { + // overwrite by default + couldOpen = mLocalFile.open ( IO_WriteOnly ); + } + + if ( !couldOpen ) + { + transfer->slotError ( KIO::ERR_COULD_NOT_WRITE, fileName ); + + deleteLater (); + } + else + { + connect ( mKopeteTransfer, SIGNAL ( result ( KIO::Job * ) ), this, SLOT ( slotTransferResult () ) ); + connect ( mXMPPTransfer, SIGNAL ( readyRead ( const QByteArray& ) ), this, SLOT ( slotIncomingDataReady ( const QByteArray & ) ) ); + connect ( mXMPPTransfer, SIGNAL ( error ( int ) ), this, SLOT ( slotTransferError ( int ) ) ); + mXMPPTransfer->accept ( offset, length ); + } + +} + +void JabberFileTransfer::slotTransferRefused ( const Kopete::FileTransferInfo &transfer ) +{ + + if ( (long)transfer.transferId () != mTransferId ) + return; + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Local user refused transfer from " << mXMPPTransfer->peer().full () << endl; + + deleteLater (); + +} + +void JabberFileTransfer::slotTransferResult () +{ + + if ( mKopeteTransfer->error () == KIO::ERR_USER_CANCELED ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Transfer with " << mXMPPTransfer->peer().full () << " has been canceled." << endl; + mXMPPTransfer->close (); + deleteLater (); + } + +} + +void JabberFileTransfer::slotTransferError ( int errorCode ) +{ + + switch ( errorCode ) + { + case XMPP::FileTransfer::ErrReject: + // user rejected the transfer request + mKopeteTransfer->slotError ( KIO::ERR_ACCESS_DENIED, + mXMPPTransfer->peer().full () ); + break; + + case XMPP::FileTransfer::ErrNeg: + // unable to negotiate a suitable connection for the file transfer with the user + mKopeteTransfer->slotError ( KIO::ERR_COULD_NOT_LOGIN, + mXMPPTransfer->peer().full () ); + break; + + case XMPP::FileTransfer::ErrConnect: + // could not connect to the user + mKopeteTransfer->slotError ( KIO::ERR_COULD_NOT_CONNECT, + mXMPPTransfer->peer().full () ); + break; + + case XMPP::FileTransfer::ErrStream: + // data stream was disrupted, probably cancelled + mKopeteTransfer->slotError ( KIO::ERR_CONNECTION_BROKEN, + mXMPPTransfer->peer().full () ); + break; + + default: + // unknown error + mKopeteTransfer->slotError ( KIO::ERR_UNKNOWN, + mXMPPTransfer->peer().full () ); + break; + } + + deleteLater (); + +} + +void JabberFileTransfer::slotIncomingDataReady ( const QByteArray &data ) +{ + + mBytesTransferred += data.size (); + mBytesToTransfer -= data.size (); + + mKopeteTransfer->slotProcessed ( mBytesTransferred ); + + mLocalFile.writeBlock ( data ); + + if ( mBytesToTransfer <= 0 ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Transfer from " << mXMPPTransfer->peer().full () << " done." << endl; + + mKopeteTransfer->slotComplete (); + + deleteLater (); + } + +} + +void JabberFileTransfer::slotOutgoingConnected () +{ + + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Outgoing data connection is open." << endl; + + mBytesTransferred = mXMPPTransfer->offset (); + mLocalFile.at ( mXMPPTransfer->offset () ); + mBytesToTransfer = ( mXMPPTransfer->fileSize () > mXMPPTransfer->length () ) ? mXMPPTransfer->length () : mXMPPTransfer->fileSize (); + + slotOutgoingBytesWritten ( 0 ); + +} + +void JabberFileTransfer::slotOutgoingBytesWritten ( int nrWritten ) +{ + + mBytesTransferred += nrWritten; + mBytesToTransfer -= nrWritten; + + mKopeteTransfer->slotProcessed ( mBytesTransferred ); + + if ( mBytesToTransfer ) + { + int nrToWrite = mXMPPTransfer->dataSizeNeeded (); + + QByteArray readBuffer ( nrToWrite ); + + mLocalFile.readBlock ( readBuffer.data (), nrToWrite ); + + mXMPPTransfer->writeFileData ( readBuffer ); + } + else + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Transfer to " << mXMPPTransfer->peer().full () << " done." << endl; + + mKopeteTransfer->slotComplete (); + + deleteLater (); + } + +} + +#include "jabberfiletransfer.moc" diff --git a/kopete/protocols/jabber/jabberfiletransfer.h b/kopete/protocols/jabber/jabberfiletransfer.h new file mode 100644 index 00000000..01ba99e1 --- /dev/null +++ b/kopete/protocols/jabber/jabberfiletransfer.h @@ -0,0 +1,74 @@ + /* + * jabberfiletransfer.h + * + * Copyright (c) 2004 by Till Gerken + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * * + * ************************************************************************* + */ + +#ifndef JABBERFILETRANSFER_H +#define JABBERFILETRANSFER_H + +#include +#include + +class QString; +class JabberAccount; +namespace Kopete { class Transfer; } +namespace Kopete { class FileTransferInfo; } +class JabberBaseContact; + +class JabberFileTransfer : public QObject +{ + +Q_OBJECT + +public: + /** + * Constructor for an incoming transfer + */ + JabberFileTransfer ( JabberAccount *account, XMPP::FileTransfer *incomingTransfer ); + + /** + * Constructor for an outgoing transfer + */ + JabberFileTransfer ( JabberAccount *account, JabberBaseContact *contact, const QString &file ); + + ~JabberFileTransfer (); + +private slots: + void slotIncomingTransferAccepted ( Kopete::Transfer *transfer, const QString &fileName ); + void slotTransferRefused ( const Kopete::FileTransferInfo &transfer ); + void slotTransferResult (); + void slotTransferError ( int errorCode ); + + void slotOutgoingConnected (); + void slotOutgoingBytesWritten ( int nrWritten ); + + void slotIncomingDataReady ( const QByteArray &data ); + +private: + void initializeVariables (); + + JabberAccount *mAccount; + XMPP::FileTransfer *mXMPPTransfer; + Kopete::Transfer *mKopeteTransfer; + QFile mLocalFile; + int mTransferId; + Q_LLONG mBytesTransferred; + Q_LLONG mBytesToTransfer; + +}; + +#endif + +// vim: set noet ts=4 sts=4 tw=4: diff --git a/kopete/protocols/jabber/jabberformlineedit.cpp b/kopete/protocols/jabber/jabberformlineedit.cpp new file mode 100644 index 00000000..04187b20 --- /dev/null +++ b/kopete/protocols/jabber/jabberformlineedit.cpp @@ -0,0 +1,58 @@ + /* + * jabberformlineedit.cpp + * + * Copyright (c) 2002-2003 by Till Gerken + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * * + * ************************************************************************* + */ + +#include "jabberformlineedit.h" + +JabberFormLineEdit::JabberFormLineEdit (const int type, const QString & realName, const QString & value, QWidget * parent, const char *name):QLineEdit (value, + parent, + name) +{ + + fieldType = type; + fieldName = realName; + +} + +void JabberFormLineEdit::slotGatherData (XMPP::Form & form) +{ + + form += XMPP::FormField (fieldName, text ()); + +} + +JabberFormLineEdit::~JabberFormLineEdit () +{ +} + +JabberFormPasswordEdit::JabberFormPasswordEdit (const int type, const QString & realName, const QString & value, QWidget * parent, const char *name):KPasswordEdit(parent, name) +{ + + setText(value); + fieldType = type; + fieldName = realName; + +} + +void JabberFormPasswordEdit::slotGatherData (XMPP::Form & form) +{ + + form += XMPP::FormField (fieldName, password()); + +} + + +#include "jabberformlineedit.moc" diff --git a/kopete/protocols/jabber/jabberformlineedit.h b/kopete/protocols/jabber/jabberformlineedit.h new file mode 100644 index 00000000..770bab39 --- /dev/null +++ b/kopete/protocols/jabber/jabberformlineedit.h @@ -0,0 +1,59 @@ + /* + * jabberformlineedit.h + * + * Copyright (c) 2002-2003 by Till Gerken + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * * + * ************************************************************************* + */ + +#ifndef JABBERFORMLINEEDIT_H +#define JABBERFORMLINEEDIT_H + +#include +#include +#include + +#include "xmpp_tasks.h" + +/** + *@author Till Gerken + */ + +class JabberFormLineEdit:public QLineEdit +{ + + Q_OBJECT public: + JabberFormLineEdit (const int type, const QString & realName, const QString & value, QWidget * parent = 0, const char *name = 0); + ~JabberFormLineEdit (); + + public slots:void slotGatherData (XMPP::Form & form); + + private: + int fieldType; + QString fieldName; + +}; + +class JabberFormPasswordEdit:public KPasswordEdit +{ + + Q_OBJECT public: + JabberFormPasswordEdit(const int type, const QString & realName, const QString & value, QWidget * parent = 0, const char *name = 0); + + public slots:void slotGatherData (XMPP::Form & form); + + private: + int fieldType; + QString fieldName; + +}; +#endif diff --git a/kopete/protocols/jabber/jabberformtranslator.cpp b/kopete/protocols/jabber/jabberformtranslator.cpp new file mode 100644 index 00000000..fe6ec230 --- /dev/null +++ b/kopete/protocols/jabber/jabberformtranslator.cpp @@ -0,0 +1,91 @@ + /* + * jabberformtranslator.cpp + * + * Copyright (c) 2002-2003 by Till Gerken + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * * + * ************************************************************************* + */ + +#include + +#include + +#include "jabberformlineedit.h" +#include "jabberformtranslator.h" + +JabberFormTranslator::JabberFormTranslator (const XMPP::Form & form, QWidget * parent, const char *name):QWidget (parent, name) +{ + /* Copy basic form values. */ + privForm.setJid (form.jid ()); + privForm.setInstructions (form.instructions ()); + privForm.setKey (form.key ()); + + emptyForm = privForm; + + /* Add instructions to layout. */ + QVBoxLayout *innerLayout = new QVBoxLayout (this, 0, 4); + + QLabel *label = new QLabel (form.instructions (), this, "InstructionLabel"); + label->setAlignment (int (QLabel::WordBreak | QLabel::AlignVCenter)); + label->setSizePolicy(QSizePolicy::Minimum,QSizePolicy::Fixed, true); + label->show (); + + innerLayout->addWidget (label, 0); + + QGridLayout *formLayout = new QGridLayout (innerLayout, form.count (), 2); + + int row = 1; + XMPP::Form::const_iterator formEnd = form.end (); + for (XMPP::Form::const_iterator it = form.begin (); it != formEnd; ++it, ++row) + { + kdDebug (14130) << "[JabberFormTranslator] Adding field realName()==" << + (*it).realName () << ", fieldName()==" << (*it).fieldName () << " to the dialog" << endl; + + label = new QLabel ((*it).fieldName (), this, (*it).fieldName ().latin1 ()); + formLayout->addWidget (label, row, 0); + label->show (); + + QLineEdit *edit; + if ((*it).type() == XMPP::FormField::password) + { + edit = new JabberFormPasswordEdit((*it).type (), (*it).realName (), (*it).value (), this); + } + else + { + edit = new JabberFormLineEdit ((*it).type (), (*it).realName (), + (*it).value (), this); + } + formLayout->addWidget (edit, row, 1); + edit->show (); + + connect (this, SIGNAL (gatherData (XMPP::Form &)), edit, SLOT (slotGatherData (XMPP::Form &))); + } + + innerLayout->addStretch (); +} + +XMPP::Form & JabberFormTranslator::resultData () +{ + // clear form data + privForm = emptyForm; + + // let all line edit fields write into our form + emit gatherData (privForm); + + return privForm; +} + +JabberFormTranslator::~JabberFormTranslator () +{ +} + +#include "jabberformtranslator.moc" diff --git a/kopete/protocols/jabber/jabberformtranslator.h b/kopete/protocols/jabber/jabberformtranslator.h new file mode 100644 index 00000000..d9cf4044 --- /dev/null +++ b/kopete/protocols/jabber/jabberformtranslator.h @@ -0,0 +1,49 @@ + /* + * jabberformtranslator.h + * + * Copyright (c) 2002-2003 by Till Gerken + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * * + * ************************************************************************* + */ + +#ifndef JABBERFORMTRANSLATOR_H +#define JABBERFORMTRANSLATOR_H + +#include +#include + +#include "xmpp_tasks.h" + +/** + *@author Till Gerken + */ + +class JabberFormTranslator:public QWidget +{ + +Q_OBJECT + +public: + JabberFormTranslator (const XMPP::Form & form, QWidget * parent = 0, const char *name = 0); + ~JabberFormTranslator (); + + XMPP::Form & resultData (); + +signals: + void gatherData (XMPP::Form & form); + +private: + XMPP::Form emptyForm, privForm; + +}; + +#endif diff --git a/kopete/protocols/jabber/jabbergroupchatmanager.cpp b/kopete/protocols/jabber/jabbergroupchatmanager.cpp new file mode 100644 index 00000000..7686ba8c --- /dev/null +++ b/kopete/protocols/jabber/jabbergroupchatmanager.cpp @@ -0,0 +1,163 @@ +/* + jabbergroupchatmanager.cpp - Jabber Message Manager for group chats + + Copyright (c) 2004 by Till Gerken + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "jabbergroupchatmanager.h" + +#include +#include +#include +#include "kopetechatsessionmanager.h" +#include "kopeteview.h" +#include "jabberprotocol.h" +#include "jabberaccount.h" +#include "jabberclient.h" +#include "jabbercontact.h" + +JabberGroupChatManager::JabberGroupChatManager ( JabberProtocol *protocol, const JabberBaseContact *user, + Kopete::ContactPtrList others, XMPP::Jid roomJid, const char *name ) + : Kopete::ChatSession ( user, others, protocol, name ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "New message manager for " << user->contactId () << endl; + + mRoomJid = roomJid; + + setMayInvite( true ); + + // make sure Kopete knows about this instance + Kopete::ChatSessionManager::self()->registerChatSession ( this ); + + connect ( this, SIGNAL ( messageSent ( Kopete::Message &, Kopete::ChatSession * ) ), + this, SLOT ( slotMessageSent ( Kopete::Message &, Kopete::ChatSession * ) ) ); + + updateDisplayName (); +} + +JabberGroupChatManager::~JabberGroupChatManager() +{ +} + +void JabberGroupChatManager::updateDisplayName () +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << endl; + + setDisplayName ( mRoomJid.full () ); + +} + +const JabberBaseContact *JabberGroupChatManager::user () const +{ + + return static_cast(Kopete::ChatSession::myself()); + +} + +JabberAccount *JabberGroupChatManager::account () const +{ + + return user()->account(); + +} + +void JabberGroupChatManager::slotMessageSent ( Kopete::Message &message, Kopete::ChatSession * ) +{ + + if( account()->isConnected () ) + { + XMPP::Message jabberMessage; + + jabberMessage.setFrom ( account()->client()->jid() ); + + + XMPP::Jid toJid ( mRoomJid ); + + jabberMessage.setTo ( toJid ); + + jabberMessage.setSubject ( message.subject () ); + jabberMessage.setTimeStamp ( message.timestamp () ); + + if ( message.plainBody().find ( "-----BEGIN PGP MESSAGE-----" ) != -1 ) + { + /* + * This message is encrypted, so we need to set + * a fake body indicating that this is an encrypted + * message (for clients not implementing this + * functionality) and then generate the encrypted + * payload out of the old message body. + */ + + // please don't translate the following string + jabberMessage.setBody ( i18n ( "This message is encrypted." ) ); + + QString encryptedBody = message.plainBody (); + + // remove PGP header and footer from message + encryptedBody.truncate ( encryptedBody.length () - QString("-----END PGP MESSAGE-----").length () - 2 ); + encryptedBody = encryptedBody.right ( encryptedBody.length () - encryptedBody.find ( "\n\n" ) - 2 ); + + // assign payload to message + jabberMessage.setXEncrypted ( encryptedBody ); + } + else + { + // this message is not encrypted + jabberMessage.setBody ( message.plainBody () ); + } + + jabberMessage.setType ( "groupchat" ); + + // send the message + account()->client()->sendMessage ( jabberMessage ); + + // tell the manager that we sent successfully + messageSucceeded (); + } + else + { + account()->errorConnectFirst (); + + // FIXME: there is no messageFailed() yet, + // but we need to stop the animation etc. + messageSucceeded (); + } +} + +void JabberGroupChatManager::inviteContact( const QString & contactId ) +{ + if( account()->isConnected () ) + { + //NOTE: this is the obsolete, NOT RECOMMANDED protocol. + // iris doesn't implement groupchat yet + XMPP::Message jabberMessage; + jabberMessage.setFrom ( account()->client()->jid() ); + jabberMessage.setTo ( contactId ); + jabberMessage.setInvite( mRoomJid.userHost() ); + jabberMessage.setBody( i18n("You have been invited to %1").arg( mRoomJid.userHost() ) ); + + // send the message + account()->client()->sendMessage ( jabberMessage ); + } + else + { + account()->errorConnectFirst (); + } +} + + +#include "jabbergroupchatmanager.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/jabber/jabbergroupchatmanager.h b/kopete/protocols/jabber/jabbergroupchatmanager.h new file mode 100644 index 00000000..96c689d0 --- /dev/null +++ b/kopete/protocols/jabber/jabbergroupchatmanager.h @@ -0,0 +1,78 @@ +/* + jabbergroupchatmanager.h - Jabber Message Manager for group chats + + Copyright (c) 2004 by Till Gerken + + Kopete (c) 2004 by the Kopete developers + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#ifndef JABBERGROUPCHATMANAGER_H +#define JABBERGROUPCHATMANAGER_H + +#include "kopetechatsession.h" +#include "xmpp.h" + +class JabberProtocol; +class JabberAccount; +class JabberBaseContact; +namespace Kopete { class Message; } +class QString; + +/** + * @author Till Gerken + */ +class JabberGroupChatManager : public Kopete::ChatSession +{ + Q_OBJECT + +public: + JabberGroupChatManager ( JabberProtocol *protocol, const JabberBaseContact *user, + Kopete::ContactPtrList others, XMPP::Jid roomJid, const char *name = 0 ); + + ~JabberGroupChatManager(); + + /** + * @brief Get the local user in the session + * @return the local user in the session, same as account()->myself() + */ + const JabberBaseContact *user () const; + + /** + * @brief get the account + * @return the account + */ + JabberAccount *account() const ; + + /** + * Re-generate the display name + */ + void updateDisplayName (); + + /** + * reimplemented from Kopete::ChatSession + * called when a contact is droped in the window + */ + virtual void inviteContact(const QString &contactId); + +private slots: + void slotMessageSent ( Kopete::Message &message, Kopete::ChatSession *kmm ); + + + +private: + XMPP::Jid mRoomJid; +}; + +#endif + +// vim: set noet ts=4 sts=4 tw=4: + diff --git a/kopete/protocols/jabber/jabbergroupcontact.cpp b/kopete/protocols/jabber/jabbergroupcontact.cpp new file mode 100644 index 00000000..83d69ab9 --- /dev/null +++ b/kopete/protocols/jabber/jabbergroupcontact.cpp @@ -0,0 +1,378 @@ + /* + * jabbercontact.cpp - Regular Kopete Jabber protocol contact + * + * Copyright (c) 2002-2004 by Till Gerken + * Copyright (c) 2006 by Olivier Goffart + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * * + * ************************************************************************* + */ + +#include "jabbergroupcontact.h" + +#include +#include +#include +#include +#include "jabberprotocol.h" +#include "jabberaccount.h" +#include "jabberclient.h" +#include "jabberfiletransfer.h" +#include "jabbergroupchatmanager.h" +#include "jabbergroupmembercontact.h" +#include "jabbercontactpool.h" +#include "kopetemetacontact.h" +#include "xmpp_tasks.h" + +/** + * JabberGroupContact constructor + */ +JabberGroupContact::JabberGroupContact (const XMPP::RosterItem &rosterItem, JabberAccount *account, Kopete::MetaContact * mc) + : JabberBaseContact ( XMPP::RosterItem ( rosterItem.jid().userHost () ), account, mc) , mNick( rosterItem.jid().resource() ) +{ + setIcon( "jabber_group" ); + + // initialize here, we need it set before we instantiate the manager below + mManager = 0; + + setFileCapable ( false ); + + /** + * Add our own nick as first subcontact (we need to do that here + * because we need to set this contact as myself() of the message + * manager). + */ + mSelfContact = addSubContact ( rosterItem ); + + /** + * Instantiate a new message manager without members. + */ + mManager = new JabberGroupChatManager ( protocol (), mSelfContact, + Kopete::ContactPtrList (), XMPP::Jid ( rosterItem.jid().userHost () ) ); + + connect ( mManager, SIGNAL ( closing ( Kopete::ChatSession* ) ), this, SLOT ( slotChatSessionDeleted () ) ); + + connect ( account->myself() , SIGNAL(onlineStatusChanged( Kopete::Contact*, const Kopete::OnlineStatus&, const Kopete::OnlineStatus& ) ) , + this , SLOT(slotStatusChanged() ) ) ; + + /** + * FIXME: The first contact in the list of the message manager + * needs to be our own contact. This is a flaw in the Kopete + * API because it can't deal with group chat properly. + * If we are alone in a room, we are myself() already and members() + * is empty. This makes at least the history plugin crash. + */ + mManager->addContact ( this ); + + + + /** + * Let's construct the window: + * otherwise, the ref count of maznager is equal to zero. + * and if we receive a message before the window is shown, + * it will be deleted and we will be out of the channel + * In all case, there are no reason to don't show it. + */ + mManager->view( true , "kopete_chatwindow" ); +} + +JabberGroupContact::~JabberGroupContact () +{ + + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << endl; + + if(mManager) + { + mManager->deleteLater(); + } + + for ( Kopete::Contact *contact = mContactList.first (); contact; contact = mContactList.next () ) + { + /*if(mManager) + mManager->removeContact( contact );*/ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Deleting KC " << contact->contactId () << endl; + contact->deleteLater(); + } + + for ( Kopete::MetaContact *metaContact = mMetaContactList.first (); metaContact; metaContact = mMetaContactList.next () ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Deleting KMC " << metaContact->metaContactId () << endl; + metaContact->deleteLater(); + } +} + +QPtrList *JabberGroupContact::customContextMenuActions () +{ + QPtrList *actionCollection = new QPtrList(); + + KAction *actionSetNick = new KAction (i18n ("Change nick name"), 0, 0, this, SLOT (slotChangeNick()), this, "jabber_changenick"); + actionCollection->append( actionSetNick ); + + return actionCollection; +} + +Kopete::ChatSession *JabberGroupContact::manager ( Kopete::Contact::CanCreateFlags canCreate ) +{ + if(!mManager && canCreate == Kopete::Contact::CanCreate) + { + kdWarning (JABBER_DEBUG_GLOBAL) << k_funcinfo << "somehow, the chat manager was removed, and the contact is still there" << endl; + mManager = new JabberGroupChatManager ( protocol (), mSelfContact, + Kopete::ContactPtrList (), XMPP::Jid ( rosterItem().jid().userHost() ) ); + + mManager->addContact ( this ); + + connect ( mManager, SIGNAL ( closing ( Kopete::ChatSession* ) ), this, SLOT ( slotChatSessionDeleted () ) ); + + //if we have to recreate the manager, we probably have to connect again to the chat. + slotStatusChanged(); + } + return mManager; + +} + +void JabberGroupContact::handleIncomingMessage (const XMPP::Message & message) +{ + // message type is always chat in a groupchat + QString viewType = "kopete_chatwindow"; + Kopete::Message *newMessage = 0L; + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Received a message" << endl; + + /** + * Don't display empty messages, these were most likely just carrying + * event notifications or other payload. + */ + if ( message.body().isEmpty () ) + return; + + manager(CanCreate); //force to create mManager + + Kopete::ContactPtrList contactList = manager()->members(); + + // check for errors + if ( message.type () == "error" ) + { + newMessage = new Kopete::Message( message.timeStamp (), this, contactList, + i18n("Your message could not be delivered: \"%1\", Reason: \"%2\""). + arg ( message.body () ).arg ( message.error().text ), + message.subject(), Kopete::Message::Inbound, Kopete::Message::PlainText, viewType ); + } + else + { + // retrieve and reformat body + QString body = message.body (); + + if( !message.xencrypted().isEmpty () ) + { + body = QString ("-----BEGIN PGP MESSAGE-----\n\n") + message.xencrypted () + QString ("\n-----END PGP MESSAGE-----\n"); + } + + // locate the originating contact + JabberBaseContact *subContact = account()->contactPool()->findExactMatch ( message.from () ); + + if ( !subContact ) + { + kdWarning (JABBER_DEBUG_GLOBAL) << k_funcinfo << "the contact is not in the list : " << message.from().full()<< endl; + + /** + * We couldn't find the contact for this message. That most likely means + * that it originated from a history backlog or something similar and + * the sending person is not in the channel anymore. We need to create + * a new contact for this which does not show up in the manager. + */ + subContact = addSubContact ( XMPP::RosterItem ( message.from () ), false ); + } + + // convert XMPP::Message into Kopete::Message + newMessage = new Kopete::Message ( message.timeStamp (), subContact, contactList, body, + message.subject (), + subContact != mManager->myself() ? Kopete::Message::Inbound : Kopete::Message::Outbound, + Kopete::Message::PlainText, viewType ); + } + + // append message to manager + mManager->appendMessage ( *newMessage ); + + delete newMessage; + +} + +JabberBaseContact *JabberGroupContact::addSubContact ( const XMPP::RosterItem &rosterItem, bool addToManager ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Adding new subcontact " << rosterItem.jid().full () << " to room " << mRosterItem.jid().full () << endl; + + // see if this contact already exists, skip creation otherwise + JabberBaseContact *subContact = dynamic_cast( account()->contactPool()->findExactMatch ( rosterItem.jid () ) ); + + if ( subContact ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Contact already exists, not adding again." << endl; + return subContact; + } + + // Create new meta contact that holds the group chat contact. + Kopete::MetaContact *metaContact = new Kopete::MetaContact (); + metaContact->setTemporary ( true ); + mMetaContactList.append ( metaContact ); + + // now add contact to the pool, no dirty flag + subContact = account()->contactPool()->addGroupContact ( rosterItem, false, metaContact, false ); + + /** + * Add the contact to our message manager first. We need + * to check the pointer for validity, because this method + * gets called from the constructor, where the manager + * does not exist yet. + */ + if ( mManager && addToManager ) + mManager->addContact ( subContact ); + + // now, add the contact also to our own list + mContactList.append ( subContact ); + + connect(subContact , SIGNAL(contactDestroyed(Kopete::Contact*)) , this , SLOT(slotSubContactDestroyed(Kopete::Contact*))); + + return subContact; + +} + +void JabberGroupContact::removeSubContact ( const XMPP::RosterItem &rosterItem ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Removing subcontact " << rosterItem.jid().full () << " from room " << mRosterItem.jid().full () << endl; + + // make sure that subcontacts are only removed from the room contact, which has no resource + if ( !mRosterItem.jid().resource().isEmpty () ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "WARNING: Trying to remove subcontact from subcontact!" << endl; + return; + } + + // find contact in the pool + JabberGroupMemberContact *subContact = dynamic_cast( account()->contactPool()->findExactMatch ( rosterItem.jid () ) ); + + if ( !subContact ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "WARNING: Subcontact couldn't be located!" << endl; + return; + } + + if(mManager && subContact->contactId() == mManager->myself()->contactId() ) + { + //HACK WORKAROUND FIXME KDE4 + //impossible to remove myself, or we will die + //subContact->setNickName( mNick ); //this is even worse than nothing + return; + } + + // remove the contact from the message manager first + if(mManager) + mManager->removeContact ( subContact ); + + // remove the contact's meta contact from our internal list + mMetaContactList.remove ( subContact->metaContact () ); + + // remove the contact from our internal list + mContactList.remove ( subContact ); + + // delete the meta contact first + delete subContact->metaContact (); + + // finally, delete the contact itself from the pool + account()->contactPool()->removeContact ( rosterItem.jid () ); + +} + +void JabberGroupContact::sendFile ( const KURL &sourceURL, const QString &/*fileName*/, uint /*fileSize*/ ) +{ + QString filePath; + + // if the file location is null, then get it from a file open dialog + if ( !sourceURL.isValid () ) + filePath = KFileDialog::getOpenFileName( QString::null , "*", 0L, i18n ( "Kopete File Transfer" ) ); + else + filePath = sourceURL.path(-1); + + QFile file ( filePath ); + + if ( file.exists () ) + { + // send the file + new JabberFileTransfer ( account (), this, filePath ); + } + +} + +void JabberGroupContact::slotChatSessionDeleted () +{ + + mManager = 0; + + if ( account()->isConnected () ) + { + account()->client()->leaveGroupChat ( mRosterItem.jid().host (), mRosterItem.jid().user () ); + } + + //deleteLater(); //we will be deleted later when the the account will know we have left + +} + +void JabberGroupContact::slotStatusChanged( ) +{ + if( !account()->isConnected() ) + { + //we need to remove all contact, because when we connect again, we will not receive the notificaion they are gone. + QPtrList copy_contactlist=mContactList; + for ( Kopete::Contact *contact = copy_contactlist.first (); contact; contact = copy_contactlist.next () ) + { + removeSubContact( XMPP::Jid(contact->contactId()) ); + } + return; + } + + + if( !isOnline() ) + { + //HACK WORKAROUND XMPP::client->d->groupChatList must contains us. + account()->client()->joinGroupChat( rosterItem().jid().host() , rosterItem().jid().user() , mNick ); + } + + //TODO: away message + XMPP::Status newStatus = account()->protocol()->kosToStatus( account()->myself()->onlineStatus() ); + account()->client()->setGroupChatStatus( rosterItem().jid().host() , rosterItem().jid().user() , newStatus ); +} + +void JabberGroupContact::slotChangeNick( ) +{ + + bool ok; + QString futureNewNickName = KInputDialog::getText( i18n( "Change nickanme - Jabber Plugin" ), + i18n( "Please enter the new nick name you want to have on the room %1" ).arg(rosterItem().jid().userHost()), + mNick, &ok ); + if ( !ok || !account()->isConnected()) + return; + + mNick=futureNewNickName; + + XMPP::Status status = account()->protocol()->kosToStatus( account()->myself()->onlineStatus() ); + account()->client()->changeGroupChatNick( rosterItem().jid().host() , rosterItem().jid().user() , mNick , status); + +} + +void JabberGroupContact::slotSubContactDestroyed( Kopete::Contact * deadContact ) +{ + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "cleaning dead subcontact " << deadContact->contactId() << " from room " << mRosterItem.jid().full () << endl; + + mMetaContactList.remove ( deadContact->metaContact () ); + mContactList.remove ( deadContact ); + +} + +#include "jabbergroupcontact.moc" diff --git a/kopete/protocols/jabber/jabbergroupcontact.h b/kopete/protocols/jabber/jabbergroupcontact.h new file mode 100644 index 00000000..c157b823 --- /dev/null +++ b/kopete/protocols/jabber/jabbergroupcontact.h @@ -0,0 +1,107 @@ + /* + * jabbercontact.cpp - Kopete Jabber protocol groupchat contact + * + * Copyright (c) 2002-2004 by Till Gerken + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * * + * ************************************************************************* + */ + +#ifndef JABBERGROUPCONTACT_H +#define JABBERGROUPCONTACT_H + +#include "jabberbasecontact.h" + +namespace Kopete { class MetaContact; } +class JabberGroupChatManager; + +class JabberGroupContact : public JabberBaseContact +{ + +Q_OBJECT + +public: + + JabberGroupContact (const XMPP::RosterItem &rosterItem, + JabberAccount *account, Kopete::MetaContact * mc); + + ~JabberGroupContact (); + + /** + * Create custom context menu items for the contact + * FIXME: implement manager version here? + */ + QPtrList *customContextMenuActions (); + + /** + * Deal with an incoming message for this contact. + */ + void handleIncomingMessage ( const XMPP::Message &message ); + + /** + * Add a contact to this room. + */ + JabberBaseContact *addSubContact ( const XMPP::RosterItem &rosterItem, bool addToManager = true ); + + /** + * Remove a contact from this room. + */ + void removeSubContact ( const XMPP::RosterItem &rosterItem ); + + Kopete::ChatSession *manager ( Kopete::Contact::CanCreateFlags canCreate = Kopete::Contact::CannotCreate ); + +public slots: + + /** + * This is the JabberContact level slot for sending files. + * + * @param sourceURL The actual KURL of the file you are sending + * @param fileName (Optional) An alternate name for the file - what the + * receiver will see + * @param fileSize (Optional) Size of the file being sent. Used when sending + * a nondeterminate file size (such as over a socket) + */ + virtual void sendFile( const KURL &sourceURL = KURL(), + const QString &fileName = QString::null, uint fileSize = 0L ); + +private slots: + + /** + * Catch a dying message manager and leave the room. + */ + void slotChatSessionDeleted (); + + /** + * When our own status change, we need to manually send the presence. + */ + void slotStatusChanged(); + + /** + * ask the user to change the nick, and change it + */ + void slotChangeNick(); + + /** + * a subcontact has been destroyed (may happen when closing kopete) + */ + void slotSubContactDestroyed(Kopete::Contact*); + +private: + + QPtrList mContactList; + QPtrList mMetaContactList; + + JabberGroupChatManager *mManager; + JabberBaseContact *mSelfContact; + QString mNick; +}; + +#endif diff --git a/kopete/protocols/jabber/jabbergroupmembercontact.cpp b/kopete/protocols/jabber/jabbergroupmembercontact.cpp new file mode 100644 index 00000000..2e86b898 --- /dev/null +++ b/kopete/protocols/jabber/jabbergroupmembercontact.cpp @@ -0,0 +1,168 @@ + /* + * jabbergroupmembercontact.cpp - Regular Kopete Jabber protocol contact + * + * Copyright (c) 2002-2004 by Till Gerken + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * * + * ************************************************************************* + */ + +#include "jabbergroupmembercontact.h" + +#include +#include +#include +#include "jabberprotocol.h" +#include "jabberaccount.h" +#include "jabberfiletransfer.h" +#include "jabbergroupchatmanager.h" +#include "jabberchatsession.h" +#include "jabbercontactpool.h" +#include "kopetemetacontact.h" + +/** + * JabberGroupMemberContact constructor + */ +JabberGroupMemberContact::JabberGroupMemberContact (const XMPP::RosterItem &rosterItem, + JabberAccount *account, Kopete::MetaContact * mc) + : JabberBaseContact ( rosterItem, account, mc) +{ + + mc->setDisplayName ( rosterItem.jid().resource() ); + setNickName ( rosterItem.jid().resource() ); + + setFileCapable ( true ); + + mManager = 0; + +} + +/** + * JabberGroupMemberContact destructor + */ +JabberGroupMemberContact::~JabberGroupMemberContact () +{ + if(mManager) + { + mManager->deleteLater(); + } +} + +QPtrList *JabberGroupMemberContact::customContextMenuActions () +{ + + return 0; + +} + +Kopete::ChatSession *JabberGroupMemberContact::manager ( Kopete::Contact::CanCreateFlags canCreate ) +{ + + if ( mManager ) + return mManager; + + if ( !mManager && !canCreate ) + return 0; + + Kopete::ContactPtrList chatMembers; + chatMembers.append ( this ); + + /* + * FIXME: We might have to use the group chat contact here instead of + * the global myself() instance for a correct representation. + */ + mManager = new JabberChatSession ( protocol(), static_cast(account()->myself()), chatMembers ); + connect ( mManager, SIGNAL ( destroyed ( QObject * ) ), this, SLOT ( slotChatSessionDeleted () ) ); + + return mManager; + +} + +void JabberGroupMemberContact::slotChatSessionDeleted () +{ + + mManager = 0; + +} + +void JabberGroupMemberContact::handleIncomingMessage ( const XMPP::Message &message ) +{ + // message type is always chat in a groupchat + QString viewType = "kopete_chatwindow"; + Kopete::Message *newMessage = 0L; + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Received Message Type:" << message.type () << endl; + + /** + * Don't display empty messages, these were most likely just carrying + * event notifications or other payload. + */ + if ( message.body().isEmpty () ) + return; + + Kopete::ChatSession *kmm = manager( Kopete::Contact::CanCreate ); + if(!kmm) + return; + Kopete::ContactPtrList contactList = kmm->members(); + + // check for errors + if ( message.type () == "error" ) + { + newMessage = new Kopete::Message( message.timeStamp (), this, contactList, + i18n("Your message could not be delivered: \"%1\", Reason: \"%2\""). + arg ( message.body () ).arg ( message.error().text ), + message.subject(), Kopete::Message::Inbound, Kopete::Message::PlainText, viewType ); + } + else + { + // retrieve and reformat body + QString body = message.body (); + + if( !message.xencrypted().isEmpty () ) + { + body = QString ("-----BEGIN PGP MESSAGE-----\n\n") + message.xencrypted () + QString ("\n-----END PGP MESSAGE-----\n"); + } + + // convert XMPP::Message into Kopete::Message + newMessage = new Kopete::Message ( message.timeStamp (), this, contactList, body, + message.subject (), Kopete::Message::Inbound, + Kopete::Message::PlainText, viewType ); + } + + // append message to manager + kmm->appendMessage ( *newMessage ); + + delete newMessage; + +} + +void JabberGroupMemberContact::sendFile ( const KURL &sourceURL, const QString &/*fileName*/, uint /*fileSize*/ ) +{ + QString filePath; + + // if the file location is null, then get it from a file open dialog + if ( !sourceURL.isValid () ) + filePath = KFileDialog::getOpenFileName( QString::null , "*", 0L, i18n ( "Kopete File Transfer" ) ); + else + filePath = sourceURL.path(-1); + + QFile file ( filePath ); + + if ( file.exists () ) + { + // send the file + new JabberFileTransfer ( account (), this, filePath ); + } + +} + + +#include "jabbergroupmembercontact.moc" diff --git a/kopete/protocols/jabber/jabbergroupmembercontact.h b/kopete/protocols/jabber/jabbergroupmembercontact.h new file mode 100644 index 00000000..d4ec5b06 --- /dev/null +++ b/kopete/protocols/jabber/jabbergroupmembercontact.h @@ -0,0 +1,80 @@ + /* + * jabbergroupmembercontact.cpp - Kopete Jabber protocol groupchat contact (member) + * + * Copyright (c) 2002-2004 by Till Gerken + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * * + * ************************************************************************* + */ + +#ifndef JABBERGROUPMEMBERCONTACT_H +#define JABBERGROUPMEMBERCONTACT_H + +#include "jabberbasecontact.h" + +namespace Kopete { class MetaContact; } +class JabberGroupChatManager; +class JabberChatSession; + +class JabberGroupMemberContact : public JabberBaseContact +{ + +Q_OBJECT + +public: + + JabberGroupMemberContact (const XMPP::RosterItem &rosterItem, + JabberAccount *account, Kopete::MetaContact * mc); + + ~JabberGroupMemberContact (); + + /** + * Create custom context menu items for the contact + * FIXME: implement manager version here? + */ + QPtrList *customContextMenuActions (); + + /** + * Return message manager for this instance. + */ + Kopete::ChatSession *manager ( Kopete::Contact::CanCreateFlags canCreate = Kopete::Contact::CannotCreate ); + + /** + * Deal with incoming messages. + */ + void handleIncomingMessage ( const XMPP::Message &message ); + +public slots: + + /** + * This is the JabberContact level slot for sending files. + * + * @param sourceURL The actual KURL of the file you are sending + * @param fileName (Optional) An alternate name for the file - what the + * receiver will see + * @param fileSize (Optional) Size of the file being sent. Used when sending + * a nondeterminate file size (such as over a socket) + */ + virtual void sendFile( const KURL &sourceURL = KURL(), + const QString &fileName = QString::null, uint fileSize = 0L ); + +private slots: + /** + * Catch a dying message manager + */ + void slotChatSessionDeleted (); + +private: + JabberChatSession *mManager; + +}; + +#endif diff --git a/kopete/protocols/jabber/jabberprotocol.cpp b/kopete/protocols/jabber/jabberprotocol.cpp new file mode 100644 index 00000000..ea2e8039 --- /dev/null +++ b/kopete/protocols/jabber/jabberprotocol.cpp @@ -0,0 +1,345 @@ + /* + * jabberprotocol.cpp - Base class for the Kopete Jabber protocol + * + * Copyright (c) 2002-2003 by Till Gerken + * Copyright (c) 2002 by Daniel Stone + * Copyright (c) 2006 by Olivier Goffart + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * * + * ************************************************************************* + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "im.h" +#include "xmpp.h" + +#include + +#include "kopetecontact.h" +#include "kopetecontactlist.h" +#include "kopetemetacontact.h" +#include "kopetechatsession.h" +#include "kopeteonlinestatusmanager.h" +#include "kopeteaway.h" +#include "kopeteglobal.h" +#include "kopeteprotocol.h" +#include "kopeteplugin.h" +#include "kopeteaccountmanager.h" +#include "addcontactpage.h" +#include "kopetecommandhandler.h" + +#include "jabbercontact.h" +#include "jabberaddcontactpage.h" +#include "jabberprotocol.h" +#include "jabberaccount.h" +#include "jabbereditaccountwidget.h" +#include "jabbercapabilitiesmanager.h" +#include "jabbertransport.h" +#include "dlgjabbersendraw.h" +#include "dlgjabberservices.h" +#include "dlgjabberchatjoin.h" +#include "dlgjabberregister.h" + +JabberProtocol *JabberProtocol::protocolInstance = 0; + +typedef KGenericFactory JabberProtocolFactory; + +K_EXPORT_COMPONENT_FACTORY( kopete_jabber, JabberProtocolFactory( "kopete_jabber" ) ) + +JabberProtocol::JabberProtocol (QObject * parent, const char *name, const QStringList &) +: Kopete::Protocol( JabberProtocolFactory::instance(), parent, name ), + JabberKOSChatty(Kopete::OnlineStatus::Online, 100, this, JabberFreeForChat, "jabber_chatty", i18n ("Free for Chat"), i18n ("Free for Chat"), Kopete::OnlineStatusManager::FreeForChat, Kopete::OnlineStatusManager::HasAwayMessage ), + JabberKOSOnline(Kopete::OnlineStatus::Online, 90, this, JabberOnline, QString::null, i18n ("Online"), i18n ("Online"), Kopete::OnlineStatusManager::Online, Kopete::OnlineStatusManager::HasAwayMessage ), + JabberKOSAway(Kopete::OnlineStatus::Away, 80, this, JabberAway, "contact_away_overlay", i18n ("Away"), i18n ("Away"), Kopete::OnlineStatusManager::Away, Kopete::OnlineStatusManager::HasAwayMessage), + JabberKOSXA(Kopete::OnlineStatus::Away, 70, this, JabberXA, "contact_xa_overlay", i18n ("Extended Away"), i18n ("Extended Away"), 0, Kopete::OnlineStatusManager::HasAwayMessage), + JabberKOSDND(Kopete::OnlineStatus::Away, 60, this, JabberDND, "contact_busy_overlay", i18n ("Do not Disturb"), i18n ("Do not Disturb"), Kopete::OnlineStatusManager::Busy, Kopete::OnlineStatusManager::HasAwayMessage), + JabberKOSOffline(Kopete::OnlineStatus::Offline, 50, this, JabberOffline, QString::null, i18n ("Offline") ,i18n ("Offline"), Kopete::OnlineStatusManager::Offline, Kopete::OnlineStatusManager::HasAwayMessage ), + JabberKOSInvisible(Kopete::OnlineStatus::Invisible, 40, this, JabberInvisible, "contact_invisible_overlay", i18n ("Invisible") ,i18n ("Invisible"), Kopete::OnlineStatusManager::Invisible), + JabberKOSConnecting(Kopete::OnlineStatus::Connecting, 30, this, JabberConnecting, "jabber_connecting", i18n("Connecting")), + propLastSeen(Kopete::Global::Properties::self()->lastSeen()), + propAwayMessage(Kopete::Global::Properties::self()->awayMessage()), + propFirstName(Kopete::Global::Properties::self()->firstName()), + propLastName(Kopete::Global::Properties::self()->lastName()), + propFullName(Kopete::Global::Properties::self()->fullName()), + propEmailAddress(Kopete::Global::Properties::self()->emailAddress()), + propPrivatePhone(Kopete::Global::Properties::self()->privatePhone()), + propPrivateMobilePhone(Kopete::Global::Properties::self()->privateMobilePhone()), + propWorkPhone(Kopete::Global::Properties::self()->workPhone()), + propWorkMobilePhone(Kopete::Global::Properties::self()->workMobilePhone()), + propNickName(Kopete::Global::Properties::self()->nickName()), + propSubscriptionStatus("jabberSubscriptionStatus", i18n ("Subscription"), QString::null, true, false), + propAuthorizationStatus("jabberAuthorizationStatus", i18n ("Authorization Status"), QString::null, true, false), + propAvailableResources("jabberAvailableResources", i18n ("Available Resources"), "jabber_chatty", false, true), + propVCardCacheTimeStamp("jabberVCardCacheTimeStamp", i18n ("vCard Cache Timestamp"), QString::null, true, false, true), + propPhoto(Kopete::Global::Properties::self()->photo()), + propJid("jabberVCardJid", i18n("Jabber ID"), QString::null, true, false), + propBirthday("jabberVCardBirthday", i18n("Birthday"), QString::null, true, false), + propTimezone("jabberVCardTimezone", i18n("Timezone"), QString::null, true, false), + propHomepage("jabberVCardHomepage", i18n("Homepage"), QString::null, true, false), + propCompanyName("jabberVCardCompanyName", i18n("Company name"), QString::null, true, false), + propCompanyDepartement("jabberVCardCompanyDepartement", i18n("Company Departement"), QString::null, true, false), + propCompanyPosition("jabberVCardCompanyPosition", i18n("Company Position"), QString::null, true, false), + propCompanyRole("jabberVCardCompanyRole", i18n("Company Role"), QString::null, true, false), + propWorkStreet("jabberVCardWorkStreet", i18n("Work Street"), QString::null, true, false), + propWorkExtAddr("jabberVCardWorkExtAddr", i18n("Work Extra Address"), QString::null, true, false), + propWorkPOBox("jabberVCardWorkPOBox", i18n("Work PO Box"), QString::null, true, false), + propWorkCity("jabberVCardWorkCity", i18n("Work City"), QString::null, true, false), + propWorkPostalCode("jabberVCardWorkPostalCode", i18n("Work Postal Code"), QString::null, true, false), + propWorkCountry("jabberVCardWorkCountry", i18n("Work Country"), QString::null, true, false), + propWorkEmailAddress("jabberVCardWorkEmailAddress", i18n("Work Email Address"), QString::null, true, false), + propHomeStreet("jabberVCardHomeStreet", i18n("Home Street"), QString::null, true, false), + propHomeExtAddr("jabberVCardHomeExt", i18n("Home Extra Address"), QString::null, true, false), + propHomePOBox("jabberVCardHomePOBox", i18n("Home PO Box"), QString::null, true, false), + propHomeCity("jabberVCardHomeCity", i18n("Home City"), QString::null, true, false), + propHomePostalCode("jabberVCardHomePostalCode", i18n("Home Postal Code"), QString::null, true, false), + propHomeCountry("jabberVCardHomeCountry", i18n("Home Country"), QString::null, true, false), + propPhoneFax("jabberVCardPhoneFax", i18n("Fax"), QString::null, true, false), + propAbout("jabberVCardAbout", i18n("About"), QString::null, true, false) + +{ + + kdDebug (JABBER_DEBUG_GLOBAL) << "[JabberProtocol] Loading ..." << endl; + + /* This is meant to be a singleton, so we will check if we have + * been loaded before. */ + if (protocolInstance) + { + kdDebug (JABBER_DEBUG_GLOBAL) << "[JabberProtocol] Warning: Protocol already " << "loaded, not initializing again." << endl; + return; + } + + protocolInstance = this; + + addAddressBookField ("messaging/xmpp", Kopete::Plugin::MakeIndexField); + setCapabilities(Kopete::Protocol::FullRTF|Kopete::Protocol::CanSendOffline); + + // Init the Entity Capabilities manager. + capsManager = new JabberCapabilitiesManager; + capsManager->loadCachedInformation(); +} + +JabberProtocol::~JabberProtocol () +{ + //disconnectAll(); + + delete capsManager; + capsManager = 0L; + + /* make sure that the next attempt to load Jabber + * re-initializes the protocol class. */ + protocolInstance = 0L; +} + + + +AddContactPage *JabberProtocol::createAddContactWidget (QWidget * parent, Kopete::Account * i) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << "[Jabber Protocol] Create Add Contact Widget\n" << endl; + return new JabberAddContactPage (i, parent); +} + +KopeteEditAccountWidget *JabberProtocol::createEditAccountWidget (Kopete::Account * account, QWidget * parent) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << "[Jabber Protocol] Edit Account Widget\n" << endl; + JabberAccount *ja=dynamic_cast < JabberAccount * >(account); + if(ja || !account) + return new JabberEditAccountWidget (this,ja , parent); + else + { + JabberTransport *transport = dynamic_cast < JabberTransport * >(account); + if(!transport) + return 0L; + dlgJabberRegister *registerDialog = new dlgJabberRegister (transport->account(), transport->myself()->contactId()); + registerDialog->show (); + registerDialog->raise (); + return 0l; //we make ourself our own dialog, not an editAccountWidget. + } +} + +Kopete::Account *JabberProtocol::createNewAccount (const QString & accountId) +{ + kdDebug (JABBER_DEBUG_GLOBAL) << "[Jabber Protocol] Create New Account. ID: " << accountId << "\n" << endl; + if( Kopete::AccountManager::self()->findAccount( pluginId() , accountId ) ) + return 0L; //the account may already exist if greated just above + + int slash=accountId.find('/'); + if(slash>=0) + { + QString realAccountId=accountId.left(slash); + JabberAccount *realAccount=dynamic_cast(Kopete::AccountManager::self()->findAccount( pluginId() , realAccountId )); + if(!realAccount) //if it doesn't exist yet, create it + { + realAccount = new JabberAccount( this, realAccountId ); + if(!Kopete::AccountManager::self()->registerAccount( realAccount ) ) + return 0L; + } + if(!realAccount) + return 0L; + return new JabberTransport( realAccount , accountId ); + } + else + { + return new JabberAccount (this, accountId); + } +} + +Kopete::OnlineStatus JabberProtocol::resourceToKOS ( const XMPP::Resource &resource ) +{ + + // update to offline by default + Kopete::OnlineStatus status = JabberKOSOffline; + + if ( !resource.status().isAvailable () ) + { + // resource is offline + status = JabberKOSOffline; + } + else + { + if (resource.status ().show ().isEmpty ()) + { + if (resource.status ().isInvisible ()) + { + status = JabberKOSInvisible; + } + else + { + status = JabberKOSOnline; + } + } + else + if (resource.status ().show () == "chat") + { + status = JabberKOSChatty; + } + else if (resource.status ().show () == "away") + { + status = JabberKOSAway; + } + else if (resource.status ().show () == "xa") + { + status = JabberKOSXA; + } + else if (resource.status ().show () == "dnd") + { + status = JabberKOSDND; + } + else if (resource.status ().show () == "online") + { // the ApaSMSAgent sms gateway report status as "online" even if it's not in the RFC 3921 § 2.2.2.1 + // See Bug 129059 + status = JabberKOSOnline; + } + else if (resource.status ().show () == "connecting") + { // this is for kopete internals + status = JabberKOSConnecting; + } + else + { + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Unknown status " << resource.status ().show () << " for contact. One of your contact is probably using a broken client, ask him to report a bug" << endl; + } + } + + return status; + +} + +JabberCapabilitiesManager *JabberProtocol::capabilitiesManager() +{ + return capsManager; +} + +JabberProtocol *JabberProtocol::protocol () +{ + // return current instance + return protocolInstance; +} + +Kopete::Contact *JabberProtocol::deserializeContact (Kopete::MetaContact * metaContact, + const QMap < QString, QString > &serializedData, const QMap < QString, QString > & /* addressBookData */ ) +{ +// kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Deserializing data for metacontact " << metaContact->displayName () << "\n" << endl; + + QString contactId = serializedData["contactId"]; + QString displayName = serializedData["displayName"]; + QString accountId = serializedData["accountId"]; + QString jid = serializedData["JID"]; + + QDict < Kopete::Account > accounts = Kopete::AccountManager::self ()->accounts (this); + Kopete::Account *account = accounts[accountId]; + + if (!account) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "WARNING: Account for contact does not exist, skipping." << endl; + return 0; + } + + JabberTransport *transport = dynamic_cast(account); + if( transport ) + transport->account()->addContact ( jid.isEmpty() ? contactId : jid , metaContact); + else + account->addContact (contactId, metaContact); + return account->contacts()[contactId]; +} + +XMPP::Status JabberProtocol::kosToStatus( const Kopete::OnlineStatus & status , const QString & message ) +{ + XMPP::Status xmppStatus ( "", message ); + + if( status.status() == Kopete::OnlineStatus::Offline ) + { + xmppStatus.setIsAvailable( false ); + } + + switch ( status.internalStatus () ) + { + case JabberProtocol::JabberFreeForChat: + xmppStatus.setShow ( "chat" ); + break; + + case JabberProtocol::JabberOnline: + xmppStatus.setShow ( "" ); + break; + + case JabberProtocol::JabberAway: + xmppStatus.setShow ( "away" ); + break; + + case JabberProtocol::JabberXA: + xmppStatus.setShow ( "xa" ); + break; + + case JabberProtocol::JabberDND: + xmppStatus.setShow ( "dnd" ); + break; + + case JabberProtocol::JabberInvisible: + xmppStatus.setIsInvisible ( true ); + break; + } + return xmppStatus; +} + +#include "jabberprotocol.moc" diff --git a/kopete/protocols/jabber/jabberprotocol.h b/kopete/protocols/jabber/jabberprotocol.h new file mode 100644 index 00000000..798aafb4 --- /dev/null +++ b/kopete/protocols/jabber/jabberprotocol.h @@ -0,0 +1,164 @@ + /* + * jabberprotocol.h - Base class for the Kopete Jabber protocol + * + * Copyright (c) 2002-2003 by Till Gerken + * Copyright (c) 2002 by Daniel Stone + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * * + * ************************************************************************* + */ + +#ifndef JABBERPROTOCOL_H +#define JABBERPROTOCOL_H + +#include +#include +#include +#include +#include +#include +#include + +#include "kopetecontact.h" +#include "kopetecontactproperty.h" +#include "kopetemetacontact.h" +#include "kopeteonlinestatus.h" +#include "addcontactpage.h" + +#define JABBER_DEBUG_GLOBAL 14130 +#define JABBER_DEBUG_PROTOCOL 14131 + +namespace XMPP +{ + class Resource; + class Status; +} + +class JabberContact; +class dlgJabberStatus; +class dlgJabberSendRaw; +class JabberCapabilitiesManager; + +class JabberProtocol:public Kopete::Protocol +{ + Q_OBJECT + +public: + /** + * Object constructor and destructor + */ + JabberProtocol (QObject * parent, const char *name, const QStringList &); + ~JabberProtocol (); + + /** + * Creates the "add contact" dialog specific to this protocol + */ + virtual AddContactPage *createAddContactWidget (QWidget * parent, Kopete::Account * i); + virtual KopeteEditAccountWidget *createEditAccountWidget (Kopete::Account * account, QWidget * parent); + virtual Kopete::Account *createNewAccount (const QString & accountId); + + /** + * Deserialize contact data + */ + virtual Kopete::Contact *deserializeContact (Kopete::MetaContact * metaContact, + const QMap < QString, QString > &serializedData, const QMap < QString, QString > &addressBookData); + + enum OnlineStatus { JabberOnline, JabberFreeForChat, JabberAway, JabberXA, JabberDND, + JabberOffline, JabberInvisible, JabberConnecting }; + + const Kopete::OnlineStatus JabberKOSChatty; + const Kopete::OnlineStatus JabberKOSOnline; + const Kopete::OnlineStatus JabberKOSAway; + const Kopete::OnlineStatus JabberKOSXA; + const Kopete::OnlineStatus JabberKOSDND; + const Kopete::OnlineStatus JabberKOSOffline; + const Kopete::OnlineStatus JabberKOSInvisible; + const Kopete::OnlineStatus JabberKOSConnecting; + + const Kopete::ContactPropertyTmpl propLastSeen; + const Kopete::ContactPropertyTmpl propAwayMessage; + const Kopete::ContactPropertyTmpl propFirstName; + const Kopete::ContactPropertyTmpl propLastName; + const Kopete::ContactPropertyTmpl propFullName; + const Kopete::ContactPropertyTmpl propEmailAddress; + const Kopete::ContactPropertyTmpl propPrivatePhone; + const Kopete::ContactPropertyTmpl propPrivateMobilePhone; + const Kopete::ContactPropertyTmpl propWorkPhone; + const Kopete::ContactPropertyTmpl propWorkMobilePhone; + const Kopete::ContactPropertyTmpl propNickName; + const Kopete::ContactPropertyTmpl propSubscriptionStatus; + const Kopete::ContactPropertyTmpl propAuthorizationStatus; + const Kopete::ContactPropertyTmpl propAvailableResources; + const Kopete::ContactPropertyTmpl propVCardCacheTimeStamp; + const Kopete::ContactPropertyTmpl propPhoto; + // extra properties to match with vCard + const Kopete::ContactPropertyTmpl propJid; + const Kopete::ContactPropertyTmpl propBirthday; + const Kopete::ContactPropertyTmpl propTimezone; + const Kopete::ContactPropertyTmpl propHomepage; + const Kopete::ContactPropertyTmpl propCompanyName; + const Kopete::ContactPropertyTmpl propCompanyDepartement; + const Kopete::ContactPropertyTmpl propCompanyPosition; + const Kopete::ContactPropertyTmpl propCompanyRole; + const Kopete::ContactPropertyTmpl propWorkStreet; + const Kopete::ContactPropertyTmpl propWorkExtAddr; + const Kopete::ContactPropertyTmpl propWorkPOBox; + const Kopete::ContactPropertyTmpl propWorkCity; + const Kopete::ContactPropertyTmpl propWorkPostalCode; + const Kopete::ContactPropertyTmpl propWorkCountry; + const Kopete::ContactPropertyTmpl propWorkEmailAddress; + const Kopete::ContactPropertyTmpl propHomeStreet; + const Kopete::ContactPropertyTmpl propHomeExtAddr; + const Kopete::ContactPropertyTmpl propHomePOBox; + const Kopete::ContactPropertyTmpl propHomeCity; + const Kopete::ContactPropertyTmpl propHomePostalCode; + const Kopete::ContactPropertyTmpl propHomeCountry; + const Kopete::ContactPropertyTmpl propPhoneFax; + const Kopete::ContactPropertyTmpl propAbout; + + /** + * This returns our protocol instance + */ + static JabberProtocol *protocol (); + + /** + * Return whether the protocol supports offline messages. + */ + bool canSendOffline() const { return true; } + + /** + * Convert an XMPP::Resource status to a Kopete::OnlineStatus + */ + Kopete::OnlineStatus resourceToKOS ( const XMPP::Resource &resource ); + + /** + * Convert an online status to a XMPP::Status + */ + XMPP::Status kosToStatus( const Kopete::OnlineStatus & status, const QString& message=QString() ); + + /** + * Return the Entity Capabilities(JEP-0115) manager instance. + */ + JabberCapabilitiesManager *capabilitiesManager(); + +private: + /* + * Singleton instance of our protocol class + */ + static JabberProtocol *protocolInstance; + + /** + * Unique Instance of the Entity Capabilities(JEP-0115) manager for Kopete Jabber plugin. + */ + JabberCapabilitiesManager *capsManager; +}; + +#endif diff --git a/kopete/protocols/jabber/jabberresource.cpp b/kopete/protocols/jabber/jabberresource.cpp new file mode 100644 index 00000000..e74a0fa9 --- /dev/null +++ b/kopete/protocols/jabber/jabberresource.cpp @@ -0,0 +1,171 @@ + /* + * jabberresource.cpp + * + * Copyright (c) 2005-2006 by Michaël Larouche + * Copyright (c) 2004 by Till Gerken + * + * Kopete (c) 2001-2006 by the Kopete developers + * + * ************************************************************************* + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * * + * ************************************************************************* + */ + +#include "jabberresource.h" + +// Qt includes +#include + +// KDE includes +#include + +// libiris includes +#include +#include + +// Kopete includes +#include "jabberprotocol.h" +#include "jabberaccount.h" +#include "jabbercapabilitiesmanager.h" + +class JabberResource::Private +{ +public: + Private( JabberAccount *t_account, const XMPP::Jid &t_jid, const XMPP::Resource &t_resource ) + : account(t_account), jid(t_jid), resource(t_resource), capsEnabled(false) + { + // Make sure the resource is always set. + jid.setResource(resource.name()); + } + + JabberAccount *account; + XMPP::Jid jid; + XMPP::Resource resource; + + QString clientName, clientSystem; + XMPP::Features supportedFeatures; + bool capsEnabled; +}; + +JabberResource::JabberResource ( JabberAccount *account, const XMPP::Jid &jid, const XMPP::Resource &resource ) + : d( new Private(account, jid, resource) ) +{ + d->capsEnabled = account->protocol()->capabilitiesManager()->capabilitiesEnabled(jid); + + if ( account->isConnected () ) + { + QTimer::singleShot ( account->client()->getPenaltyTime () * 1000, this, SLOT ( slotGetTimedClientVersion () ) ); + if(!d->capsEnabled) + { + QTimer::singleShot ( account->client()->getPenaltyTime () * 1000, this, SLOT ( slotGetDiscoCapabilties () ) ); + } + } +} + +JabberResource::~JabberResource () +{ + delete d; +} + +const XMPP::Jid &JabberResource::jid () const +{ + return d->jid; +} + +const XMPP::Resource &JabberResource::resource () const +{ + return d->resource; +} + +void JabberResource::setResource ( const XMPP::Resource &resource ) +{ + d->resource = resource; + + // Check if the caps are now available. + d->capsEnabled = d->account->protocol()->capabilitiesManager()->capabilitiesEnabled(d->jid); + + emit updated( this ); +} + +const QString &JabberResource::clientName () const +{ + return d->clientName; +} + +const QString &JabberResource::clientSystem () const +{ + return d->clientSystem; +} + +XMPP::Features JabberResource::features() const +{ + if(d->capsEnabled) + { + return d->account->protocol()->capabilitiesManager()->features(d->jid); + } + else + { + return d->supportedFeatures; + } +} + +void JabberResource::slotGetTimedClientVersion () +{ + if ( d->account->isConnected () ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Requesting client version for " << d->jid.full () << endl; + + // request client version + XMPP::JT_ClientVersion *task = new XMPP::JT_ClientVersion ( d->account->client()->rootTask () ); + // signal to ourselves when the vCard data arrived + QObject::connect ( task, SIGNAL ( finished () ), this, SLOT ( slotGotClientVersion () ) ); + task->get ( d->jid ); + task->go ( true ); + } +} + +void JabberResource::slotGotClientVersion () +{ + XMPP::JT_ClientVersion *clientVersion = (XMPP::JT_ClientVersion *) sender (); + + if ( clientVersion->success () ) + { + d->clientName = clientVersion->name () + " " + clientVersion->version (); + d->clientSystem = clientVersion->os (); + + emit updated ( this ); + } +} + +void JabberResource:: slotGetDiscoCapabilties () +{ + if ( d->account->isConnected () ) + { + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Requesting Client Features for " << d->jid.full () << endl; + + XMPP:: JT_DiscoInfo *task = new XMPP::JT_DiscoInfo ( d->account->client()->rootTask () ); + // Retrive features when service discovery is done. + QObject::connect ( task, SIGNAL ( finished () ), this, SLOT (slotGotDiscoCapabilities () ) ); + task->get ( d->jid); + task->go ( true ); + } +} + +void JabberResource::slotGotDiscoCapabilities () +{ + XMPP::JT_DiscoInfo *discoInfo = (XMPP::JT_DiscoInfo *) sender (); + + if ( discoInfo->success () ) + { + d->supportedFeatures = discoInfo->item().features(); + + emit updated ( this ); + } +} + +#include "jabberresource.moc" diff --git a/kopete/protocols/jabber/jabberresource.h b/kopete/protocols/jabber/jabberresource.h new file mode 100644 index 00000000..7b398c09 --- /dev/null +++ b/kopete/protocols/jabber/jabberresource.h @@ -0,0 +1,86 @@ + /* + * jabberresource.h + * + * Copyright (c) 2005-2006 by Michaël Larouche + * Copyright (c) 2004 by Till Gerken + * + * Kopete (c) 2001-2006 by the Kopete developers + * + * ************************************************************************* + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * * + * ************************************************************************* + */ + +#ifndef JABBERRESOURCE_H +#define JABBERRESOURCE_H + +/** + * Container class for a contact's resource + */ + +#include +#include + +class JabberAccount; + +namespace XMPP +{ +class Resource; +class Jid; +class Features; +} + +class JabberResource : public QObject +{ +Q_OBJECT + +public: + /** + * Create a new Jabber resource. + */ + JabberResource (JabberAccount *account, const XMPP::Jid &jid, const XMPP::Resource &resource); + ~JabberResource (); + + const XMPP::Jid &jid() const; + const XMPP::Resource &resource() const; + + void setResource ( const XMPP::Resource &resource ); + + /** + * Return the client name for this resource. + * @return the client name + */ + const QString &clientName () const; + /** + * Return the client system for this resource. + * @return the client system. + */ + const QString &clientSystem () const; + + /** + * Get the available features for this resource. + */ + XMPP::Features features() const; + +signals: + void updated ( JabberResource * ); + +private slots: + void slotGetTimedClientVersion (); + void slotGotClientVersion (); + void slotGetDiscoCapabilties (); + void slotGotDiscoCapabilities (); + +private: + class Private; + Private *d; +}; + +#endif + +// vim: set noet ts=4 sts=4 tw=4: diff --git a/kopete/protocols/jabber/jabberresourcepool.cpp b/kopete/protocols/jabber/jabberresourcepool.cpp new file mode 100644 index 00000000..9d953ce6 --- /dev/null +++ b/kopete/protocols/jabber/jabberresourcepool.cpp @@ -0,0 +1,394 @@ + /* + * jabberresourcepool.cpp + * + * Copyright (c) 2004 by Till Gerken + * Copyright (c) 2006 by Michaël Larouche + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * * + * ************************************************************************* + */ + +#include +#include + +#include "jabberresourcepool.h" +#include "jabberresource.h" +#include "jabbercontactpool.h" +#include "jabberbasecontact.h" +#include "jabberaccount.h" +#include "jabberprotocol.h" +#include "jabbercapabilitiesmanager.h" + +/** + * This resource will be returned if no other resource + * for a given JID can be found. It's an empty offline + * resource. + */ +XMPP::Resource JabberResourcePool::EmptyResource ( "", XMPP::Status ( "", "", 0, false ) ); + +class JabberResourcePool::Private +{ +public: + Private(JabberAccount *pAccount) + : account(pAccount) + { + // automatically delete all resources in the pool upon removal + pool.setAutoDelete(true); + } + + QPtrList pool; + QPtrList lockList; + + /** + * Pointer to the JabberAccount instance. + */ + JabberAccount *account; +}; + +JabberResourcePool::JabberResourcePool ( JabberAccount *account ) + : d(new Private(account)) +{} + +JabberResourcePool::~JabberResourcePool () +{ + delete d; +} + +void JabberResourcePool::slotResourceDestroyed (QObject *sender) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Resource has been destroyed, collecting the pieces." << endl; + + JabberResource *oldResource = static_cast(sender); + + // remove this resource from the lock list if it existed + d->lockList.remove ( oldResource ); +} + +void JabberResourcePool::slotResourceUpdated ( JabberResource *resource ) +{ + QPtrList list = d->account->contactPool()->findRelevantSources ( resource->jid () ); + + for(JabberBaseContact *mContact = list.first (); mContact; mContact = list.next ()) + { + mContact->updateResourceList (); + } + + // Update capabilities + if( !resource->resource().status().capsNode().isEmpty() ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Updating capabilities for JID: " << resource->jid().full() << endl; + d->account->protocol()->capabilitiesManager()->updateCapabilities( d->account, resource->jid(), resource->resource().status() ); + } +} + +void JabberResourcePool::notifyRelevantContacts ( const XMPP::Jid &jid ) +{ + QPtrList list = d->account->contactPool()->findRelevantSources ( jid ); + + for(JabberBaseContact *mContact = list.first (); mContact; mContact = list.next ()) + { + mContact->reevaluateStatus (); + } +} + +void JabberResourcePool::addResource ( const XMPP::Jid &jid, const XMPP::Resource &resource ) +{ + // see if the resource already exists + for(JabberResource *mResource = d->pool.first (); mResource; mResource = d->pool.next ()) + { + if ( (mResource->jid().userHost().lower() == jid.userHost().lower()) && (mResource->resource().name().lower() == resource.name().lower()) ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Updating existing resource " << resource.name() << " for " << jid.userHost() << endl; + + // It exists, update it. Don't do a "lazy" update by deleting + // it here and readding it with new parameters later on, + // any possible lockings to this resource will get lost. + mResource->setResource ( resource ); + + // we still need to notify the contact in case the status + // of this resource changed + notifyRelevantContacts ( jid ); + + return; + } + } + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Adding new resource " << resource.name() << " for " << jid.userHost() << endl; + + // Update initial capabilities if available. + // Called before creating JabberResource so JabberResource wouldn't ask for disco information. + if( !resource.status().capsNode().isEmpty() ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Initial update of capabilities for JID: " << jid.full() << endl; + d->account->protocol()->capabilitiesManager()->updateCapabilities( d->account, jid, resource.status() ); + } + + // create new resource instance and add it to the dictionary + JabberResource *newResource = new JabberResource(d->account, jid, resource); + connect ( newResource, SIGNAL ( destroyed (QObject *) ), this, SLOT ( slotResourceDestroyed (QObject *) ) ); + connect ( newResource, SIGNAL ( updated (JabberResource *) ), this, SLOT ( slotResourceUpdated (JabberResource *) ) ); + d->pool.append ( newResource ); + + // send notifications out to the relevant contacts that + // a new resource is available for them + notifyRelevantContacts ( jid ); +} + +void JabberResourcePool::removeResource ( const XMPP::Jid &jid, const XMPP::Resource &resource ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing resource " << resource.name() << " from " << jid.userHost() << endl; + + for(JabberResource *mResource = d->pool.first (); mResource; mResource = d->pool.next ()) + { + if ( (mResource->jid().userHost().lower() == jid.userHost().lower()) && (mResource->resource().name().lower() == resource.name().lower()) ) + { + d->pool.remove (); + notifyRelevantContacts ( jid ); + return; + } + } + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "WARNING: No match found!" << endl; +} + +void JabberResourcePool::removeAllResources ( const XMPP::Jid &jid ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing all resources for " << jid.userHost() << endl; + + for(JabberResource *mResource = d->pool.first (); mResource; mResource = d->pool.next ()) + { + if ( mResource->jid().userHost().lower() == jid.userHost().lower() ) + { + // only remove preselected resource in case there is one + if ( jid.resource().isEmpty () || ( jid.resource().lower () == mResource->resource().name().lower () ) ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing resource " << jid.userHost() << "/" << mResource->resource().name () << endl; + d->pool.remove (); + } + } + } +} + +void JabberResourcePool::clear () +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Clearing the resource pool." << endl; + + /* + * Since many contacts can have multiple resources, we can't simply delete + * each resource and trigger a notification upon each deletion. This would + * cause lots of status updates in the GUI and create unnecessary flicker + * and API traffic. Instead, collect all JIDs, clear the dictionary + * and then notify all JIDs after the resources have been deleted. + */ + + QStringList jidList; + + for ( JabberResource *mResource = d->pool.first (); mResource; mResource = d->pool.next () ) + { + jidList += mResource->jid().full (); + } + + /* + * Since mPool has autodeletion enabled, this will cause all + * items to be deleted. The lock list will be cleaned automatically. + */ + d->pool.clear (); + + /* + * Now go through the list of JIDs and notify each contact + * of its status change + */ + for ( QStringList::Iterator it = jidList.begin (); it != jidList.end (); ++it ) + { + notifyRelevantContacts ( XMPP::Jid ( *it ) ); + } + +} + +void JabberResourcePool::lockToResource ( const XMPP::Jid &jid, const XMPP::Resource &resource ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Locking " << jid.full() << " to " << resource.name() << endl; + + // remove all existing locks first + removeLock ( jid ); + + // find the resource in our dictionary that matches + for(JabberResource *mResource = d->pool.first (); mResource; mResource = d->pool.next ()) + { + if ( (mResource->jid().userHost().lower() == jid.full().lower()) && (mResource->resource().name().lower() == resource.name().lower()) ) + { + d->lockList.append ( mResource ); + return; + } + } + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "WARNING: No match found!" << endl; +} + +void JabberResourcePool::removeLock ( const XMPP::Jid &jid ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing resource lock for " << jid.userHost() << endl; + + // find the resource in our dictionary that matches + for(JabberResource *mResource = d->pool.first (); mResource; mResource = d->pool.next ()) + { + if ( (mResource->jid().userHost().lower() == jid.userHost().lower()) ) + { + d->lockList.remove (mResource); + } + } + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "No locks found." << endl; +} + +JabberResource *JabberResourcePool::lockedJabberResource( const XMPP::Jid &jid ) +{ + // check if the JID already carries a resource, then we will have to use that one + if ( !jid.resource().isEmpty () ) + { + // we are subscribed to a JID, find the according resource in the pool + for ( JabberResource *mResource = d->pool.first (); mResource; mResource = d->pool.next () ) + { + if ( ( mResource->jid().userHost().lower () == jid.userHost().lower () ) && ( mResource->resource().name () == jid.resource () ) ) + { + return mResource; + } + } + + kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "WARNING: No resource found in pool, returning as offline." << endl; + + return 0L; + } + + // see if we have a locked resource + for(JabberResource *mResource = d->lockList.first (); mResource; mResource = d->lockList.next ()) + { + if ( mResource->jid().userHost().lower() == jid.userHost().lower() ) + { + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Current lock for " << jid.userHost () << " is '" << mResource->resource().name () << "'" << endl; + return mResource; + } + } + + kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "No lock available for " << jid.userHost () << endl; + + // there's no locked resource, return an empty resource + return 0L; +} + +const XMPP::Resource &JabberResourcePool::lockedResource ( const XMPP::Jid &jid ) +{ + JabberResource *resource = lockedJabberResource( jid ); + return (resource) ? resource->resource() : EmptyResource; +} + +JabberResource *JabberResourcePool::bestJabberResource( const XMPP::Jid &jid, bool honourLock ) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Determining best resource for " << jid.full () << endl; + + if ( honourLock ) + { + // if we are locked to a certain resource, always return that one + JabberResource *mResource = lockedJabberResource ( jid ); + if ( mResource ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "We have a locked resource '" << mResource->resource().name () << "' for " << jid.full () << endl; + return mResource; + } + } + + JabberResource *bestResource = 0L; + JabberResource *currentResource = 0L; + + for(currentResource = d->pool.first (); currentResource; currentResource = d->pool.next ()) + { + // make sure we are only looking up resources for the specified JID + if ( currentResource->jid().userHost().lower() != jid.userHost().lower() ) + { + continue; + } + + // take first resource if no resource has been chosen yet + if(!bestResource) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Taking '" << currentResource->resource().name () << "' as first available resource." << endl; + + bestResource = currentResource; + continue; + } + + if(currentResource->resource().priority() > bestResource->resource().priority()) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Using '" << currentResource->resource().name () << "' due to better priority." << endl; + + // got a better match by priority + bestResource = currentResource; + } + else + { + if(currentResource->resource().priority() == bestResource->resource().priority()) + { + if(currentResource->resource().status().timeStamp() > bestResource->resource().status().timeStamp()) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Using '" << currentResource->resource().name () << "' due to better timestamp." << endl; + + // got a better match by timestamp (priorities are equal) + bestResource = currentResource; + } + } + } + } + + return (bestResource) ? bestResource : 0L; +} + +const XMPP::Resource &JabberResourcePool::bestResource ( const XMPP::Jid &jid, bool honourLock ) +{ + JabberResource *bestResource = bestJabberResource( jid, honourLock); + return (bestResource) ? bestResource->resource() : EmptyResource; +} + +//TODO: Find Resources based on certain Features. +void JabberResourcePool::findResources ( const XMPP::Jid &jid, JabberResourcePool::ResourceList &resourceList ) +{ + for(JabberResource *mResource = d->pool.first (); mResource; mResource = d->pool.next ()) + { + if ( mResource->jid().userHost().lower() == jid.userHost().lower() ) + { + // we found a resource for the JID, let's see if the JID already contains a resource + if ( !jid.resource().isEmpty() && ( jid.resource().lower() != mResource->resource().name().lower() ) ) + // the JID contains a resource but it's not the one we have in the dictionary, + // thus we have to ignore this resource + continue; + + resourceList.append ( mResource ); + } + } +} + +void JabberResourcePool::findResources ( const XMPP::Jid &jid, XMPP::ResourceList &resourceList ) +{ + for(JabberResource *mResource = d->pool.first (); mResource; mResource = d->pool.next ()) + { + if ( mResource->jid().userHost().lower() == jid.userHost().lower() ) + { + // we found a resource for the JID, let's see if the JID already contains a resource + if ( !jid.resource().isEmpty() && ( jid.resource().lower() != mResource->resource().name().lower() ) ) + // the JID contains a resource but it's not the one we have in the dictionary, + // thus we have to ignore this resource + continue; + + resourceList.append ( mResource->resource () ); + } + } +} + +#include "jabberresourcepool.moc" diff --git a/kopete/protocols/jabber/jabberresourcepool.h b/kopete/protocols/jabber/jabberresourcepool.h new file mode 100644 index 00000000..a6cefcde --- /dev/null +++ b/kopete/protocols/jabber/jabberresourcepool.h @@ -0,0 +1,129 @@ + /* + * jabberresourcepool.h + * + * Copyright (c) 2004 by Till Gerken + * Copyright (c) 2006 by Michaël Larouche + * + * Kopete (c) by the Kopete developers + * + * ************************************************************************* + * * * + * * This program is free software; you can redistribute it and/or modify * + * * it under the terms of the GNU General Public License as published by * + * * the Free Software Foundation; either version 2 of the License, or * + * * (at your option) any later version. * + * * * + * ************************************************************************* + */ + +#ifndef JABBERRESOURCEPOOL_H +#define JABBERRESOURCEPOOL_H + +#include +#include + +class JabberResource; +class JabberAccount; + +/** + * @author Till Gerken + * @author Michaël Larouche + */ +class JabberResourcePool : public QObject +{ + Q_OBJECT +public: + static XMPP::Resource EmptyResource; + + typedef QPtrList ResourceList; + + /** + * Default constructor + */ + JabberResourcePool ( JabberAccount *account ); + + /** + * Default destructor + */ + ~JabberResourcePool(); + + /** + * Notify all relevant contacts in case + * a resource has been added, updated or removed. + */ + void notifyRelevantContacts ( const XMPP::Jid &jid ); + + /** + * Add a resource to the pool + */ + void addResource ( const XMPP::Jid &jid, const XMPP::Resource &resource ); + + /** + * Remove a resource from the pool + */ + void removeResource ( const XMPP::Jid &jid, const XMPP::Resource &resource ); + + /** + * Remove all resources for a given address from the pool + * NOTE: Since this method is mainly used for housekeeping, + * it does NOT notify any contacts. + */ + void removeAllResources ( const XMPP::Jid &jid ); + + /** + * Remove all resources from the pool + */ + void clear (); + + /** + * Lock to a certain resource + */ + void lockToResource ( const XMPP::Jid &jid, const XMPP::Resource &resource ); + + /** + * Remove a resource lock + */ + void removeLock ( const XMPP::Jid &jid ); + + /** + * Return the JabberResource instance for the locked resource, if any. + */ + JabberResource *lockedJabberResource( const XMPP::Jid &jid ); + + /** + * Return currently locked resource, if any + */ + const XMPP::Resource &lockedResource ( const XMPP::Jid &jid ); + + /** + * Return a usable JabberResource for a given JID. + * + * @param jid Jid to look for the best resource. + * @param honourLock Honour the resource locked by the user. + * + * @return a JabberResource instance. + */ + JabberResource *bestJabberResource( const XMPP::Jid &jid, bool honourLock = true ); + + /** + * Return usable resource for a given JID + * Matches by userHost(), honours locks for a JID by default + */ + const XMPP::Resource &bestResource ( const XMPP::Jid &jid, bool honourLock = true ); + + /** + * Find all resources that exist for a given JID + */ + void findResources ( const XMPP::Jid &jid, JabberResourcePool::ResourceList &resourceList ); + void findResources ( const XMPP::Jid &jid, XMPP::ResourceList &resourceList ); + +private slots: + void slotResourceDestroyed ( QObject *sender ); + void slotResourceUpdated ( JabberResource *resource ); + +private: + class Private; + Private *d; +}; + +#endif diff --git a/kopete/protocols/jabber/jabbertransport.cpp b/kopete/protocols/jabber/jabbertransport.cpp new file mode 100644 index 00000000..e7a8e7d3 --- /dev/null +++ b/kopete/protocols/jabber/jabbertransport.cpp @@ -0,0 +1,345 @@ + /* + + Copyright (c) 2006 by Olivier Goffart + + Kopete (c) 2006 by the Kopete developers + + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* + */ + +#include "jabbertransport.h" +#include "jabbercontact.h" +#include "jabberaccount.h" +#include "jabberprotocol.h" +#include "jabbercontactpool.h" +#include "jabberchatsession.h" + +#include +#include +#include + +#include + + +#include +#include +#include +#include +#include +#include + +#include "xmpp_tasks.h" + +JabberTransport::JabberTransport (JabberAccount * parentAccount, const XMPP::RosterItem & item, const QString& gateway_type) + : Kopete::Account ( parentAccount->protocol(), parentAccount->accountId()+"/"+ item.jid().bare() ) +{ + m_status=Creating; + m_account = parentAccount; + m_account->addTransport( this,item.jid().bare() ); + + JabberContact *myContact = m_account->contactPool()->addContact ( item , Kopete::ContactList::self()->myself(), false ); + setMyself( myContact ); + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << accountId() <<" transport created: myself: " << myContact << endl; + + setColor( account()->color() ); + +#if KOPETE_IS_VERSION(0,11,51) //setCustomIcon is new in kopete 0.12 + QString cIcon; + if(gateway_type=="msn") + cIcon="jabber_gateway_msn"; + else if(gateway_type=="icq") + cIcon="jabber_gateway_icq"; + else if(gateway_type=="aim") + cIcon="jabber_gateway_aim"; + else if(gateway_type=="yahoo") + cIcon="jabber_gateway_yahoo"; + else if(gateway_type=="sms") + cIcon="jabber_gateway_sms"; + else if(gateway_type=="gadu-gadu") + cIcon="jabber_gateway_gadu"; + else if(gateway_type=="smtp") + cIcon="jabber_gateway_smtp"; + else if(gateway_type=="http-ws") + cIcon="jabber_gateway_http-ws"; + else if(gateway_type=="qq") + cIcon="jabber_gateway_qq"; + else if(gateway_type=="tlen") + cIcon="jabber_gateway_tlen"; + else if(gateway_type=="irc") //NOTE: this is not official + cIcon="irc_protocol"; + + if( !cIcon.isEmpty() ) + setCustomIcon( cIcon ); +#endif + + configGroup()->writeEntry("GatewayJID" , item.jid().full() ); + + QTimer::singleShot(0, this, SLOT(eatContacts())); + + m_status=Normal; +} + +JabberTransport::JabberTransport( JabberAccount * parentAccount, const QString & _accountId ) + : Kopete::Account ( parentAccount->protocol(), _accountId ) +{ + m_status=Creating; + m_account = parentAccount; + + const QString contactJID_s = configGroup()->readEntry("GatewayJID"); + + if(contactJID_s.isEmpty()) + { + kdError(JABBER_DEBUG_GLOBAL) << k_funcinfo << _accountId <<": GatewayJID is empty: MISCONFIGURATION (have you used Kopete 0.12 beta ?)" << endl; + } + + XMPP::Jid contactJID= XMPP::Jid( contactJID_s ); + + m_account->addTransport( this, contactJID.bare() ); + + JabberContact *myContact = m_account->contactPool()->addContact ( contactJID , Kopete::ContactList::self()->myself(), false ); + setMyself( myContact ); + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << accountId() <<" transport created: myself: " << myContact << endl; + + m_status=Normal; +} + + + + +JabberTransport::~JabberTransport () +{ + m_account->removeTransport( myself()->contactId() ); +} + +KActionMenu *JabberTransport::actionMenu () +{ + KActionMenu *menu = new KActionMenu( accountId(), myself()->onlineStatus().iconFor( this ), this ); + QString nick = myself()->property( Kopete::Global::Properties::self()->nickName()).value().toString(); + + menu->popupMenu()->insertTitle( myself()->onlineStatus().iconFor( myself() ), + nick.isNull() ? accountLabel() : i18n( "%2 <%1>" ).arg( accountLabel(), nick ) + ); + + QPtrList *customActions = myself()->customContextMenuActions( ); + if( customActions && !customActions->isEmpty() ) + { + menu->popupMenu()->insertSeparator(); + + for( KAction *a = customActions->first(); a; a = customActions->next() ) + a->plug( menu->popupMenu() ); + } + delete customActions; + + return menu; + +/* KActionMenu *m_actionMenu = Kopete::Account::actionMenu(); + + m_actionMenu->popupMenu()->insertSeparator(); + + m_actionMenu->insert(new KAction (i18n ("Join Groupchat..."), "jabber_group", 0, + this, SLOT (slotJoinNewChat ()), this, "actionJoinChat")); + + m_actionMenu->popupMenu()->insertSeparator(); + + m_actionMenu->insert ( new KAction ( i18n ("Services..."), "jabber_serv_on", 0, + this, SLOT ( slotGetServices () ), this, "actionJabberServices") ); + + m_actionMenu->insert ( new KAction ( i18n ("Send Raw Packet to Server..."), "mail_new", 0, + this, SLOT ( slotSendRaw () ), this, "actionJabberSendRaw") ); + + m_actionMenu->insert ( new KAction ( i18n ("Edit User Info..."), "identity", 0, + this, SLOT ( slotEditVCard () ), this, "actionEditVCard") ); + + return m_actionMenu;*/ +} + + +bool JabberTransport::createContact (const QString & contactId, Kopete::MetaContact * metaContact) +{ +#if 0 //TODO + // collect all group names + QStringList groupNames; + Kopete::GroupList groupList = metaContact->groups(); + for(Kopete::Group *group = groupList.first(); group; group = groupList.next()) + groupNames += group->displayName(); + + XMPP::Jid jid ( contactId ); + XMPP::RosterItem item ( jid ); + item.setName ( metaContact->displayName () ); + item.setGroups ( groupNames ); + + // this contact will be created with the "dirty" flag set + // (it will get reset if the contact appears in the roster during connect) + JabberContact *contact = contactPool()->addContact ( item, metaContact, true ); + + return ( contact != 0 ); +#endif + return false; +} + + +void JabberTransport::setOnlineStatus( const Kopete::OnlineStatus& status , const QString &reason) +{ +#if 0 + if( status.status() == Kopete::OnlineStatus::Offline ) + { + disconnect( Kopete::Account::Manual ); + return; + } + + if( isConnecting () ) + { + errorConnectionInProgress (); + return; + } + + XMPP::Status xmppStatus ( "", reason ); + + switch ( status.internalStatus () ) + { + case JabberProtocol::JabberFreeForChat: + xmppStatus.setShow ( "chat" ); + break; + + case JabberProtocol::JabberOnline: + xmppStatus.setShow ( "" ); + break; + + case JabberProtocol::JabberAway: + xmppStatus.setShow ( "away" ); + break; + + case JabberProtocol::JabberXA: + xmppStatus.setShow ( "xa" ); + break; + + case JabberProtocol::JabberDND: + xmppStatus.setShow ( "dnd" ); + break; + + case JabberProtocol::JabberInvisible: + xmppStatus.setIsInvisible ( true ); + break; + } + + if ( !isConnected () ) + { + // we are not connected yet, so connect now + m_initialPresence = xmppStatus; + connect (); + } + else + { + setPresence ( xmppStatus ); + } +#endif +} + +JabberProtocol * JabberTransport::protocol( ) const +{ + return m_account->protocol(); +} + +bool JabberTransport::removeAccount( ) +{ + if(m_status == Removing || m_status == AccountRemoved) + return true; //so it can be deleted + + if (!account()->isConnected()) + { + account()->errorConnectFirst (); + return false; + } + + m_status = Removing; + XMPP::JT_Register *task = new XMPP::JT_Register ( m_account->client()->rootTask () ); + QObject::connect ( task, SIGNAL ( finished () ), this, SLOT ( removeAllContacts() ) ); + + //JabberContact *my=static_cast(myself()); + task->unreg ( myself()->contactId() ); + task->go ( true ); + return false; //delay the removal +} + +void JabberTransport::removeAllContacts( ) +{ +// XMPP::JT_Register * task = (XMPP::JT_Register *) sender (); + +/* if ( ! task->success ()) + KMessageBox::queuedMessageBox ( 0L, KMessageBox::Error, + i18n ("An error occured when trying to remove the transport:\n%1").arg(task->statusString()), + i18n ("Jabber Service Unregistration")); + */ //we don't really care, we remove everithing anyway. + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "delete all contacts of the transport"<< endl; + QDictIterator it( contacts() ); + for( ; it.current(); ++it ) + { + XMPP::JT_Roster * rosterTask = new XMPP::JT_Roster ( account()->client()->rootTask () ); + rosterTask->remove ( static_cast(it.current())->rosterItem().jid() ); + rosterTask->go ( true ); + } + m_status = Removing; //in theory that's already our status + Kopete::AccountManager::self()->removeAccount( this ); //this will delete this +} + +QString JabberTransport::legacyId( const XMPP::Jid & jid ) +{ + if(jid.node().isEmpty()) + return QString(); + QString node = jid.node(); + return node.replace("%","@"); +} + +void JabberTransport::jabberAccountRemoved( ) +{ + m_status = AccountRemoved; + Kopete::AccountManager::self()->removeAccount( this ); //this will delete this +} + +void JabberTransport::eatContacts( ) +{ + /* + * "Gateway Contact Eating" (c)(r)(tm)(g)(o)(f) + * this comes directly from my mind into the kopete code. + * principle: - the transport is hungry + * - it will eat contacts which belong to him + * - the contact will die + * - a new contact will born, with the same characteristics, but owned by the transport + * - Olivier 2006-01-17 - + */ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << endl; + QDict cts=account()->contacts(); + QDictIterator it( cts ); + for( ; it.current(); ++it ) + { + JabberContact *contact=dynamic_cast(it.current()); + if( contact && !contact->transport() && contact->rosterItem().jid().domain() == myself()->contactId() && contact != account()->myself()) + { + XMPP::RosterItem item=contact->rosterItem(); + Kopete::MetaContact *mc=contact->metaContact(); + Kopete::OnlineStatus status = contact->onlineStatus(); + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << item.jid().full() << " will be soon eat - " << contact << endl; + delete contact; + Kopete::Contact *c2=account()->contactPool()->addContact( item , mc , false ); //not sure this is false; + if(c2) + c2->setOnlineStatus( status ); //put back the old status + } + } +} + + + +#include "jabbertransport.moc" + +// vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/protocols/jabber/jabbertransport.h b/kopete/protocols/jabber/jabbertransport.h new file mode 100644 index 00000000..b26fd9c0 --- /dev/null +++ b/kopete/protocols/jabber/jabbertransport.h @@ -0,0 +1,138 @@ + /* + + Copyright (c) 2006 by Olivier Goffart + + Kopete (c) 2006 by the Kopete developers + + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* + */ + +#ifndef JABBERTRANSPORT_H +#define JABBERTRANSPORT_H + +#ifdef HAVE_CONFIG_H +#include +#endif + + +#include + + +namespace XMPP { + class Jid; + class RosterItem; +} +class JabberAccount; +class JabberProtocol; + +/** + * this class handle a jabber gateway + * @author Olivier Goffart */ + +class JabberTransport : public Kopete::Account +{ + Q_OBJECT + +public: + /** + * constructor called when the transport is created by info from server (i.e not when loading kopete) + * @param parentAccount is the parent jabber account. + * @param item is the roster item of the gateway + * @param gateway_type eg: "msn" or "icq" only used when the account is not loaded from config file for determining the icon + */ + JabberTransport (JabberAccount * parentAccount, const XMPP::RosterItem &item, const QString& gateway_type=QString()); + + /** + * constructor called when the transport is loaded from config + * @param parentAccount is the parent jabber account. + * @param accountId is the accountId + */ + JabberTransport (JabberAccount * parentAccount, const QString &accountId ); + + ~JabberTransport (); + + /** Returns the action menu for this account. */ + virtual KActionMenu *actionMenu (); + + /** the parent account */ + JabberAccount *account() const + { return m_account; } + + /* to get the protocol from the account */ + JabberProtocol *protocol () const; + + void connect( const Kopete::OnlineStatus& ) {} + virtual void disconnect( ) {} + + /** + * called when the account is removed in the config ui + * will remove the subscription + */ + virtual bool removeAccount(); + + + enum TransportStatus { Normal , Creating, Removing , AccountRemoved }; + TransportStatus transportStatus() { return m_status; }; + + /** + * return the legacyId conrresponding to the jid + * example: jhon%msn.com@msn.foojabber.org -> jhon@msn.com + */ + QString legacyId( const XMPP::Jid &jid ); + +public slots: + + /* Reimplemented from Kopete::Account */ + void setOnlineStatus( const Kopete::OnlineStatus& status , const QString &reason = QString::null); + + /** + * the account has been unregistered. + * loop over all contact and remove them + */ + void removeAllContacts(); + + /** + * the JabberAccount has been removed from Kopete, remove this account also + */ + void jabberAccountRemoved(); + + /** + * "eat" all contact in the account that have the same domain as us. + */ + void eatContacts(); + +protected: + /** + * Create a new contact in the specified metacontact + * + * You shouldn't ever call this method yourself, For adding contacts see @ref addContact() + * + * This method is called by @ref Kopete::Account::addContact() in this method, you should + * simply create the new custom @ref Kopete::Contact in the given metacontact. You should + * NOT add the contact to the server here as this method gets only called when synchronizing + * the contact list on disk with the one in memory. As such, all created contacts from this + * method should have the "dirty" flag set. + * + * This method should simply be used to intantiate the new contact, everything else + * (updating the GUI, parenting to meta contact, etc.) is being taken care of. + * + * @param contactId The unique ID for this protocol + * @param parentContact The metacontact to add this contact to + */ + virtual bool createContact (const QString & contactID, Kopete::MetaContact * parentContact); + +private: + JabberAccount *m_account; + TransportStatus m_status; + +}; + +#endif diff --git a/kopete/protocols/jabber/jingle/DESIGN b/kopete/protocols/jabber/jingle/DESIGN new file mode 100644 index 00000000..b1cbd666 --- /dev/null +++ b/kopete/protocols/jabber/jingle/DESIGN @@ -0,0 +1,121 @@ +Voice Use cases: +---------------- + +In JabberAccount: +-Account is connected: +* Init the JingleSessionManager (accessible via account()->jingleSessionManager()) +* Add voice extension to client features. +* Connect to incomingSession(const QString &sessionType, JingleSession *session) signal in JabberAccount. + +-On incoming session +* Create and show VoiceConversationDialog. +* VoiceConversationDialog will handle the communcation between the user and the session. + +In JabberContact: +-User select "Start voice conversation..." +* Get the best resource that support voice. If no compatible resource is found, show a message box. +* Create a JingleVoiceSession using JingleVoiceSessionManager. +* Create VoiceConversationDialog +* and VoiceConversationDialog will handle the communication between the user and the session. + +In VoiceConversationDialog: +-Incoming voice session +* Accept the session call JingleVoiceSession::accept(); +* Decline the session call JingleVoiceSession::decline(); + +-Accepted voice session +* Change GUI to "Voice session in progress." + +-On declining voice session or terminating a session. +* Remove JingleVoiceSession from JingleVoiceSessionManager. +* Close the dialog. + +=================================================================================================== +Design with future in mind. Only voice session type is available today, but others will come. + +A session is a connection between two or multiple peers. +A session do not handle multiple "calls"(or whatever it called depending of the context). That's will be job of SessionManager +A sesson has a myself user and others users, all identified by their full JID. (maybe their JabberBaseContact or JabberResource object ?) + +-Maybe use the Channel pattern, where Session will hold one or multiple Channels. Think for voice+video for example. ? + +All manager classes must be unique for each account. + +JidList = QValueList or QStringList if QValueList doesn't work. + +JingleSession and derivated are created by the Manager class. + +SessionType is the XML Namespace of the session type (ex: http://jabber.org/protocol/sessions/audio) + +JingleSessionManager +-------------------- +Manage Jingle sessions. +-Manage global (maybe static ?)objects shared by all sessions (cricket::BasicPortAllocator, cricket::SessionManager). + +Has a JingleWatchSessionTask(derived from XMPP::Task) that check for incoming session in JingleSessionManager, that check the session type, +create the right JingleSession subclass, then emit the required signal. This bypass libjingle to have a better +control on incoming session request and avoid using multiple Manager for each session type. + +JingleSessionManager manage the JingleSession pointers. Do not delete it in user classes. + +* JingleSessionManager(JabberAccount *) + +* public slots: +* JingleSession *createSession(const QString &sessionType, const JidList &peers); +* void removeSession(JingleSession *); + +signals: +* void incomingSession(const QString &sessionType, JingleSession *session); + +JingleSession +------------- +Base class for Jingle session. A session is a + +* JingleSession(JingleSessionManager *manager, const JidList &peers); + +* XMPP::Jid &myself(); // account()->client()->jid(); +* JidList &peers(); +* JabberAccount *account(); +* JingleSessionManager *manager(); + +// Start the negociation phase. +* virtual void start() = 0; +// Send the IQ stanza with action "accept" +* virtual void accept() = 0; +// Send the IQ stanza with action " +* virtual void decline() = 0; +* virtual void terminate() = 0; + +// Return Session XML namespace +* virtual QString sessionType() = 0; + +protected slots: + void sendStanza(const QString &stanza) { account()->client->send(stanza); + +signals: + void accepted(); + void declined(); + void terminated(); + +JingleVoiceSession : public JingleSession +------------------ +Define a VoIP voice session between two peers(for the moment). +Hold the PhoneSessionClient object. + +connect(account()->client(),SIGNAL(xmlIncoming(const QString&)),SLOT(receiveStanza(const QString&))); + + +private slots: + void receiveStanza(const QString &stanza); + +VoiceConversationDialog +----------------------- +* VoiceConversationDialog(JingleVoiceSession *) +VoiceConversationDialog will handle the communcation between the user and a session. +Should auto-delete when closed. +It can: +-Accept a voice session. +-Decline a voice session. +-Terminate a voice session(or hang-up). + +It is the Action menu that can start a session. diff --git a/kopete/protocols/jabber/jingle/Makefile.am b/kopete/protocols/jabber/jingle/Makefile.am new file mode 100644 index 00000000..553be0d7 --- /dev/null +++ b/kopete/protocols/jabber/jingle/Makefile.am @@ -0,0 +1,28 @@ +SUBDIRS = libjingle +METASOURCES = AUTO +AM_CPPFLAGS = $(KOPETE_INCLUDES) \ + -I$(srcdir)/../libiris/iris/include \ + -I$(srcdir)/../libiris/iris/xmpp-im \ + -I$(srcdir)/../libiris/iris/jabber \ + -I$(srcdir)/../libiris/qca/src \ + -I$(srcdir)/../libiris/cutestuff/util \ + -I$(srcdir)/libjingle \ + -I$(srcdir)/.. \ + $(all_includes) + +noinst_LTLIBRARIES = libkopetejabberjingle.la + +# libkopetejabberjingle_la_SOURCES = jinglevoicecaller.cpp \ +# jinglewatchsessiontask.cpp jinglesession.cpp jinglevoicesession.cpp jinglesessionmanager.cpp \ +# jinglevoicesessiondialogbase.ui jinglevoicesessiondialog.cpp + +libkopetejabberjingle_la_SOURCES = jinglevoicecaller.cpp jinglevoicesessiondialogbase.ui jinglevoicesessiondialog.cpp + +libkopetejabberjingle_la_LIBADD = libjingle/talk/session/phone/libcricketsessionphone.la \ + libjingle/talk/p2p/client/libcricketp2pclient.la \ + libjingle/talk/p2p/base/libcricketp2pbase.la \ + libjingle/talk/xmpp/libcricketxmpp.la \ + libjingle/talk/xmllite/libcricketxmllite.la \ + libjingle/talk/base/libcricketbase.la \ + libjingle/talk/third_party/mediastreamer/libmediastreamer.la \ + $(EXPAT_LIBS) $(ORTP_LIBS) -lpthread $(ILBC_LIBS) $(SPEEX_LIBS) $(GLIB_LIBS) $(ALSA_LIBS) diff --git a/kopete/protocols/jabber/jingle/configure.in.bot b/kopete/protocols/jabber/jingle/configure.in.bot new file mode 100644 index 00000000..f30595e6 --- /dev/null +++ b/kopete/protocols/jabber/jingle/configure.in.bot @@ -0,0 +1,16 @@ +if test "$with_jingle" = yes; then + echo "" + echo Supported Jabber Jingle voice Codecs for Kopete: + echo Speex: $speex_found + echo iLBC: $ilbc_found + echo MULAW: yes +else + echo "" + echo "You have disabled Jabber Jingle voice support or you are missing required libraries required to compile it." + echo "Jingle is a new Jabber standard that define a signaling protocol via XMPP for peer-to-peer applications." + echo "Jingle audio is compatible with the Google Talk voice service." + echo "" + echo "Required Jingle dependencies are listed on this page:" + echo "http://wiki.kde.org/tiki-index.php?page=Kopete+Jabber+Jingle" + all_tests=bad +fi diff --git a/kopete/protocols/jabber/jingle/configure.in.in b/kopete/protocols/jabber/jingle/configure.in.in new file mode 100644 index 00000000..ee4db3fa --- /dev/null +++ b/kopete/protocols/jabber/jingle/configure.in.in @@ -0,0 +1,87 @@ +AC_DEFINE(PRODUCTION_BUILD, 1, [Build as a production build]) +AC_DEFINE(PRODUCTION, 1, [Build as a production build]) +AC_DEFINE(POSIX, 1, [If we're using configure, we're on POSIX]) + +# Check if the user want Jabber Jingle voice support +AC_ARG_ENABLE(jingle, [ --enable-jingle enable Jabber Jingle voice support ], with_jingle=$enableval, with_jingle=no) + +# Here we go +HAVE_EXPAT=no +AC_CHECK_LIB(expat, XML_ParserCreate, HAVE_EXPAT="yes") +if test "x$HAVE_EXPAT" = xyes ; then + EXPAT_LIBS="-lexpat" + AC_SUBST(EXPAT_LIBS) +else + with_jingle=no + AC_MSG_WARN([Expat is required to build Jabber Jingle voice support. You can get it from http://expat.sourceforge.net/]) +fi + +AC_CHECK_HEADERS(alsa/asoundlib.h, + [AC_CHECK_LIB(asound, snd_pcm_open, + [ALSA_LIBS="-lasound" ; AC_DEFINE(__ALSA_ENABLED__,1,[Defined when alsa support is enabled]) ]) + ] +) +AC_SUBST(ALSA_LIBS) + +# We test for GLIB in protocols/configure.in.in +if test x$have_glib = xno; then + with_jingle=no +fi + +PKG_CHECK_MODULES(ORTP, ortp, enable_ortp=yes, enable_ortp=no) +if test x$enable_ortp = xno ; then + with_jingle=no + AC_MSG_WARN([oRTP is required to build Jabber Jingle voice support. You can get it from http://www.linphone.org/ortp/]) +fi +AC_SUBST(ORTP_CFLAGS) +AC_SUBST(ORTP_LIBS) + +AC_ARG_WITH( speex, + [ --with-speex Set prefix where speex lib can be found (ex:/usr, /usr/local) [default=/usr] ], + [ speex_prefix=${withval}],[ speex_prefix="/usr" ]) + +PKG_CHECK_MODULES(SPEEX, speex, speex_found=yes, speex_found=no) +AC_CHECK_HEADERS(speex.h,[AC_CHECK_LIB(speex,speex_encode_int,speex_found=yes,speex_found=no)],speex_found=no) +AC_CHECK_HEADERS(speex/speex.h,[AC_CHECK_LIB(speex,speex_encode_int,speex_found=yes,speex_found=no)],speex_found=no) + +if test x$speex_found = xno; then + AC_MSG_WARN([Could not find a libspeex version that have the speex_encode_int() function. Please install libspeex=1.0.5 or libspeex>=1.1.6 from http://www.speex.org/]) +else + SPEEX_CFLAGS="$SPEEX_CFLAGS -I${speex_prefix}/include -I${speex_prefix}/include/speex" + AC_SUBST(SPEEX_CFLAGS) + AC_SUBST(SPEEX_LIBS) + AC_DEFINE(HAVE_SPEEX,1,[Speex codec is enabled]) +fi + + +# dnl only accept speex>=1.1.6 or 1.0.5 (the versions that have speex_encode_int ) +# AC_ARG_WITH( speex, +# [ --with-speex Set prefix where speex lib can be found (ex:/usr, /usr/local) [default=/usr] ], +# [ speex_prefix=${withval}],[ speex_prefix="/usr" ]) +# +# AC_CHECK_HEADERS(speex.h,[AC_CHECK_LIB(speex,speex_encode_int,speex_found=yes,speex_found=no) +# ],speex_found=no) +# +# if test "$speex_found" = "no" ; then +# AC_MSG_WARN([Could not find a libspeex version that have the speex_encode_int() function. Please install libspeex=1.0.5 or libspeex>=1.1.6 from http://www.speex.org/]) +# else +# SPEEX_CFLAGS=" -I${speex_prefix}/include -I${speex_prefix}/include/speex" +# SPEEX_LIBS="-L${speex_prefix}/lib -lspeex -lm" +# CPPFLAGS_save=$CPPFLAGS +# CPPFLAGS=$SPEEX_CFLAGS +# LDFLAGS_save=$LDFLAGS +# LDFLAGS=$SPEEX_LIBS +# AC_DEFINE(HAVE_SPEEX,1,[has speex]) +# fi +# +# AC_SUBST(SPEEX_CFLAGS) +# AC_SUBST(SPEEX_LIBS) +# CPPFLAGS=$CPPFLAGS_save +# LDFLAGS=$LDFLAGS_save +ilbc_found="no" + +AM_CONDITIONAL(include_jingle, test "$with_jingle" = "yes") + +if test "$with_jingle" = "yes" ; then + AC_DEFINE(SUPPORT_JINGLE,1,[Jingle support is enabled]) +fi diff --git a/kopete/protocols/jabber/jingle/jinglesession.cpp b/kopete/protocols/jabber/jingle/jinglesession.cpp new file mode 100644 index 00000000..6c370fca --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglesession.cpp @@ -0,0 +1,72 @@ +/* + jinglesession.h - Define a Jingle session. + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ +#include "jinglesession.h" + +#include + +#include "jabberaccount.h" +#include "jabberprotocol.h" + +class JingleSession::Private +{ +public: + Private(JabberAccount *t_account, const JidList &t_peers) + : peers(t_peers), account(t_account) + {} + + XMPP::Jid myself; + JidList peers; + JabberAccount *account; +}; + +JingleSession::JingleSession(JabberAccount *account, const JidList &peers) + : QObject(account, 0), d(new Private(account, peers)) +{ + d->myself = account->client()->jid(); +} + +JingleSession::~JingleSession() +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << endl; + delete d; +} + +const XMPP::Jid &JingleSession::myself() const +{ + return d->myself; +} + +const JingleSession::JidList &JingleSession::peers() const +{ + return d->peers; +} + +JingleSession::JidList &JingleSession::peers() +{ + return d->peers; +} +JabberAccount *JingleSession::account() +{ + return d->account; +} + +void JingleSession::sendStanza(const QString &stanza) +{ + account()->client()->send( stanza ); +} + +#include "jinglesession.moc" diff --git a/kopete/protocols/jabber/jingle/jinglesession.h b/kopete/protocols/jabber/jingle/jinglesession.h new file mode 100644 index 00000000..00c192bd --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglesession.h @@ -0,0 +1,94 @@ +/* + jinglesession.h - Define a Jingle session. + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ +#ifndef JINGLESESSION_H +#define JINGLESESSION_H + +#include +#include + +#include // XMPP::Jid +#include + +class JabberAccount; +/** + * @brief Base class for peer-to-peer session that use Jingle signaling + * + * @author Michaël Larouche + */ +class JingleSession : public QObject +{ + Q_OBJECT +public: + typedef QValueList JidList; + + JingleSession(JabberAccount *account, const JidList &peers); + virtual ~JingleSession(); + + /** + * Return the JabberAccount associated with this session. + */ + JabberAccount *account(); + + const XMPP::Jid &myself() const; + const JidList &peers() const; + JidList &peers(); + + /** + * Return the type of session(ex: voice, video, games) + * Note that you must return the XML namespace that define + * the session: ex:(http://jabber.org/protocol/jingle/sessions/audio) + */ + virtual QString sessionType() = 0; + +public slots: + /** + * @brief Start a session with the give JID. + * You should begin the negociation here. + */ + virtual void start() = 0; + /** + * @brief Acept a session request. + */ + virtual void accept() = 0; + /** + * @brief Decline a session request. + */ + virtual void decline() = 0; + /** + * @brief Terminate a Jingle session. + */ + virtual void terminate() = 0; + +protected slots: + void sendStanza(const QString &stanza); + +signals: + /** + * Session is started(negocation and connection test are done). + */ + void sessionStarted(); + + void accepted(); + void declined(); + void terminated(); + +private: + class Private; + Private *d; +}; + +#endif diff --git a/kopete/protocols/jabber/jingle/jinglesessionmanager.cpp b/kopete/protocols/jabber/jingle/jinglesessionmanager.cpp new file mode 100644 index 00000000..aeec2889 --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglesessionmanager.cpp @@ -0,0 +1,205 @@ +/* + jinglesessionmanager.cpp - Manage Jingle sessions. + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ +// libjingle before everything else to not clash with Qt +#define POSIX +#include "talk/xmpp/constants.h" +#include "talk/base/sigslot.h" +#include "talk/xmpp/jid.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/xmlprinter.h" +#include "talk/base/network.h" +#include "talk/p2p/base/session.h" +#include "talk/p2p/base/sessionmanager.h" +#include "talk/p2p/base/helpers.h" +#include "talk/p2p/client/basicportallocator.h" +#include "talk/p2p/client/sessionclient.h" +#include "talk/base/physicalsocketserver.h" +#include "talk/base/thread.h" +#include "talk/base/socketaddress.h" +#include "talk/session/phone/call.h" +#include "talk/session/phone/phonesessionclient.h" +#include "talk/session/sessionsendtask.h" + + +#include "jinglesessionmanager.h" + +//#include "jinglesession.h" +#include "jinglevoicesession.h" + +#include "jinglewatchsessiontask.h" + +#include "jabberaccount.h" +#include "jabberprotocol.h" + +#include + +#define JINGLE_NS "http://www.google.com/session" +#define JINGLE_VOICE_SESSION_NS "http://www.google.com/session/phone" + +//BEGIN JingleSessionManager::SlotsProxy +class JingleSessionManager; +class JingleSessionManager::SlotsProxy : public sigslot::has_slots<> +{ +public: + SlotsProxy(JingleSessionManager *parent) + : sessionManager(parent) + {} + + void OnSignalingRequest() + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Requesting Jingle signaling." << endl; + sessionManager->cricketSessionManager()->OnSignalingReady(); + } + + +private: + JingleSessionManager *sessionManager; +}; + +//END JingleSessionManager::SlotsProxy + +//BEGIN JingleSessionManager::Private +class JingeSession; +class JingleSessionManager::Private +{ +public: + Private(JabberAccount *t_account) + : account(t_account), watchSessionTask(0L) + {} + + ~Private() + { + delete networkManager; + delete portAllocator; + delete sessionThread; + delete cricketSessionManager; + } + + JabberAccount *account; + QValueList sessionList; + JingleWatchSessionTask *watchSessionTask; + + cricket::NetworkManager *networkManager; + cricket::BasicPortAllocator *portAllocator; + cricket::Thread *sessionThread; + cricket::SessionManager *cricketSessionManager; +}; +//END JingleSessionManager::Private + +JingleSessionManager::JingleSessionManager(JabberAccount *account) + : QObject(account, 0), d(new Private(account)) +{ + // Create slots proxy for libjingle + slotsProxy = new SlotsProxy(this); + + // Create watch incoming session task. + d->watchSessionTask = new JingleWatchSessionTask(account->client()->rootTask()); + connect(d->watchSessionTask, SIGNAL(watchSession(const QString &, const QString &)), this, SLOT(slotIncomingSession(const QString &, const QString &))); + + // Create global cricket variables common to all sessions. + // Seed random generation with the JID of the account. + QString accountJid = account->client()->jid().full(); + cricket::InitRandom( accountJid.ascii(), accountJid.length() ); + + // Create the libjingle NetworkManager that manager local network connections + d->networkManager = new cricket::NetworkManager(); + + // Init the port allocator(select best ports) with the Google STUN server to help. + cricket::SocketAddress *googleStunAddress = new cricket::SocketAddress("64.233.167.126", 19302); + // TODO: Define a relay server. + d->portAllocator = new cricket::BasicPortAllocator(d->networkManager, googleStunAddress, 0L); + + // Create the Session manager that manager peer-to-peer sessions. + d->sessionThread = new cricket::Thread(); + d->cricketSessionManager = new cricket::SessionManager(d->portAllocator, d->sessionThread); + d->cricketSessionManager->SignalRequestSignaling.connect(slotsProxy, &JingleSessionManager::SlotsProxy::OnSignalingRequest); + d->cricketSessionManager->OnSignalingReady(); + + d->sessionThread->Start(); +} + +JingleSessionManager::~JingleSessionManager() +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << endl; + + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Cleaning up Jingle sessions." << endl; + QValueList::Iterator it, itEnd = d->sessionList.end(); + for(it = d->sessionList.begin(); it != itEnd; ++it) + { + JingleSession *deletedSession = *it; + if( deletedSession ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "deleting a session." << endl; + delete deletedSession; + } + } + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Done Cleaning up Jingle sessions." << endl; + + delete d; +} + +cricket::SessionManager *JingleSessionManager::cricketSessionManager() +{ + return d->cricketSessionManager; +} + +JabberAccount *JingleSessionManager::account() +{ + return d->account; +} + +JingleSession *JingleSessionManager::createSession(const QString &sessionType, const JidList &peers) +{ + JingleSession *newSession = 0L; + + if(sessionType == JINGLE_VOICE_SESSION_NS) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Creating a voice session" << endl; + newSession = new JingleVoiceSession(account(), peers); + } + + if(newSession) + d->sessionList.append(newSession); + + return newSession; +} + +JingleSession *JingleSessionManager::createSession(const QString &sessionType, const XMPP::Jid &user) +{ + JingleSessionManager::JidList jidList; + jidList.append(user); + + return createSession(sessionType, jidList); +} + +void JingleSessionManager::removeSession(JingleSession *session) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing a jingle session." << endl; + + d->sessionList.remove(session); + delete session; +} + +void JingleSessionManager::slotIncomingSession(const QString &sessionType, const QString &initiator) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Incoming session: " << sessionType << ". Initiator: " << initiator << endl; + + JingleSession *newSession = createSession(sessionType, XMPP::Jid(initiator)); + emit incomingSession(sessionType, newSession); +} + +#include "jinglesessionmanager.moc" diff --git a/kopete/protocols/jabber/jingle/jinglesessionmanager.h b/kopete/protocols/jabber/jingle/jinglesessionmanager.h new file mode 100644 index 00000000..06951c2f --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglesessionmanager.h @@ -0,0 +1,89 @@ +/* + jinglesessionmanager.h - Manage Jingle sessions. + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ +#ifndef JINGLESESSIONMANAGER_H +#define JINGLESESSIONMANAGER_H + +#include +#include + +#include +#include + +namespace cricket +{ + class SessionManager; +} + +class JingleSession; +class JingleVoiceSession; +class JabberAccount; + +/** + * @brief Manage Jingle sessions. + * @author Michaël Larouche + */ +class JingleSessionManager : public QObject +{ + Q_OBJECT +public: + typedef QValueList JidList; + + JingleSessionManager(JabberAccount *account); + ~JingleSessionManager(); + + /** + * Get the (single) instance of the cricket session manager. + */ + cricket::SessionManager *cricketSessionManager(); + + /** + * Return the JabberAccount associated with this session manager. + */ + JabberAccount *account(); + +public slots: + /** + * Create a new Jingle session. Returned pointer is managed by this class. + * @param sessionType the session you want to create. You must pass its XML namespace(ex: http://jabber.org/protocol/sessions/audio) + * @param peers Lists of participants of the session. + */ + JingleSession *createSession(const QString &sessionType, const JidList &peers); + /** + * Override method that create a session for a one-to-one session. + * It behave like createSession method. + * @param sessionType the sesion you want to create. You must pass its XML namespace(ex: http://jabber.org/protocol/sessions/audio) + * @param user The JID of the user you want to begin a session with. + */ + JingleSession *createSession(const QString &sessionType, const XMPP::Jid &user); + + void removeSession(JingleSession *session); + +signals: + void incomingSession(const QString &sessionType, JingleSession *session); + +private slots: + void slotIncomingSession(const QString &sessionType, const QString &initiator); + +private: + class Private; + Private *d; + + class SlotsProxy; + SlotsProxy *slotsProxy; +}; + +#endif diff --git a/kopete/protocols/jabber/jingle/jinglevoicecaller.cpp b/kopete/protocols/jabber/jingle/jinglevoicecaller.cpp new file mode 100644 index 00000000..3ad6a89a --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglevoicecaller.cpp @@ -0,0 +1,376 @@ + +#define POSIX //FIXME +#include "talk/xmpp/constants.h" +#include "talk/base/sigslot.h" +#include "talk/xmpp/jid.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/xmlprinter.h" +#include "talk/base/network.h" +#include "talk/p2p/base/session.h" +#include "talk/p2p/base/sessionmanager.h" +#include "talk/p2p/base/helpers.h" +#include "talk/p2p/client/basicportallocator.h" +#include "talk/p2p/client/sessionclient.h" +#include "talk/base/physicalsocketserver.h" +#include "talk/base/thread.h" +#include "talk/base/socketaddress.h" +#include "talk/session/phone/call.h" +#include "talk/session/phone/phonesessionclient.h" +#include "talk/session/sessionsendtask.h" + + + +#include +#include + + + +#include "im.h" +#include "xmpp.h" +#include "xmpp_xmlcommon.h" +#include "jinglevoicecaller.h" +#include "jabberprotocol.h" + +// Should change in the future +#define JINGLE_NS "http://www.google.com/session" + +#include "jabberaccount.h" +#include +#define qDebug( X ) kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << X << endl +#define qWarning( X ) kdWarning() < +{ +public: + JingleClientSlots(JingleVoiceCaller *voiceCaller); + + void callCreated(cricket::Call *call); + void callDestroyed(cricket::Call *call); + void sendStanza(cricket::SessionClient*, const buzz::XmlElement *stanza); + void requestSignaling(); + void stateChanged(cricket::Call *call, cricket::Session *session, cricket::Session::State state); + +private: + JingleVoiceCaller* voiceCaller_; +}; + + +JingleClientSlots::JingleClientSlots(JingleVoiceCaller *voiceCaller) : voiceCaller_(voiceCaller) +{ +} + +void JingleClientSlots::callCreated(cricket::Call *call) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << endl; + call->SignalSessionState.connect(this, &JingleClientSlots::stateChanged); +} + +void JingleClientSlots::callDestroyed(cricket::Call *call) +{ + qDebug("JingleClientSlots: Call destroyed"); + Jid jid(call->sessions()[0]->remote_address().c_str()); + if (voiceCaller_->calling(jid)) { + qDebug(QString("Removing unterminated call to %1").arg(jid.full())); + voiceCaller_->removeCall(jid); + emit voiceCaller_->terminated(jid); + } +} + +void JingleClientSlots::sendStanza(cricket::SessionClient*, const buzz::XmlElement *stanza) +{ + QString st(stanza->Str().c_str()); + st.replace("cli:iq","iq"); + st.replace(":cli=","="); + fprintf(stderr,"bling\n"); + voiceCaller_->sendStanza(st.latin1()); + fprintf(stderr,"blong\n"); + fprintf(stderr,"Sending stanza \n%s\n\n",st.latin1()); +} + +void JingleClientSlots::requestSignaling() +{ + voiceCaller_->session_manager_->OnSignalingReady(); +} + +void JingleClientSlots::stateChanged(cricket::Call *call, cricket::Session *session, cricket::Session::State state) +{ + qDebug(QString("jinglevoicecaller.cpp: State changed (%1)").arg(state)); + // Why is c_str() stuff needed to make it compile on OS X ? + Jid jid(session->remote_address().c_str()); + + if (state == cricket::Session::STATE_INIT) { } + else if (state == cricket::Session::STATE_SENTINITIATE) { + voiceCaller_->registerCall(jid,call); + } + else if (state == cricket::Session::STATE_RECEIVEDINITIATE) { + voiceCaller_->registerCall(jid,call); + emit voiceCaller_->incoming(jid); + } + else if (state == cricket::Session::STATE_SENTACCEPT) { } + else if (state == cricket::Session::STATE_RECEIVEDACCEPT) { + emit voiceCaller_->accepted(jid); + } + else if (state == cricket::Session::STATE_SENTMODIFY) { } + else if (state == cricket::Session::STATE_RECEIVEDMODIFY) { + qWarning(QString("jinglevoicecaller.cpp: RECEIVEDMODIFY not implemented yet (was from %1)").arg(jid.full())); + } + else if (state == cricket::Session::STATE_SENTREJECT) { } + else if (state == cricket::Session::STATE_RECEIVEDREJECT) { + voiceCaller_->removeCall(jid); + emit voiceCaller_->rejected(jid); + } + else if (state == cricket::Session::STATE_SENTREDIRECT) { } + else if (state == cricket::Session::STATE_SENTTERMINATE) { + voiceCaller_->removeCall(jid); + emit voiceCaller_->terminated(jid); + } + else if (state == cricket::Session::STATE_RECEIVEDTERMINATE) { + voiceCaller_->removeCall(jid); + emit voiceCaller_->terminated(jid); + } + else if (state == cricket::Session::STATE_INPROGRESS) { + emit voiceCaller_->in_progress(jid); + } +} + +// ---------------------------------------------------------------------------- + +/** + * \class JingleVoiceCaller + * \brief A Voice Calling implementation using libjingle. + */ + +JingleVoiceCaller::JingleVoiceCaller(PsiAccount* acc) : VoiceCaller(acc) +{ + initialized_ = false; +} + +void JingleVoiceCaller::initialize() +{ + if (initialized_) + return; + + QString jid = ((ClientStream&) account()->client()->client()->stream()).jid().full(); + qDebug(QString("jinglevoicecaller.cpp: Creating new caller for %1").arg(jid)); + if (jid.isEmpty()) { + qWarning("jinglevoicecaller.cpp: Empty JID"); + return; + } + + buzz::Jid j(jid.ascii()); + cricket::InitRandom(j.Str().c_str(),j.Str().size()); + + // Global variables + if (!socket_server_) { + socket_server_ = new cricket::PhysicalSocketServer(); + cricket::Thread *t = new cricket::Thread((cricket::PhysicalSocketServer*)(socket_server_)); + cricket::ThreadManager::SetCurrent(t); + t->Start(); + thread_ = t; + + stun_addr_ = new cricket::SocketAddress("64.233.167.126",19302); + network_manager_ = new cricket::NetworkManager(); + port_allocator_ = new cricket::BasicPortAllocator((cricket::NetworkManager*)(network_manager_), (cricket::SocketAddress*)(stun_addr_), /* relay server */ NULL); + } + + // Session manager + session_manager_ = new cricket::SessionManager((cricket::PortAllocator*)(port_allocator_), thread_); + slots_ = new JingleClientSlots(this); + session_manager_->SignalRequestSignaling.connect(slots_, &JingleClientSlots::requestSignaling); + session_manager_->OnSignalingReady(); + + // Phone Client + phone_client_ = new cricket::PhoneSessionClient(j, (cricket::SessionManager*)(session_manager_)); + phone_client_->SignalCallCreate.connect(slots_, &JingleClientSlots::callCreated); + phone_client_->SignalCallDestroy.connect(slots_, &JingleClientSlots::callDestroyed); + phone_client_->SignalSendStanza.connect(slots_, &JingleClientSlots::sendStanza); + + // IQ Responder + new JingleIQResponder(account()->client()->rootTask()); + + // Listen to incoming packets + connect(account()->client()->client(),SIGNAL(xmlIncoming(const QString&)),SLOT(receiveStanza(const QString&))); + + initialized_ = true; +} + + +void JingleVoiceCaller::deinitialize() +{ + if (!initialized_) + return; + + // Stop listening to incoming packets + disconnect(account()->client(),SIGNAL(xmlIncoming(const QString&)),this,SLOT(receiveStanza(const QString&))); + + // Disconnect signals (is this needed) + //phone_client_->SignalCallCreate.disconnect(slots_); + //phone_client_->SignalSendStanza.disconnect(slots_); + + // Delete objects + delete phone_client_; + delete session_manager_; + delete slots_; + + initialized_ = false; +} + + +JingleVoiceCaller::~JingleVoiceCaller() +{ +} + +bool JingleVoiceCaller::calling(const Jid& jid) +{ + return calls_.contains(jid.full()); +} + +void JingleVoiceCaller::call(const Jid& jid) +{ + qDebug(QString("jinglevoicecaller.cpp: Calling %1").arg(jid.full())); + cricket::Call *c = ((cricket::PhoneSessionClient*)(phone_client_))->CreateCall(); + c->InitiateSession(buzz::Jid(jid.full().ascii())); + phone_client_->SetFocus(c); +} + +void JingleVoiceCaller::accept(const Jid& j) +{ + qDebug("jinglevoicecaller.cpp: Accepting call"); + cricket::Call* call = calls_[j.full()]; + if (call != NULL) { + call->AcceptSession(call->sessions()[0]); + phone_client_->SetFocus(call); + } +} + +void JingleVoiceCaller::reject(const Jid& j) +{ + qDebug("jinglevoicecaller.cpp: Rejecting call"); + cricket::Call* call = calls_[j.full()]; + if (call != NULL) { + call->RejectSession(call->sessions()[0]); + calls_.remove(j.full()); + } +} + +void JingleVoiceCaller::terminate(const Jid& j) +{ + qDebug(QString("jinglevoicecaller.cpp: Terminating call to %1").arg(j.full())); + cricket::Call* call = calls_[j.full()]; + if (call != NULL) { + call->Terminate(); + calls_.remove(j.full()); + } +} + +void JingleVoiceCaller::sendStanza(const char* stanza) +{ + account()->client()->send(QString(stanza)); +} + +void JingleVoiceCaller::registerCall(const Jid& jid, cricket::Call* call) +{ + qDebug("jinglevoicecaller.cpp: Registering call\n"); + kdDebug(14000) << k_funcinfo << jid.full() << endl; + if (!calls_.contains(jid.full())) { + calls_[jid.full()] = call; + } +// else { +// qWarning("jinglevoicecaller.cpp: Auto-rejecting call because another call is currently open"); +// call->RejectSession(call->sessions()[0]); +// } +} + +void JingleVoiceCaller::removeCall(const Jid& j) +{ + qDebug(QString("JingleVoiceCaller: Removing call to %1").arg(j.full())); + calls_.remove(j.full()); +} + +void JingleVoiceCaller::receiveStanza(const QString& stanza) +{ + QDomDocument doc; + doc.setContent(stanza); + + // Check if it is offline presence from an open chat + if (doc.documentElement().tagName() == "presence") { + Jid from = Jid(doc.documentElement().attribute("from")); + QString type = doc.documentElement().attribute("type"); + if (type == "unavailable" && calls_.contains(from.full())) { + qDebug("JingleVoiceCaller: User went offline without closing a call."); + removeCall(from); + emit terminated(from); + } + return; + } + + // Check if the packet is destined for libjingle. + // We could use Session::IsClientStanza to check this, but this one crashes + // for some reason. + QDomNode n = doc.documentElement().firstChild(); + bool ok = false; + while (!n.isNull() && !ok) { + QDomElement e = n.toElement(); + if (!e.isNull() && e.attribute("xmlns") == JINGLE_NS) { + ok = true; + } + n = n.nextSibling(); + } + + // Spread the word + if (ok) { + qDebug(QString("jinglevoicecaller.cpp: Handing down %1").arg(stanza)); + buzz::XmlElement *e = buzz::XmlElement::ForStr(stanza.ascii()); + phone_client_->OnIncomingStanza(e); + } +} + +cricket::SocketServer* JingleVoiceCaller::socket_server_ = NULL; +cricket::Thread* JingleVoiceCaller::thread_ = NULL; +cricket::NetworkManager* JingleVoiceCaller::network_manager_ = NULL; +cricket::BasicPortAllocator* JingleVoiceCaller::port_allocator_ = NULL; +cricket::SocketAddress* JingleVoiceCaller::stun_addr_ = NULL; diff --git a/kopete/protocols/jabber/jingle/jinglevoicecaller.h b/kopete/protocols/jabber/jingle/jinglevoicecaller.h new file mode 100644 index 00000000..4448d530 --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglevoicecaller.h @@ -0,0 +1,72 @@ +#define PsiAccount JabberAccount + +#ifndef JINGLEVOICECALLER_H +#define JINGLEVOICECALLER_H + +#include + +#include "im.h" +#include "voicecaller.h" + +using namespace XMPP; + + +class PsiAccount; + +namespace cricket { + class SocketServer; + class Thread; + class NetworkManager; + class BasicPortAllocator; + class SessionManager; + class PhoneSessionClient; + class Call; + class SocketAddress; +} + +class JingleClientSlots; +class JingleCallSlots; + + +class JingleVoiceCaller : public VoiceCaller +{ + Q_OBJECT + + friend class JingleClientSlots; + +public: + JingleVoiceCaller(PsiAccount* account); + ~JingleVoiceCaller(); + + virtual bool calling(const Jid&); + + virtual void initialize(); + virtual void deinitialize(); + + virtual void call(const Jid&); + virtual void accept(const Jid&); + virtual void reject(const Jid&); + virtual void terminate(const Jid&); + +protected: + void sendStanza(const char*); + void registerCall(const Jid&, cricket::Call*); + void removeCall(const Jid&); + +protected slots: + void receiveStanza(const QString&); + +private: + bool initialized_; + static cricket::SocketServer *socket_server_; + static cricket::Thread *thread_; + static cricket::NetworkManager *network_manager_; + static cricket::BasicPortAllocator *port_allocator_; + static cricket::SocketAddress *stun_addr_; + cricket::SessionManager *session_manager_; + cricket::PhoneSessionClient *phone_client_; + JingleClientSlots *slots_; + QMap calls_; +}; + +#endif diff --git a/kopete/protocols/jabber/jingle/jinglevoicesession.cpp b/kopete/protocols/jabber/jingle/jinglevoicesession.cpp new file mode 100644 index 00000000..09019ce4 --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglevoicesession.cpp @@ -0,0 +1,333 @@ +/* + jinglevoicesession.cpp - Define a Jingle voice session. + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +// libjingle before everything else to not clash with Qt +#define POSIX +#include "talk/xmpp/constants.h" +#include "talk/base/sigslot.h" +#include "talk/xmpp/jid.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/xmlprinter.h" +#include "talk/base/network.h" +#include "talk/p2p/base/session.h" +#include "talk/p2p/base/sessionmanager.h" +#include "talk/p2p/base/helpers.h" +#include "talk/p2p/client/basicportallocator.h" +#include "talk/p2p/client/sessionclient.h" +#include "talk/base/physicalsocketserver.h" +#include "talk/base/thread.h" +#include "talk/base/socketaddress.h" +#include "talk/session/phone/call.h" +#include "talk/session/phone/phonesessionclient.h" +#include "talk/session/sessionsendtask.h" + +#include "jinglevoicesession.h" +#include "jinglesessionmanager.h" + +// Qt includes +#include + +// KDE includes +#include + +// Kopete Jabber includes +#include "jabberaccount.h" +#include "jabberprotocol.h" + +#include +#include + +#define JINGLE_NS "http://www.google.com/session" +#define JINGLE_VOICE_SESSION_NS "http://www.google.com/session/phone" + +static bool hasPeer(const JingleVoiceSession::JidList &jidList, const XMPP::Jid &peer) +{ + JingleVoiceSession::JidList::ConstIterator it, itEnd = jidList.constEnd(); + for(it = jidList.constBegin(); it != itEnd; ++it) + { + if( (*it).compare(peer, true) ) + return true; + } + + return false; +} +//BEGIN SlotsProxy +/** + * This class is used to receive signals from libjingle, + * which is are not compatible with Qt signals. + * So it's a proxy between JingeVoiceSession(qt)<->linjingle class. + */ +class JingleVoiceSession::SlotsProxy : public sigslot::has_slots<> +{ +public: + SlotsProxy(JingleVoiceSession *parent) + : voiceSession(parent) + {} + + void OnCallCreated(cricket::Call* call) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "SlotsProxy: CallCreated." << endl; + + call->SignalSessionState.connect(this, &JingleVoiceSession::SlotsProxy::PhoneSessionStateChanged); + voiceSession->setCall(call); + } + + void PhoneSessionStateChanged(cricket::Call *call, cricket::Session *session, cricket::Session::State state) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "State changed: " << state << endl; + + XMPP::Jid jid(session->remote_address().c_str()); + + // Do nothing if the session do not contain a peers. + //if( !voiceSession->peers().contains(jid) ) + if( !hasPeer(voiceSession->peers(), jid) ) + return; + + if (state == cricket::Session::STATE_INIT) + {} + else if (state == cricket::Session::STATE_SENTINITIATE) + {} + else if (state == cricket::Session::STATE_RECEIVEDINITIATE) + { + voiceSession->setCall(call); + } + else if (state == cricket::Session::STATE_SENTACCEPT) + {} + else if (state == cricket::Session::STATE_RECEIVEDACCEPT) + { + emit voiceSession->accepted(); + } + else if (state == cricket::Session::STATE_SENTMODIFY) + {} + else if (state == cricket::Session::STATE_RECEIVEDMODIFY) + { + //qWarning(QString("jinglevoicecaller.cpp: RECEIVEDMODIFY not implemented yet (was from %1)").arg(jid.full())); + } + else if (state == cricket::Session::STATE_SENTREJECT) + {} + else if (state == cricket::Session::STATE_RECEIVEDREJECT) + { + emit voiceSession->declined(); + } + else if (state == cricket::Session::STATE_SENTREDIRECT) + {} + else if (state == cricket::Session::STATE_SENTTERMINATE) + { + emit voiceSession->terminated(); + } + else if (state == cricket::Session::STATE_RECEIVEDTERMINATE) + { + emit voiceSession->terminated(); + } + else if (state == cricket::Session::STATE_INPROGRESS) + { + emit voiceSession->sessionStarted(); + } + } + + void OnSendingStanza(cricket::SessionClient*, const buzz::XmlElement *buzzStanza) + { + QString irisStanza(buzzStanza->Str().c_str()); + irisStanza.replace("cli:iq","iq"); + irisStanza.replace(":cli=","="); + + voiceSession->sendStanza(irisStanza); + } +private: + JingleVoiceSession *voiceSession; +}; +//END SlotsProxy + +//BEGIN JingleIQResponder +class JingleVoiceSession::JingleIQResponder : public XMPP::Task +{ +public: + JingleIQResponder(XMPP::Task *); + ~JingleIQResponder(); + + bool take(const QDomElement &); +}; + +/** + * \class JingleIQResponder + * \brief A task that responds to jingle candidate queries with an empty reply. + */ + +JingleVoiceSession::JingleIQResponder::JingleIQResponder(Task *parent) :Task(parent) +{ +} + +JingleVoiceSession::JingleIQResponder::~JingleIQResponder() +{ +} + +bool JingleVoiceSession::JingleIQResponder::take(const QDomElement &e) +{ + if(e.tagName() != "iq") + return false; + + QDomElement first = e.firstChild().toElement(); + if (!first.isNull() && first.attribute("xmlns") == JINGLE_NS) { + QDomElement iq = createIQ(doc(), "result", e.attribute("from"), e.attribute("id")); + send(iq); + return true; + } + + return false; +} +//END JingleIQResponder + +class JingleVoiceSession::Private +{ +public: + Private() + : phoneSessionClient(0L), currentCall(0L) + {} + + ~Private() + { + if(currentCall) + currentCall->Terminate(); + + delete currentCall; + } + + cricket::PhoneSessionClient *phoneSessionClient; + cricket::Call* currentCall; +}; + +JingleVoiceSession::JingleVoiceSession(JabberAccount *account, const JidList &peers) + : JingleSession(account, peers), d(new Private) +{ + slotsProxy = new SlotsProxy(this); + + buzz::Jid buzzJid( account->client()->jid().full().ascii() ); + + // Create the phone(voice) session. + d->phoneSessionClient = new cricket::PhoneSessionClient( buzzJid, account->sessionManager()->cricketSessionManager() ); + + d->phoneSessionClient->SignalSendStanza.connect(slotsProxy, &JingleVoiceSession::SlotsProxy::OnSendingStanza); + d->phoneSessionClient->SignalCallCreate.connect(slotsProxy, &JingleVoiceSession::SlotsProxy::OnCallCreated); + + // Listen to incoming packets + connect(account->client()->client(), SIGNAL(xmlIncoming(const QString&)), this, SLOT(receiveStanza(const QString&))); + + new JingleIQResponder(account->client()->rootTask()); +} + +JingleVoiceSession::~JingleVoiceSession() +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << endl; + delete slotsProxy; + delete d; +} + +QString JingleVoiceSession::sessionType() +{ + return QString(JINGLE_VOICE_SESSION_NS); +} + +void JingleVoiceSession::start() +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Starting a voice session..." << endl; + d->currentCall = d->phoneSessionClient->CreateCall(); + + QString firstPeerJid = ((XMPP::Jid)peers().first()).full(); + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "With peer: " << firstPeerJid << endl; + d->currentCall->InitiateSession( buzz::Jid(firstPeerJid.ascii()) ); + + d->phoneSessionClient->SetFocus(d->currentCall); +} + +void JingleVoiceSession::accept() +{ + if(d->currentCall) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Accepting a voice session..." << endl; + + d->currentCall->AcceptSession(d->currentCall->sessions()[0]); + d->phoneSessionClient->SetFocus(d->currentCall); + } +} + +void JingleVoiceSession::decline() +{ + if(d->currentCall) + { + d->currentCall->RejectSession(d->currentCall->sessions()[0]); + } +} + +void JingleVoiceSession::terminate() +{ + if(d->currentCall) + { + d->currentCall->Terminate(); + } +} + +void JingleVoiceSession::setCall(cricket::Call *call) +{ + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Updating cricket::call object." << endl; + d->currentCall = call; + d->phoneSessionClient->SetFocus(d->currentCall); +} + +void JingleVoiceSession::receiveStanza(const QString &stanza) +{ + QDomDocument doc; + doc.setContent(stanza); + + // Check if it is offline presence from an open chat + if( doc.documentElement().tagName() == "presence" ) + { + XMPP::Jid from = XMPP::Jid(doc.documentElement().attribute("from")); + QString type = doc.documentElement().attribute("type"); + if( type == "unavailable" && hasPeer(peers(), from) ) + { + //qDebug("JingleVoiceCaller: User went offline without closing a call."); + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "User went offline without closing a call." << endl; + emit terminated(); + } + return; + } + + // Check if the packet is destined for libjingle. + // We could use Session::IsClientStanza to check this, but this one crashes + // for some reason. + QDomNode node = doc.documentElement().firstChild(); + bool ok = false; + while( !node.isNull() && !ok ) + { + QDomElement element = node.toElement(); + if( !element.isNull() && element.attribute("xmlns") == JINGLE_NS) + { + ok = true; + } + node = node.nextSibling(); + } + + // Spread the word + if( ok ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Handing down buzz::stanza" << endl; + buzz::XmlElement *e = buzz::XmlElement::ForStr(stanza.ascii()); + d->phoneSessionClient->OnIncomingStanza(e); + } +} + +#include "jinglevoicesession.moc" diff --git a/kopete/protocols/jabber/jingle/jinglevoicesession.h b/kopete/protocols/jabber/jingle/jinglevoicesession.h new file mode 100644 index 00000000..e70d3b54 --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglevoicesession.h @@ -0,0 +1,70 @@ +/* + jinglevoicesession.h - Define a Jingle voice session. + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ +#ifndef JINGLEVOICESESSION_H +#define JINGLEVOICESESSION_H + +#include + +#include // XMPP::Jid +#include + +namespace cricket +{ + class Call; +} + +class JabberAccount; +class JingleSession; + +/** + * Implement a Jingle voice peer-to-peer session that is compatible with Google Talk voice offering. + * + * @author Michaël Larouche +*/ +class JingleVoiceSession : public JingleSession +{ + Q_OBJECT +public: + typedef QValueList JidList; + + JingleVoiceSession(JabberAccount *account, const JidList &peers); + virtual ~JingleVoiceSession(); + + virtual QString sessionType(); + +public slots: + virtual void accept(); + virtual void decline(); + virtual void start(); + virtual void terminate(); + +protected slots: + void receiveStanza(const QString &stanza); + +private: + void setCall(cricket::Call *call); + + class Private; + Private *d; + + class SlotsProxy; + SlotsProxy *slotsProxy; + + class JingleIQResponder; +}; + +#endif diff --git a/kopete/protocols/jabber/jingle/jinglevoicesessiondialog.cpp b/kopete/protocols/jabber/jingle/jinglevoicesessiondialog.cpp new file mode 100644 index 00000000..9fb61274 --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglevoicesessiondialog.cpp @@ -0,0 +1,208 @@ +/* + jinglevoicesessiondialog.cpp - GUI for a voice session. + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ +#include "jinglevoicesessiondialog.h" + +// Qt includes +#include +#include +#include + +// Jingle includes +// #include "jinglevoicesession.h" +// #include "jinglesessionmanager.h" +#include "voicecaller.h" + +// KDE includes +#include +#include + +// Kopete includes +#include "jabberaccount.h" +#include "jabbercontact.h" +#include "jabbercontactpool.h" + +#include "kopeteglobal.h" +#include "kopetemetacontact.h" + +using namespace XMPP; + +JingleVoiceSessionDialog::JingleVoiceSessionDialog(const Jid &peerJid, VoiceCaller *caller, QWidget *parent, const char *name) + : JingleVoiceSessionDialogBase(parent, name), m_session(caller), m_peerJid(peerJid), m_sessionState(Incoming) +{ + QString contactJid = m_peerJid.full(); + setCaption( i18n("Voice session with %1").arg(contactJid) ); + + connect(buttonAccept, SIGNAL(clicked()), this, SLOT(slotAcceptClicked())); + connect(buttonDecline, SIGNAL(clicked()), this, SLOT(slotDeclineClicked())); + connect(buttonTerminate, SIGNAL(clicked()), this, SLOT(slotTerminateClicked())); + +// NOTE: Disabled for 0.12 +#if 0 + connect(m_session, SIGNAL(sessionStarted()), this, SLOT(sessionStarted())); + connect(m_session, SIGNAL(accepted()), this, SLOT(sessionAccepted())); + connect(m_session, SIGNAL(declined()), this, SLOT(sessionDeclined())); + connect(m_session, SIGNAL(terminated()), this, SLOT(sessionTerminated())); +#endif + connect(m_session, SIGNAL(accepted( const Jid & )), this, SLOT( sessionAccepted(const Jid &) )); + connect(m_session, SIGNAL(in_progress( const Jid & )), this, SLOT( sessionStarted(const Jid &) )); + connect(m_session, SIGNAL(rejected( const Jid& )), this, SLOT( sessionDeclined(const Jid &) )); + connect(m_session, SIGNAL(terminated( const Jid& )), this, SLOT( sessionTerminated(const Jid &) )); + + // Find JabberContact for the peer and fill this dialog with contact information. + JabberContact *peerContact = static_cast( m_session->account()->contactPool()->findRelevantRecipient( m_peerJid ) ); + if( peerContact ) + { + setContactInformation( peerContact ); + } + + labelSessionStatus->setText( i18n("Incoming Session...") ); + buttonAccept->setEnabled(true); + buttonDecline->setEnabled(true); +} + +JingleVoiceSessionDialog::~JingleVoiceSessionDialog() +{ + //m_session->account()->sessionManager()->removeSession(m_session); +} + +void JingleVoiceSessionDialog::setContactInformation(JabberContact *contact) +{ + if( contact->metaContact() ) + { + labelDisplayName->setText( contact->metaContact()->displayName() ); + labelContactPhoto->setPixmap( QPixmap(contact->metaContact()->photo()) ); + } + else + { + labelDisplayName->setText( contact->nickName() ); + labelDisplayName->setPixmap( QPixmap(contact->property(Kopete::Global::Properties::self()->photo()).value().toString()) ); + } +} + +void JingleVoiceSessionDialog::start() +{ + labelSessionStatus->setText( i18n("Waiting for other peer...") ); + buttonAccept->setEnabled(false); + buttonDecline->setEnabled(false); + buttonTerminate->setEnabled(true); + //m_session->start(); + m_session->call( m_peerJid ); + m_sessionState = Waiting; +} + +void JingleVoiceSessionDialog::slotAcceptClicked() +{ + labelSessionStatus->setText( i18n("Session accepted.") ); + buttonAccept->setEnabled(false); + buttonDecline->setEnabled(false); + buttonTerminate->setEnabled(true); + + //m_session->accept(); + m_session->accept( m_peerJid ); + m_sessionState = Accepted; +} + +void JingleVoiceSessionDialog::slotDeclineClicked() +{ + labelSessionStatus->setText( i18n("Session declined.") ); + buttonAccept->setEnabled(false); + buttonDecline->setEnabled(false); + buttonTerminate->setEnabled(false); + + //m_session->decline(); + m_session->reject( m_peerJid ); + m_sessionState = Declined; + finalize(); +} + +void JingleVoiceSessionDialog::slotTerminateClicked() +{ + labelSessionStatus->setText( i18n("Session terminated.") ); + buttonAccept->setEnabled(false); + buttonDecline->setEnabled(false); + buttonTerminate->setEnabled(false); + + //m_session->terminate(); + m_session->terminate( m_peerJid ); + m_sessionState = Terminated; + finalize(); + close(); +} + +void JingleVoiceSessionDialog::sessionStarted(const Jid &jid) +{ + if( m_peerJid.compare(jid) ) + { + labelSessionStatus->setText( i18n("Session in progress.") ); + buttonAccept->setEnabled(false); + buttonDecline->setEnabled(false); + buttonTerminate->setEnabled(true); + m_sessionState = Started; + } +} + +void JingleVoiceSessionDialog::sessionAccepted(const Jid &jid) +{ + if( m_peerJid.compare(jid) ) + { + labelSessionStatus->setText( i18n("Session accepted.") ); + buttonAccept->setEnabled(false); + buttonDecline->setEnabled(false); + buttonTerminate->setEnabled(true); + m_sessionState = Accepted; + } +} + +void JingleVoiceSessionDialog::sessionDeclined(const Jid &jid) +{ + if( m_peerJid.compare(jid) ) + { + labelSessionStatus->setText( i18n("Session declined.") ); + buttonAccept->setEnabled(false); + buttonDecline->setEnabled(false); + buttonTerminate->setEnabled(false); + m_sessionState = Declined; + } +} + +void JingleVoiceSessionDialog::sessionTerminated(const Jid &jid) +{ + if( m_peerJid.compare(jid) ) + { + labelSessionStatus->setText( i18n("Session terminated.") ); + buttonAccept->setEnabled(false); + buttonDecline->setEnabled(false); + buttonTerminate->setEnabled(false); + m_sessionState = Terminated; + } +} + +void JingleVoiceSessionDialog::reject() +{ + finalize(); + QDialog::reject(); +} + +void JingleVoiceSessionDialog::finalize() +{ + disconnect(m_session, SIGNAL(accepted( const Jid & )), this, SLOT( sessionAccepted(const Jid &) )); + disconnect(m_session, SIGNAL(in_progress( const Jid & )), this, SLOT( sessionStarted(const Jid &) )); + disconnect(m_session, SIGNAL(rejected( const Jid& )), this, SLOT( sessionDeclined(const Jid &) )); + disconnect(m_session, SIGNAL(terminated( const Jid& )), this, SLOT( sessionTerminated(const Jid &) )); +} + +#include "jinglevoicesessiondialog.moc" diff --git a/kopete/protocols/jabber/jingle/jinglevoicesessiondialog.h b/kopete/protocols/jabber/jingle/jinglevoicesessiondialog.h new file mode 100644 index 00000000..29d0c091 --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglevoicesessiondialog.h @@ -0,0 +1,66 @@ +/* + jinglevoicesessiondialog.h - GUI for a voice session. + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ +#ifndef JINGLEVOICESESSIONDIALOG_H +#define JINGLEVOICESESSIONDIALOG_H + +#include "jinglevoicesessiondialogbase.h" + +#include +#include + +using namespace XMPP; + +class JabberContact; +class VoiceCaller; + +class JingleVoiceSessionDialog : public JingleVoiceSessionDialogBase +{ + Q_OBJECT +public: + enum SessionState { Incoming, Waiting, Accepted, Declined, Started, Terminated }; + + JingleVoiceSessionDialog(const Jid &peerJid, VoiceCaller *caller, QWidget *parent = 0, const char *name = 0); + ~JingleVoiceSessionDialog(); + +public slots: + void start(); + +protected slots: + void reject(); + +protected: + void finalize(); + +private slots: + void slotAcceptClicked(); + void slotDeclineClicked(); + void slotTerminateClicked(); + + void sessionStarted(const Jid &jid); + void sessionAccepted(const Jid &jid); + void sessionDeclined(const Jid &jid); + void sessionTerminated(const Jid &jid); + +private: + void setContactInformation(JabberContact *contact); + + VoiceCaller *m_session; + Jid m_peerJid; + SessionState m_sessionState; +}; + +#endif diff --git a/kopete/protocols/jabber/jingle/jinglevoicesessiondialogbase.ui b/kopete/protocols/jabber/jingle/jinglevoicesessiondialogbase.ui new file mode 100644 index 00000000..0dc9ab35 --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglevoicesessiondialogbase.ui @@ -0,0 +1,369 @@ + +JingleVoiceSessionDialogBase + + + JingleVoiceSessionDialogBase + + + + 0 + 0 + 329 + 188 + + + + JabberVoiceSessionDialogBase + + + + unnamed + + + + layout8 + + + + unnamed + + + + layout5 + + + + unnamed + + + + spacer9 + + + Horizontal + + + Expanding + + + + 16 + 20 + + + + + + textLabel1 + + + Voice session with: + + + + + spacer10 + + + Horizontal + + + Expanding + + + + 20 + 20 + + + + + + + + layout4 + + + + unnamed + + + + spacer7 + + + Horizontal + + + Expanding + + + + 16 + 20 + + + + + + labelContactPhoto + + + + 4 + 4 + 0 + 0 + + + + + 128 + 128 + + + + true + + + + + spacer8 + + + Horizontal + + + Expanding + + + + 16 + 20 + + + + + + + + layout7 + + + + unnamed + + + + spacer11 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + labelDisplayName + + + Contact displayname + + + + + spacer12 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + + + + + spacer5 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + line1 + + + HLine + + + Sunken + + + Horizontal + + + + + layout1 + + + + unnamed + + + + spacer1 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + buttonAccept + + + false + + + Accep&t + + + + + buttonDecline + + + false + + + &Decline + + + + + buttonTerminate + + + false + + + Termi&nate + + + + + spacer2 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + + + layout3 + + + + unnamed + + + + textLabel4 + + + Current status: + + + + + labelSessionStatus + + + + 5 + 7 + 0 + 0 + + + + Session status + + + + + + + spacer5_2 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + + + kpushbutton.h + kpushbutton.h + kpushbutton.h + + diff --git a/kopete/protocols/jabber/jingle/jinglewatchsessiontask.cpp b/kopete/protocols/jabber/jingle/jinglewatchsessiontask.cpp new file mode 100644 index 00000000..fc7de053 --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglewatchsessiontask.cpp @@ -0,0 +1,75 @@ +/* + jingleswatchsessiontask.cpp - Watch for incoming Jingle sessions. + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "jinglewatchsessiontask.h" + +#include + +#include "jabberprotocol.h" + +#define JINGLE_NS "http://www.google.com/session" + +JingleWatchSessionTask::JingleWatchSessionTask(XMPP::Task *parent) + : Task(parent) +{} + +JingleWatchSessionTask::~JingleWatchSessionTask() +{} + +//NOTE: This task watch for pre-JEP session. +bool JingleWatchSessionTask::take(const QDomElement &element) +{ + if(element.tagName() != "iq") + return false; + + QString sessionType, initiator; + + QDomElement first = element.firstChild().toElement(); + if( !first.isNull() && first.attribute("xmlns") == JINGLE_NS && first.tagName() == "session" ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Checking for incoming sesssion." << endl; + initiator = first.attribute("initiator"); + + // Only proceed initiate type Jingle XMPP call. + if( first.attribute("type") != QString::fromUtf8("initiate") ) + return false; + + int nodeIndex; + + QDomNodeList nodeList = first.childNodes(); + // Do not check first child + for(nodeIndex = 0; nodeIndex < nodeList.length(); nodeIndex++) + { + QDomElement nodeElement = nodeList.item(nodeIndex).toElement(); + if(nodeElement.tagName() == "description") + { + sessionType = nodeElement.attribute("xmlns"); + } + } + + if( !initiator.isEmpty() && !sessionType.isEmpty() ) + { + kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Emmiting incoming sesssion." << endl; + emit watchSession(sessionType, initiator); + return true; + } + } + + return false; +} + +#include "jinglewatchsessiontask.moc" \ No newline at end of file diff --git a/kopete/protocols/jabber/jingle/jinglewatchsessiontask.h b/kopete/protocols/jabber/jingle/jinglewatchsessiontask.h new file mode 100644 index 00000000..99b76661 --- /dev/null +++ b/kopete/protocols/jabber/jingle/jinglewatchsessiontask.h @@ -0,0 +1,39 @@ +/* + jingleswatchsessiontask.h - Watch for incoming Jingle sessions. + + Copyright (c) 2006 by Michaël Larouche + + Kopete (c) 2001-2006 by the Kopete developers + + ************************************************************************* + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ************************************************************************* +*/ +#ifndef JINGLEWATCHSESSIONTASK_H +#define JINGLEWATCHSESSIONTASK_H + +#include + +/** + * This task watch for incoming Jingle session and notify manager. + * It is declared in the header to be "moc"-able. + */ +class JingleWatchSessionTask : public XMPP::Task +{ + Q_OBJECT +public: + JingleWatchSessionTask(XMPP::Task *parent); + ~JingleWatchSessionTask(); + + bool take(const QDomElement &element); + +signals: + void watchSession(const QString &sessionType, const QString &initiator); +}; + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/AUTHORS b/kopete/protocols/jabber/jingle/libjingle/AUTHORS new file mode 100644 index 00000000..e491a9e7 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/AUTHORS @@ -0,0 +1 @@ +Google Inc. diff --git a/kopete/protocols/jabber/jingle/libjingle/COPYING b/kopete/protocols/jabber/jingle/libjingle/COPYING new file mode 100644 index 00000000..d58182b1 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/COPYING @@ -0,0 +1,25 @@ +Copyright (c) 2004--2005, Google 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: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * 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. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, 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. diff --git a/kopete/protocols/jabber/jingle/libjingle/ChangeLog b/kopete/protocols/jabber/jingle/libjingle/ChangeLog new file mode 100644 index 00000000..6d15ac52 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/ChangeLog @@ -0,0 +1,4 @@ +Libjingle + +0.1.0 - Dec 15 2005 + - Initial release diff --git a/kopete/protocols/jabber/jingle/libjingle/INSTALL b/kopete/protocols/jabber/jingle/libjingle/INSTALL new file mode 100644 index 00000000..a4b34144 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/INSTALL @@ -0,0 +1,229 @@ +Copyright 1994, 1995, 1996, 1999, 2000, 2001, 2002 Free Software +Foundation, Inc. + + This file is free documentation; the Free Software Foundation gives +unlimited permission to copy, distribute and modify it. + +Basic Installation +================== + + These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. (Caching is +disabled by default to prevent problems with accidental use of stale +cache files.) + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You only need +`configure.ac' if you want to change it or regenerate `configure' using +a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes awhile. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. Run `./configure --help' +for details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c89 CFLAGS=-O2 LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not support the `VPATH' +variable, you have to compile the package for one architecture at a +time in the source code directory. After you have installed the +package for one architecture, use `make distclean' before reconfiguring +for another architecture. + +Installation Names +================== + + By default, `make install' will install the package's files in +`/usr/local/bin', `/usr/local/man', etc. You can specify an +installation prefix other than `/usr/local' by giving `configure' the +option `--prefix=PATH'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PATH', the package will use +PATH as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=PATH' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + + There may be some features `configure' cannot figure out +automatically, but needs to determine by the type of machine the package +will run on. Usually, assuming the package is built to be run on the +_same_ architectures, `configure' can figure that out, but if it prints +a message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the `--target=TYPE' option to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + + Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +will cause the specified gcc to be used as the C compiler (unless it is +overridden in the site shell script). + +`configure' Invocation +====================== + + `configure' recognizes the following options to control how it +operates. + +`--help' +`-h' + Print a summary of the options to `configure', and exit. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. + diff --git a/kopete/protocols/jabber/jingle/libjingle/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/Makefile.am new file mode 100644 index 00000000..164f7058 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/Makefile.am @@ -0,0 +1,4 @@ +SUBDIRS=talk + +dist-hook: + sed -i -f talk/sanitize.sed `find $(distdir) -type f` \ No newline at end of file diff --git a/kopete/protocols/jabber/jingle/libjingle/NEWS b/kopete/protocols/jabber/jingle/libjingle/NEWS new file mode 100644 index 00000000..1694c754 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/NEWS @@ -0,0 +1 @@ +* Initial source release diff --git a/kopete/protocols/jabber/jingle/libjingle/README b/kopete/protocols/jabber/jingle/libjingle/README new file mode 100644 index 00000000..ec130b36 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/README @@ -0,0 +1,59 @@ +Libjingle + +Libjingle is a set of components provided by Google to interoperate with Google +Talk's peer-to-peer and voice capabilities. This package will create several +static libraries you may link to your project as needed. + +-talk - No source files in talk/, just these subdirectories +|-base - Contains basic low-level portable utility functions for +| things like threads and sockets +|-p2p - The P2P stack + |-base - Base p2p functionality + |-client - Hooks to tie it into XMPP +|-session - Signaling + |-phone - Signaling code specific to making phone calls +|-third_party - Components that aren't ours + |-mediastreamer - Media components for dealing with sound hardware and + | voice codecs +|-xmllite - XML parser +|-xmpp - XMPP engine + +In addition, this package contains two examples in talk/examples which +illustrate the basic concepts of how the provided classes work. + +The xmllite component of libjingle depends on expat. You can download expat +from http://expat.sourceforge.net/. + +mediastreamer, the media components used by the example applications depend on +the oRTP and iLBC components from linphone, which can be found at +http://www.linphone.org. Linphone, in turn depends on GLib, which can be found +at http://www.gtk.org. This GLib dependency should be removed in future +releases. + +Building Libjingle + +Once the dependencies are installed, run ./configure. ./configure will return +an error if it failed to locate the proper dependencies. If ./configure +succeeds, run 'make' to build the components and examples. + +When the build is complete, you can run the call example from +talk/examples/call. This will ask you for your GMail username and your GMail +auth cookie. Your GMail auth cookie is the GX cookie from mail.google.com +found in your web browser. + +Relay Server + +Libjingle will also build a relay server that may be used to relay traffic +when a direct peer-to-peer connection could not be established. The relay +server will build in talk/p2p/base/relayserver and will listen on UDP +ports 5000 and 5001. See the Libjingle Developer Guide at +http://code.google.com/apis/talk/index.html for information about configuring +a client to use this relay server. + +STUN Server + +Lastly, Libjingle builds a STUN server which implements the STUN protocol for +Simple Traversal of UDP over NAT. The STUN server is built as +talk/p2p/base/stunserver and listens on UDP port 7000. See the Libjingle +Developer Guide at http://code.google.com/apis/talk/index.html for information +about configuring a client to use this STUN server. diff --git a/kopete/protocols/jabber/jingle/libjingle/libjingle.pro b/kopete/protocols/jabber/jingle/libjingle/libjingle.pro new file mode 100644 index 00000000..53c8e293 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/libjingle.pro @@ -0,0 +1,142 @@ +TEMPLATE = lib +CONFIG += staticlib +CONFIG += debug + +target.extra = true + +exists(../../conf.pri) { + include(../../conf.pri) +} + +JINGLE_CPP = . +INCLUDEPATH += $$JINGLE_CPP $$JINGLE_CPP/talk/third_party/mediastreamer +DEFINES += POSIX +OBJECTS_DIR = $$JINGLE_CPP/.obj + +# Base +SOURCES += \ + $$JINGLE_CPP/talk/base/asyncpacketsocket.cc \ + $$JINGLE_CPP/talk/base/asynctcpsocket.cc \ + $$JINGLE_CPP/talk/base/asyncudpsocket.cc \ + $$JINGLE_CPP/talk/base/base64.cc \ + $$JINGLE_CPP/talk/base/bytebuffer.cc \ + $$JINGLE_CPP/talk/base/md5c.c \ + $$JINGLE_CPP/talk/base/messagequeue.cc \ + $$JINGLE_CPP/talk/base/network.cc \ + $$JINGLE_CPP/talk/base/physicalsocketserver.cc \ + $$JINGLE_CPP/talk/base/socketadapters.cc \ + $$JINGLE_CPP/talk/base/socketaddress.cc \ + $$JINGLE_CPP/talk/base/task.cc \ + $$JINGLE_CPP/talk/base/taskrunner.cc \ + $$JINGLE_CPP/talk/base/thread.cc \ + $$JINGLE_CPP/talk/base/time.cc + +# Not needed ? +#$$JINGLE_CPP/talk/base/socketaddresspair.cc \ +#$$JINGLE_CPP/talk/base/host.cc \ + +# P2P Base +SOURCES += \ + $$JINGLE_CPP/talk/p2p/base/helpers.cc \ + $$JINGLE_CPP/talk/p2p/base/p2psocket.cc \ + $$JINGLE_CPP/talk/p2p/base/port.cc \ + $$JINGLE_CPP/talk/p2p/base/relayport.cc \ + $$JINGLE_CPP/talk/p2p/base/session.cc \ + $$JINGLE_CPP/talk/p2p/base/sessionmanager.cc \ + $$JINGLE_CPP/talk/p2p/base/socketmanager.cc \ + $$JINGLE_CPP/talk/p2p/base/stun.cc \ + $$JINGLE_CPP/talk/p2p/base/stunport.cc \ + $$JINGLE_CPP/talk/p2p/base/stunrequest.cc \ + $$JINGLE_CPP/talk/p2p/base/tcpport.cc \ + $$JINGLE_CPP/talk/p2p/base/udpport.cc + +# P2P Client +SOURCES += \ + $$JINGLE_CPP/talk/p2p/client/basicportallocator.cc \ + $$JINGLE_CPP/talk/p2p/client/sessionclient.cc \ + $$JINGLE_CPP/talk/p2p/client/socketmonitor.cc + + +# XMLLite +SOURCES += \ + $$JINGLE_CPP/talk/xmllite/qname.cc \ + $$JINGLE_CPP/talk/xmllite/xmlbuilder.cc \ + $$JINGLE_CPP/talk/xmllite/xmlconstants.cc \ + $$JINGLE_CPP/talk/xmllite/xmlelement.cc \ + $$JINGLE_CPP/talk/xmllite/xmlnsstack.cc \ + $$JINGLE_CPP/talk/xmllite/xmlparser.cc \ + $$JINGLE_CPP/talk/xmllite/xmlprinter.cc + +# XMPP +SOURCES += \ + $$JINGLE_CPP/talk/xmpp/constants.cc \ + $$JINGLE_CPP/talk/xmpp/jid.cc \ + $$JINGLE_CPP/talk/xmpp/saslmechanism.cc \ + $$JINGLE_CPP/talk/xmpp/xmppclient.cc \ + $$JINGLE_CPP/talk/xmpp/xmppengineimpl.cc \ + $$JINGLE_CPP/talk/xmpp/xmppengineimpl_iq.cc \ + $$JINGLE_CPP/talk/xmpp/xmpplogintask.cc \ + $$JINGLE_CPP/talk/xmpp/xmppstanzaparser.cc \ + $$JINGLE_CPP/talk/xmpp/xmpptask.cc + +# Session +SOURCES += \ + $$JINGLE_CPP/talk/session/phone/call.cc \ + $$JINGLE_CPP/talk/session/phone/audiomonitor.cc \ + $$JINGLE_CPP/talk/session/phone/phonesessionclient.cc \ + $$JINGLE_CPP/talk/session/phone/channelmanager.cc \ + $$JINGLE_CPP/talk/session/phone/linphonemediaengine.cc \ + $$JINGLE_CPP/talk/session/phone/voicechannel.cc + +#contains(DEFINES, HAVE_PORTAUDIO) { +# SOURCES += \ +# $$JINGLE_CPP/talk/session/phone/portaudiomediaengine.cc +#} + + +# Mediastreamer +SOURCES += \ + $$JINGLE_CPP/talk/third_party/mediastreamer/audiostream.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/ms.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msAlawdec.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msAlawenc.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msbuffer.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/mscodec.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/mscopy.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msfdispatcher.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msfifo.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msfilter.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msilbcdec.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msilbcenc.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msMUlawdec.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msMUlawenc.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msnosync.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msossread.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msosswrite.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msqdispatcher.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msqueue.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msread.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msringplayer.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msrtprecv.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msrtpsend.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/mssoundread.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/mssoundwrite.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msspeexdec.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/msspeexenc.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/mssync.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/mstimer.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/mswrite.c \ + $$JINGLE_CPP/talk/third_party/mediastreamer/sndcard.c + +contains(DEFINES, HAVE_ALSA_ASOUNDLIB_H) { + SOURCES += $$JINGLE_CPP/talk/third_party/mediastreamer/alsacard.c +} + +contains(DEFINES, HAVE_PORTAUDIO) { + SOURCES += $$JINGLE_CPP/talk/third_party/mediastreamer/portaudiocard.c +} + +#$$JINGLE_CPP/talk/third_party/mediastreamer/osscard.c \ +#$$JINGLE_CPP/talk/third_party/mediastreamer/jackcard.c \ +#$$JINGLE_CPP/talk/third_party/mediastreamer/hpuxsndcard.c \ + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/Makefile.am new file mode 100644 index 00000000..2a845dc0 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=base p2p xmllite xmpp session third_party diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/base/Makefile.am new file mode 100644 index 00000000..2921049a --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/Makefile.am @@ -0,0 +1,62 @@ +## Does not compile with final +KDE_OPTIONS = nofinal + +libcricketbase_la_SOURCES = socketaddress.cc \ + jtime.cc \ + asyncudpsocket.cc \ + messagequeue.cc \ + thread.cc \ + physicalsocketserver.cc \ + bytebuffer.cc \ + asyncpacketsocket.cc \ + network.cc \ + asynctcpsocket.cc \ + socketadapters.cc \ + md5c.c \ + base64.cc \ + task.cc \ + taskrunner.cc \ + host.cc \ + socketaddresspair.cc + +noinst_HEADERS = asyncfile.h \ + common.h \ + asyncpacketsocket.h \ + socketfactory.h \ + asyncsocket.h \ + socket.h \ + asynctcpsocket.h \ + linked_ptr.h \ + asyncudpsocket.h \ + logging.h \ + socketserver.h \ + base64.h \ + md5.h \ + stl_decl.h \ + basicdefs.h \ + messagequeue.h \ + basictypes.h \ + stringutils.h \ + bytebuffer.h \ + task.h \ + byteorder.h \ + taskrunner.h \ + criticalsection.h \ + network.h \ + thread.h \ + jtime.h \ + physicalsocketserver.h \ + proxyinfo.h \ + host.h \ + scoped_ptr.h \ + sigslot.h \ + winping.h \ + socketadapters.h \ + socketaddress.h \ + host.h \ + socketaddresspair.h + +AM_CPPFLAGS = -DPOSIX -I$(srcdir)/../.. -I$(top_builddir) $(all_includes) +noinst_LTLIBRARIES = libcricketbase.la +DEFAULT_INCLUDES = -I$(srcdir)/../.. + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncfile.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncfile.h new file mode 100644 index 00000000..0faac9ea --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncfile.h @@ -0,0 +1,56 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef CRICKET_BASE_ASYNCFILEH_H__ +#define CRICKET_BASE_ASYNCFILEH_H__ + +#include "talk/base/sigslot.h" + +namespace cricket { + +// Provides the ability to perform file I/O asynchronously. +// TODO: Create a common base class with AsyncSocket. +class AsyncFile { +public: + virtual ~AsyncFile() {} + + // Determines whether the file will receive read events. + virtual bool readable() = 0; + virtual void set_readable(bool value) = 0; + + // Determines whether the file will receive write events. + virtual bool writable() = 0; + virtual void set_writable(bool value) = 0; + + sigslot::signal1 SignalReadEvent; + sigslot::signal1 SignalWriteEvent; + sigslot::signal2 SignalCloseEvent; +}; + +} // namespace cricket + +#endif // CRICKET_BASE_ASYNCFILEH_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncpacketsocket.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncpacketsocket.cc new file mode 100644 index 00000000..10cfa617 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncpacketsocket.cc @@ -0,0 +1,83 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/base/asyncpacketsocket.h" + +namespace cricket { + +AsyncPacketSocket::AsyncPacketSocket(AsyncSocket* socket) : socket_(socket) { +} + +AsyncPacketSocket::~AsyncPacketSocket() { + delete socket_; +} + +SocketAddress AsyncPacketSocket::GetLocalAddress() const { + return socket_->GetLocalAddress(); +} + +SocketAddress AsyncPacketSocket::GetRemoteAddress() const { + return socket_->GetRemoteAddress(); +} + +int AsyncPacketSocket::Bind(const SocketAddress& addr) { + return socket_->Bind(addr); +} + +int AsyncPacketSocket::Connect(const SocketAddress& addr) { + return socket_->Connect(addr); +} + +int AsyncPacketSocket::Send(const void *pv, size_t cb) { + return socket_->Send(pv, cb); +} + +int AsyncPacketSocket::SendTo( + const void *pv, size_t cb, const SocketAddress& addr) { + return socket_->SendTo(pv, cb, addr); +} + +int AsyncPacketSocket::Close() { + return socket_->Close(); +} + +int AsyncPacketSocket::SetOption(Socket::Option opt, int value) { + return socket_->SetOption(opt, value); +} + +int AsyncPacketSocket::GetError() const { + return socket_->GetError(); +} + +void AsyncPacketSocket::SetError(int error) { + return socket_->SetError(error); +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncpacketsocket.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncpacketsocket.h new file mode 100644 index 00000000..b5119c8d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncpacketsocket.h @@ -0,0 +1,63 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __ASYNCPACKETSOCKET_H__ +#define __ASYNCPACKETSOCKET_H__ + +#include "talk/base/asyncsocket.h" + +namespace cricket { + +// Provides the ability to receive packets asynchronously. Sends are not +// buffered since it is acceptable to drop packets under high load. +class AsyncPacketSocket : public sigslot::has_slots<> { +public: + AsyncPacketSocket(AsyncSocket* socket); + virtual ~AsyncPacketSocket(); + + // Relevant socket methods: + virtual SocketAddress GetLocalAddress() const; + virtual SocketAddress GetRemoteAddress() const; + virtual int Bind(const SocketAddress& addr); + virtual int Connect(const SocketAddress& addr); + virtual int Send(const void *pv, size_t cb); + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr); + virtual int Close(); + virtual int SetOption(Socket::Option opt, int value); + virtual int GetError() const; + virtual void SetError(int error); + + // Emitted each time a packet is read. + sigslot::signal4 SignalReadPacket; + +protected: + AsyncSocket* socket_; +}; + +} // namespace cricket + +#endif // __ASYNCPACKETSOCKET_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncsocket.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncsocket.h new file mode 100644 index 00000000..d6404232 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncsocket.h @@ -0,0 +1,91 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __ASYNCSOCKET_H__ +#define __ASYNCSOCKET_H__ + +#include "talk/base/sigslot.h" +#include "talk/base/socket.h" + +namespace cricket { + +// Provides the ability to perform socket I/O asynchronously. +class AsyncSocket : public Socket, public sigslot::has_slots<> { +public: + virtual ~AsyncSocket() {} + + sigslot::signal1 SignalReadEvent; // ready to read + sigslot::signal1 SignalWriteEvent; // ready to write + sigslot::signal1 SignalConnectEvent; // connected + sigslot::signal2 SignalCloseEvent; // closed + // TODO: error +}; + +class AsyncSocketAdapter : public AsyncSocket { +public: + AsyncSocketAdapter(Socket * socket) : socket_(socket) { + } + AsyncSocketAdapter(AsyncSocket * socket) : socket_(socket) { + socket->SignalConnectEvent.connect(this, &AsyncSocketAdapter::OnConnectEvent); + socket->SignalReadEvent.connect(this, &AsyncSocketAdapter::OnReadEvent); + socket->SignalWriteEvent.connect(this, &AsyncSocketAdapter::OnWriteEvent); + socket->SignalCloseEvent.connect(this, &AsyncSocketAdapter::OnCloseEvent); + } + virtual ~AsyncSocketAdapter() { delete socket_; } + + virtual SocketAddress GetLocalAddress() const { return socket_->GetLocalAddress(); } + virtual SocketAddress GetRemoteAddress() const { return socket_->GetRemoteAddress(); } + + virtual int Bind(const SocketAddress& addr) { return socket_->Bind(addr); } + virtual int Connect(const SocketAddress& addr) { return socket_->Connect(addr); } + virtual int Send(const void *pv, size_t cb) { return socket_->Send(pv, cb); } + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr) { return socket_->SendTo(pv, cb, addr); } + virtual int Recv(void *pv, size_t cb) { return socket_->Recv(pv, cb); } + virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr) { return socket_->RecvFrom(pv, cb, paddr); } + virtual int Listen(int backlog) { return socket_->Listen(backlog); } + virtual Socket *Accept(SocketAddress *paddr) { return socket_->Accept(paddr); } + virtual int Close() { return socket_->Close(); } + virtual int GetError() const { return socket_->GetError(); } + virtual void SetError(int error) { return socket_->SetError(error); } + + virtual ConnState GetState() const { return socket_->GetState(); } + + virtual int EstimateMTU(uint16* mtu) { return socket_->EstimateMTU(mtu); } + virtual int SetOption(Option opt, int value) { return socket_->SetOption(opt, value); } + +protected: + virtual void OnConnectEvent(AsyncSocket * socket) { SignalConnectEvent(this); } + virtual void OnReadEvent(AsyncSocket * socket) { SignalReadEvent(this); } + virtual void OnWriteEvent(AsyncSocket * socket) { SignalWriteEvent(this); } + virtual void OnCloseEvent(AsyncSocket * socket, int err) { SignalCloseEvent(this, err); } + + Socket * socket_; +}; + +} // namespace cricket + +#endif // __ASYNCSOCKET_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/asynctcpsocket.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/asynctcpsocket.cc new file mode 100644 index 00000000..6d4697a6 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/asynctcpsocket.cc @@ -0,0 +1,197 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/base/asynctcpsocket.h" +#include "talk/base/byteorder.h" +#include "talk/base/common.h" +#include "talk/base/logging.h" + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::strerror; +} +#endif + +#ifdef POSIX +extern "C" { +#include +} +#endif // POSIX + +namespace cricket { + +const size_t MAX_PACKET_SIZE = 64 * 1024; + +typedef uint16 PacketLength; +const size_t PKT_LEN_SIZE = sizeof(PacketLength); + +const size_t BUF_SIZE = MAX_PACKET_SIZE + PKT_LEN_SIZE; + +AsyncTCPSocket::AsyncTCPSocket(AsyncSocket* socket) : AsyncPacketSocket(socket), insize_(BUF_SIZE), inpos_(0), outsize_(BUF_SIZE), outpos_(0) { + inbuf_ = new char[insize_]; + outbuf_ = new char[outsize_]; + + ASSERT(socket_ != NULL); + socket_->SignalConnectEvent.connect(this, &AsyncTCPSocket::OnConnectEvent); + socket_->SignalReadEvent.connect(this, &AsyncTCPSocket::OnReadEvent); + socket_->SignalWriteEvent.connect(this, &AsyncTCPSocket::OnWriteEvent); + socket_->SignalCloseEvent.connect(this, &AsyncTCPSocket::OnCloseEvent); +} + +AsyncTCPSocket::~AsyncTCPSocket() { + delete [] inbuf_; + delete [] outbuf_; +} + +int AsyncTCPSocket::Send(const void *pv, size_t cb) { + if (cb > MAX_PACKET_SIZE) { + socket_->SetError(EMSGSIZE); + return -1; + } + + // If we are blocking on send, then silently drop this packet + if (outpos_) + return static_cast(cb); + + PacketLength pkt_len = HostToNetwork16(static_cast(cb)); + memcpy(outbuf_, &pkt_len, PKT_LEN_SIZE); + memcpy(outbuf_ + PKT_LEN_SIZE, pv, cb); + outpos_ = PKT_LEN_SIZE + cb; + + int res = Flush(); + if (res <= 0) { + // drop packet if we made no progress + outpos_ = 0; + return res; + } + + // We claim to have sent the whole thing, even if we only sent partial + return static_cast(cb); +} + +int AsyncTCPSocket::SendTo(const void *pv, size_t cb, const SocketAddress& addr) { + if (addr == GetRemoteAddress()) + return Send(pv, cb); + + ASSERT(false); + socket_->SetError(ENOTCONN); + return -1; +} + +int AsyncTCPSocket::SendRaw(const void * pv, size_t cb) { + if (outpos_ + cb > outsize_) { + socket_->SetError(EMSGSIZE); + return -1; + } + + memcpy(outbuf_ + outpos_, pv, cb); + outpos_ += cb; + + return Flush(); +} + +void AsyncTCPSocket::ProcessInput(char * data, size_t& len) { + SocketAddress remote_addr(GetRemoteAddress()); + + while (true) { + if (len < PKT_LEN_SIZE) + return; + + PacketLength pkt_len; + memcpy(&pkt_len, data, PKT_LEN_SIZE); + pkt_len = NetworkToHost16(pkt_len); + + if (len < PKT_LEN_SIZE + pkt_len) + return; + + SignalReadPacket(data + PKT_LEN_SIZE, pkt_len, remote_addr, this); + + len -= PKT_LEN_SIZE + pkt_len; + if (len > 0) { + memmove(data, data + PKT_LEN_SIZE + pkt_len, len); + } + } +} + +int AsyncTCPSocket::Flush() { + int res = socket_->Send(outbuf_, outpos_); + if (res <= 0) { + return res; + } + if (static_cast(res) <= outpos_) { + outpos_ -= res; + } else { + ASSERT(false); + return -1; + } + if (outpos_ > 0) { + memmove(outbuf_, outbuf_ + res, outpos_); + } + return res; +} + +void AsyncTCPSocket::OnConnectEvent(AsyncSocket* socket) { + SignalConnect(this); +} + +void AsyncTCPSocket::OnReadEvent(AsyncSocket* socket) { + ASSERT(socket == socket_); + + int len = socket_->Recv(inbuf_ + inpos_, insize_ - inpos_); + if (len < 0) { + // TODO: Do something better like forwarding the error to the user. + LOG(INFO) << "recvfrom: " << errno << " " << std::strerror(errno); + return; + } + + inpos_ += len; + + ProcessInput(inbuf_, inpos_); + + if (inpos_ >= insize_) { + LOG(INFO) << "input buffer overflow"; + ASSERT(false); + inpos_ = 0; + } +} + +void AsyncTCPSocket::OnWriteEvent(AsyncSocket* socket) { + ASSERT(socket == socket_); + + if (outpos_ > 0) { + Flush(); + } +} + +void AsyncTCPSocket::OnCloseEvent(AsyncSocket* socket, int error) { + SignalClose(this, error); +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/asynctcpsocket.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/asynctcpsocket.h new file mode 100644 index 00000000..e93e5e7d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/asynctcpsocket.h @@ -0,0 +1,68 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __ASYNCTCPSOCKET_H__ +#define __ASYNCTCPSOCKET_H__ + +#include "talk/base/asyncpacketsocket.h" + +namespace cricket { + +// Simulates UDP semantics over TCP. Send and Recv packet sizes +// are preserved, and drops packets silently on Send, rather than +// buffer them in user space. +class AsyncTCPSocket : public AsyncPacketSocket { +public: + AsyncTCPSocket(AsyncSocket* socket); + virtual ~AsyncTCPSocket(); + + virtual int Send(const void *pv, size_t cb); + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr); + + sigslot::signal1 SignalConnect; + sigslot::signal2 SignalClose; + +protected: + int SendRaw(const void * pv, size_t cb); + virtual void ProcessInput(char * data, size_t& len); + +private: + char* inbuf_, * outbuf_; + size_t insize_, inpos_, outsize_, outpos_; + + int Flush(); + + // Called by the underlying socket + void OnConnectEvent(AsyncSocket* socket); + void OnReadEvent(AsyncSocket* socket); + void OnWriteEvent(AsyncSocket* socket); + void OnCloseEvent(AsyncSocket* socket, int error); +}; + +} // namespace cricket + +#endif // __ASYNCSTCPOCKET_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncudpsocket.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncudpsocket.cc new file mode 100644 index 00000000..5b8c2466 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncudpsocket.cc @@ -0,0 +1,83 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/base/asyncudpsocket.h" +#include "talk/base/logging.h" +#include +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::strerror; +} +#endif + +#ifdef POSIX +extern "C" { +#include +} +#endif // POSIX + +namespace cricket { + +const int BUF_SIZE = 64 * 1024; + +AsyncUDPSocket::AsyncUDPSocket(AsyncSocket* socket) : AsyncPacketSocket(socket) { + size_ = BUF_SIZE; + buf_ = new char[size_]; + + assert(socket_); + // The socket should start out readable but not writable. + socket_->SignalReadEvent.connect(this, &AsyncUDPSocket::OnReadEvent); +} + +AsyncUDPSocket::~AsyncUDPSocket() { + delete [] buf_; +} + +void AsyncUDPSocket::OnReadEvent(AsyncSocket* socket) { + assert(socket == socket_); + + SocketAddress remote_addr; + int len = socket_->RecvFrom(buf_, size_, &remote_addr); + if (len < 0) { + // TODO: Do something better like forwarding the error to the user. + PLOG(LS_ERROR, socket_->GetError()) << "recvfrom"; + return; + } + + // TODO: Make sure that we got all of the packet. If we did not, then we + // should resize our buffer to be large enough. + + SignalReadPacket(buf_, (size_t)len, remote_addr, this); +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncudpsocket.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncudpsocket.h new file mode 100644 index 00000000..7fac7713 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/asyncudpsocket.h @@ -0,0 +1,59 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __ASYNCUDPSOCKET_H__ +#define __ASYNCUDPSOCKET_H__ + +#include "talk/base/asyncpacketsocket.h" +#include "talk/base/socketfactory.h" + +namespace cricket { + +// Provides the ability to receive packets asynchronously. Sends are not +// buffered since it is acceptable to drop packets under high load. +class AsyncUDPSocket : public AsyncPacketSocket { +public: + AsyncUDPSocket(AsyncSocket* socket); + virtual ~AsyncUDPSocket(); + +private: + char* buf_; + size_t size_; + + // Called when the underlying socket is ready to be read from. + void OnReadEvent(AsyncSocket* socket); +}; + +// Creates a new socket for sending asynchronous UDP packets using an +// asynchronous socket from the given factory. +inline AsyncUDPSocket* CreateAsyncUDPSocket(SocketFactory* factory) { + return new AsyncUDPSocket(factory->CreateAsyncSocket(SOCK_DGRAM)); +} + +} // namespace cricket + +#endif // __ASYNCSUDPOCKET_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/base64.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/base64.cc new file mode 100644 index 00000000..e0ec1b90 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/base64.cc @@ -0,0 +1,194 @@ + +//********************************************************************* +//* Base64 - a simple base64 encoder and decoder. +//* +//* Copyright (c) 1999, Bob Withers - bwit@pobox.com +//* +//* This code may be freely used for any purpose, either personal +//* or commercial, provided the authors copyright notice remains +//* intact. +//* +//* Enhancements by Stanley Yamane: +//* o reverse lookup table for the decode function +//* o reserve string buffer space in advance +//* +//********************************************************************* + +#include "talk/base/base64.h" + +using namespace std; + +static const char fillchar = '='; +static const string::size_type np = string::npos; + +const string Base64::Base64Table( + // 0000000000111111111122222222223333333333444444444455555555556666 + // 0123456789012345678901234567890123456789012345678901234567890123 + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); + +// Decode Table gives the index of any valid base64 character in the Base64 table] +// 65 == A, 97 == a, 48 == 0, 43 == +, 47 == / + + // 0 1 2 3 4 5 6 7 8 9 +const string::size_type Base64::DecodeTable[] = { + np,np,np,np,np,np,np,np,np,np, // 0 - 9 + np,np,np,np,np,np,np,np,np,np, //10 -19 + np,np,np,np,np,np,np,np,np,np, //20 -29 + np,np,np,np,np,np,np,np,np,np, //30 -39 + np,np,np,62,np,np,np,63,52,53, //40 -49 + 54,55,56,57,58,59,60,61,np,np, //50 -59 + np,np,np,np,np, 0, 1, 2, 3, 4, //60 -69 + 5, 6, 7, 8, 9,10,11,12,13,14, //70 -79 + 15,16,17,18,19,20,21,22,23,24, //80 -89 + 25,np,np,np,np,np,np,26,27,28, //90 -99 + 29,30,31,32,33,34,35,36,37,38, //100 -109 + 39,40,41,42,43,44,45,46,47,48, //110 -119 + 49,50,51,np,np,np,np,np,np,np, //120 -129 + np,np,np,np,np,np,np,np,np,np, //130 -139 + np,np,np,np,np,np,np,np,np,np, //140 -149 + np,np,np,np,np,np,np,np,np,np, //150 -159 + np,np,np,np,np,np,np,np,np,np, //160 -169 + np,np,np,np,np,np,np,np,np,np, //170 -179 + np,np,np,np,np,np,np,np,np,np, //180 -189 + np,np,np,np,np,np,np,np,np,np, //190 -199 + np,np,np,np,np,np,np,np,np,np, //200 -209 + np,np,np,np,np,np,np,np,np,np, //210 -219 + np,np,np,np,np,np,np,np,np,np, //220 -229 + np,np,np,np,np,np,np,np,np,np, //230 -239 + np,np,np,np,np,np,np,np,np,np, //240 -249 + np,np,np,np,np,np //250 -256 +}; + +string Base64::encodeFromArray(const char * data, size_t len) { + size_t i; + char c; + string ret; + + ret.reserve(len * 2); + + for (i = 0; i < len; ++i) + { + c = (data[i] >> 2) & 0x3f; + ret.append(1, Base64Table[c]); + c = (data[i] << 4) & 0x3f; + if (++i < len) + c |= (data[i] >> 4) & 0x0f; + + ret.append(1, Base64Table[c]); + if (i < len) + { + c = (data[i] << 2) & 0x3f; + if (++i < len) + c |= (data[i] >> 6) & 0x03; + + ret.append(1, Base64Table[c]); + } + else + { + ++i; + ret.append(1, fillchar); + } + + if (i < len) + { + c = data[i] & 0x3f; + ret.append(1, Base64Table[c]); + } + else + { + ret.append(1, fillchar); + } + } + + return(ret); +} + + +string Base64::encode(const string& data) +{ + string::size_type i; + char c; + string::size_type len = data.length(); + string ret; + + ret.reserve(len * 2); + + for (i = 0; i < len; ++i) + { + c = (data[i] >> 2) & 0x3f; + ret.append(1, Base64Table[c]); + c = (data[i] << 4) & 0x3f; + if (++i < len) + c |= (data[i] >> 4) & 0x0f; + + ret.append(1, Base64Table[c]); + if (i < len) + { + c = (data[i] << 2) & 0x3f; + if (++i < len) + c |= (data[i] >> 6) & 0x03; + + ret.append(1, Base64Table[c]); + } + else + { + ++i; + ret.append(1, fillchar); + } + + if (i < len) + { + c = data[i] & 0x3f; + ret.append(1, Base64Table[c]); + } + else + { + ret.append(1, fillchar); + } + } + + return(ret); +} + +string Base64::decode(const string& data) +{ + string::size_type i; + char c; + char c1; + string::size_type len = data.length(); + string ret; + + ret.reserve(len); + + for (i = 0; i < len; ++i) + { + c = static_cast(DecodeTable[static_cast(data[i])]); + ++i; + c1 = static_cast(DecodeTable[static_cast(data[i])]); + c = (c << 2) | ((c1 >> 4) & 0x3); + ret.append(1, c); + if (++i < len) + { + c = data[i]; + if (fillchar == c) + break; + + c = static_cast(DecodeTable[static_cast(data[i])]); + c1 = ((c1 << 4) & 0xf0) | ((c >> 2) & 0xf); + ret.append(1, c1); + } + + if (++i < len) + { + c1 = data[i]; + if (fillchar == c1) + break; + + c1 = static_cast(DecodeTable[static_cast(data[i])]); + c = ((c << 6) & 0xc0) | c1; + ret.append(1, c); + } + } + + return(ret); +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/base64.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/base64.h new file mode 100644 index 00000000..4622b329 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/base64.h @@ -0,0 +1,29 @@ + +//********************************************************************* +//* C_Base64 - a simple base64 encoder and decoder. +//* +//* Copyright (c) 1999, Bob Withers - bwit@pobox.com +//* +//* This code may be freely used for any purpose, either personal +//* or commercial, provided the authors copyright notice remains +//* intact. +//********************************************************************* + +#ifndef Base64_H +#define Base64_H + +#include +using std::string; // comment if your compiler doesn't use namespaces + +class Base64 +{ +public: + static string encode(const string & data); + static string decode(const string & data); + static string encodeFromArray(const char * data, size_t len); +private: + static const string Base64Table; + static const string::size_type DecodeTable[]; +}; + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/basicdefs.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/basicdefs.h new file mode 100644 index 00000000..171bc9f9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/basicdefs.h @@ -0,0 +1,53 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __BASICDEFS_H__ +#define __BASICDEFS_H__ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + + +// Used in GUI when referring to the product name (& Version Resource Product Name) +#define PRODUCT_NAME "Google Talk" + +// Used in GUI when referring to the publisher of the product +#define COMPANY_NAME "Google" + +// Used in filenames, directories, registry key names, etc to refer to the product +#define DIRECTORY_NAME "Google Talk" + +// Used in URLs, registry values, etc, where we prefer not to use a space +#define PRODUCT_SIGNATURE "googletalk" + +// Used whenever we do HTTP +#define USERAGENT_STRING "Google Talk" + +#define ARRAY_SIZE(x) (static_cast((sizeof(x)/sizeof(x[0])))) + +#endif // __BASICDEFS_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/basictypes.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/basictypes.h new file mode 100644 index 00000000..ea393c09 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/basictypes.h @@ -0,0 +1,85 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __BASICTYPES_H__ +#define __BASICTYPES_H__ + +#ifdef COMPILER_MSVC +typedef __int64 int64; +#else +typedef long long int64; +#endif /* COMPILER_MSVC */ +typedef long int32; +typedef short int16; +typedef char int8; + +#ifdef COMPILER_MSVC +typedef unsigned __int64 uint64; +typedef __int64 int64; +#else +typedef unsigned long long uint64; +typedef long long int64; +#endif /* COMPILER_MSVC */ +typedef unsigned long uint32; +typedef unsigned short uint16; +typedef unsigned char uint8; + +#ifdef WIN32 +typedef int socklen_t; +#endif + +namespace cricket { + template inline T _min(T a, T b) { return (a > b) ? b : a; } + template inline T _max(T a, T b) { return (a < b) ? b : a; } +} + +// A macro to disallow the evil copy constructor and operator= functions +// This should be used in the private: declarations for a class +#define DISALLOW_EVIL_CONSTRUCTORS(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + +// A macro to disallow all the implicit constructors, namely the +// default constructor, copy constructor and operator= functions. +// +// This should be used in the private: declarations for a class +// that wants to prevent anyone from instantiating it. This is +// especially useful for classes containing only static methods. +#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName(); \ + DISALLOW_EVIL_CONSTRUCTORS(TypeName) + +#ifndef UNUSED +#define UNUSED(x) Unused(static_cast(&x)) +#define UNUSED2(x,y) Unused(static_cast(&x)); Unused(static_cast(&y)) +#define UNUSED3(x,y,z) Unused(static_cast(&x)); Unused(static_cast(&y)); Unused(static_cast(&z)) +#define UNUSED4(x,y,z,a) Unused(static_cast(&x)); Unused(static_cast(&y)); Unused(static_cast(&z)); Unused(static_cast(&a)) +#define UNUSED5(x,y,z,a,b) Unused(static_cast(&x)); Unused(static_cast(&y)); Unused(static_cast(&z)); Unused(static_cast(&a)); Unused(static_cast(&b)) +inline void Unused(const void *) { } +#endif // UNUSED + +#endif // __BASICTYPES_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/bytebuffer.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/bytebuffer.cc new file mode 100644 index 00000000..067f50ed --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/bytebuffer.cc @@ -0,0 +1,165 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/base/basictypes.h" +#include "talk/base/bytebuffer.h" +#include "talk/base/byteorder.h" +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::memcpy; +} +#endif + +namespace cricket { + +const int DEFAULT_SIZE = 4096; + +ByteBuffer::ByteBuffer() { + start_ = 0; + end_ = 0; + size_ = DEFAULT_SIZE; + bytes_ = new char[size_]; +} + +ByteBuffer::ByteBuffer(const char* bytes, size_t len) { + start_ = 0; + end_ = len; + size_ = len; + bytes_ = new char[size_]; + memcpy(bytes_, bytes, end_); +} + +ByteBuffer::ByteBuffer(const char* bytes) { + start_ = 0; + end_ = strlen(bytes); + size_ = end_; + bytes_ = new char[size_]; + memcpy(bytes_, bytes, end_); +} + +ByteBuffer::~ByteBuffer() { + delete bytes_; +} + +bool ByteBuffer::ReadUInt8(uint8& val) { + return ReadBytes(reinterpret_cast(&val), 1); +} + +bool ByteBuffer::ReadUInt16(uint16& val) { + uint16 v; + if (!ReadBytes(reinterpret_cast(&v), 2)) { + return false; + } else { + val = NetworkToHost16(v); + return true; + } +} + +bool ByteBuffer::ReadUInt32(uint32& val) { + uint32 v; + if (!ReadBytes(reinterpret_cast(&v), 4)) { + return false; + } else { + val = NetworkToHost32(v); + return true; + } +} + +bool ByteBuffer::ReadString(std::string& val, size_t len) { + if (len > Length()) { + return false; + } else { + val.append(bytes_ + start_, len); + start_ += len; + return true; + } +} + +bool ByteBuffer::ReadBytes(char* val, size_t len) { + if (len > Length()) { + return false; + } else { + memcpy(val, bytes_ + start_, len); + start_ += len; + return true; + } +} + +void ByteBuffer::WriteUInt8(uint8 val) { + WriteBytes(reinterpret_cast(&val), 1); +} + +void ByteBuffer::WriteUInt16(uint16 val) { + uint16 v = HostToNetwork16(val); + WriteBytes(reinterpret_cast(&v), 2); +} + +void ByteBuffer::WriteUInt32(uint32 val) { + uint32 v = HostToNetwork32(val); + WriteBytes(reinterpret_cast(&v), 4); +} + +void ByteBuffer::WriteString(const std::string& val) { + WriteBytes(val.c_str(), val.size()); +} + +void ByteBuffer::WriteBytes(const char* val, size_t len) { + if (Length() + len > Capacity()) + Resize(Length() + len); + + memcpy(bytes_ + end_, val, len); + end_ += len; +} + +void ByteBuffer::Resize(size_t size) { + if (size > size_) + size = _max(size, 3 * size_ / 2); + + size_t len = _min(end_ - start_, size); + char* new_bytes = new char[size]; + memcpy(new_bytes, bytes_ + start_, len); + delete [] bytes_; + + start_ = 0; + end_ = len; + size_ = size; + bytes_ = new_bytes; +} + +void ByteBuffer::Shift(size_t size) { + if (size > Length()) + return; + + end_ = Length() - size; + memmove(bytes_, bytes_ + start_ + size, end_); + start_ = 0; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/bytebuffer.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/bytebuffer.h new file mode 100644 index 00000000..b0a52344 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/bytebuffer.h @@ -0,0 +1,71 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __BYTEBUFFER_H__ +#define __BYTEBUFFER_H__ + +#include "talk/base/basictypes.h" +#include + +namespace cricket { + +class ByteBuffer { +public: + ByteBuffer(); + ByteBuffer(const char* bytes, size_t len); + ByteBuffer(const char* bytes); // uses strlen + ~ByteBuffer(); + + const char* Data() const { return bytes_ + start_; } + size_t Length() { return end_ - start_; } + size_t Capacity() { return size_ - start_; } + + bool ReadUInt8(uint8& val); + bool ReadUInt16(uint16& val); + bool ReadUInt32(uint32& val); + bool ReadString(std::string& val, size_t len); // append to val + bool ReadBytes(char* val, size_t len); + + void WriteUInt8(uint8 val); + void WriteUInt16(uint16 val); + void WriteUInt32(uint32 val); + void WriteString(const std::string& val); + void WriteBytes(const char* val, size_t len); + + void Resize(size_t size); + void Shift(size_t size); + +private: + char* bytes_; + size_t size_; + size_t start_; + size_t end_; +}; + +} // namespace cricket + +#endif // __BYTEBUFFER_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/byteorder.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/byteorder.h new file mode 100644 index 00000000..4b70f47e --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/byteorder.h @@ -0,0 +1,63 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __BYTEORDER_H__ +#define __BYTEORDER_H__ + +#include "talk/base/basictypes.h" + +#ifdef POSIX +extern "C" { +#include +} +#endif + +#ifdef WIN32 +#include +#endif + +namespace cricket { + +inline uint16 HostToNetwork16(uint16 n) { + return htons(n); +} + +inline uint32 HostToNetwork32(uint32 n) { + return htonl(n); +} + +inline uint16 NetworkToHost16(uint16 n) { + return ntohs(n); +} + +inline uint32 NetworkToHost32(uint32 n) { + return ntohl(n); +} + +} // namespace cricket + +#endif // __BYTEORDER_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/common.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/common.h new file mode 100644 index 00000000..b21be2f1 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/common.h @@ -0,0 +1,231 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _common_h_ +#define _common_h_ + +#if defined(_MSC_VER) +// warning C4355: 'this' : used in base member initializer list +#pragma warning(disable:4355) +#endif + +#if defined(ENABLE_DEBUG_MALLOC) && !defined(ENABLE_DEBUG) +#define ENABLE_DEBUG 1 +#endif + +////////////////////////////////////////////////////////////////////// +// General Utilities +////////////////////////////////////////////////////////////////////// + +#ifndef UNUSED +#define UNUSED(x) Unused(static_cast(&x)) +#define UNUSED2(x,y) Unused(static_cast(&x)); Unused(static_cast(&y)) +#define UNUSED3(x,y,z) Unused(static_cast(&x)); Unused(static_cast(&y)); Unused(static_cast(&z)) +#define UNUSED4(x,y,z,a) Unused(static_cast(&x)); Unused(static_cast(&y)); Unused(static_cast(&z)); Unused(static_cast(&a)) +#define UNUSED5(x,y,z,a,b) Unused(static_cast(&x)); Unused(static_cast(&y)); Unused(static_cast(&z)); Unused(static_cast(&a)); Unused(static_cast(&b)) +inline void Unused(const void *) { } +#endif // UNUSED + +#define ARRAY_SIZE(x) (static_cast((sizeof(x)/sizeof(x[0])))) + +///////////////////////////////////////////////////////////////////////////// +// std:min/std:max on msvc +///////////////////////////////////////////////////////////////////////////// + +#if defined(_MSC_VER) && _MSC_VER <= 1200 // 1200 == VC++ 6.0 + +#undef min +#undef max + +namespace std { + + + /** + * @brief This does what you think it does. + * @param a A thing of arbitrary type. + * @param b Another thing of arbitrary type. + * @return The lesser of the parameters. + * + * This is the simple classic generic implementation. It will work on + * temporary expressions, since they are only evaluated once, unlike a + * preprocessor macro. + */ + template + inline const _Tp& + min(const _Tp& __a, const _Tp& __b) + { + //return __b < __a ? __b : __a; + if (__b < __a) return __b; return __a; + } + + /** + * @brief This does what you think it does. + * @param a A thing of arbitrary type. + * @param b Another thing of arbitrary type. + * @return The greater of the parameters. + * + * This is the simple classic generic implementation. It will work on + * temporary expressions, since they are only evaluated once, unlike a + * preprocessor macro. + */ + template + inline const _Tp& + max(const _Tp& __a, const _Tp& __b) + { + //return __a < __b ? __b : __a; + if (__a < __b) return __b; return __a; + } + + /** + * @brief This does what you think it does. + * @param a A thing of arbitrary type. + * @param b Another thing of arbitrary type. + * @param comp A @link s20_3_3_comparisons comparison functor@endlink. + * @return The lesser of the parameters. + * + * This will work on temporary expressions, since they are only evaluated + * once, unlike a preprocessor macro. + */ + template + inline const _Tp& + min(const _Tp& __a, const _Tp& __b, _Compare __comp) + { + //return __comp(__b, __a) ? __b : __a; + if (__comp(__b, __a)) return __b; return __a; + } + + /** + * @brief This does what you think it does. + * @param a A thing of arbitrary type. + * @param b Another thing of arbitrary type. + * @param comp A @link s20_3_3_comparisons comparison functor@endlink. + * @return The greater of the parameters. + * + * This will work on temporary expressions, since they are only evaluated + * once, unlike a preprocessor macro. + */ + template + inline const _Tp& + max(const _Tp& __a, const _Tp& __b, _Compare __comp) + { + //return __comp(__a, __b) ? __b : __a; + if (__comp(__a, __b)) return __b; return __a; + } + +} + +#endif // _MSC_VER <= 1200 + + +///////////////////////////////////////////////////////////////////////////// +// Assertions +///////////////////////////////////////////////////////////////////////////// + +#ifdef ENABLE_DEBUG + +namespace buzz { + +// Break causes the debugger to stop executing, or the program to abort +void Break(); + +// LogAssert writes information about an assertion to the log +void LogAssert(const char * function, const char * file, int line, const char * expression); + +inline void Assert(bool result, const char * function, const char * file, int line, const char * expression) { + if (!result) { + LogAssert(function, file, line, expression); + Break(); + } +} + +}; // namespace buzz + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#define __FUNCTION__ "" +#endif + +#define ASSERT(x) buzz::Assert((x),__FUNCTION__,__FILE__,__LINE__,#x) +#define VERIFY(x) buzz::Assert((x),__FUNCTION__,__FILE__,__LINE__,#x) + +#else // !ENABLE_DEBUG + +#define ASSERT(x) (void)0 +#define VERIFY(x) (void)(x) + +#endif // !ENABLE_DEBUG + +#define COMPILE_TIME_ASSERT(expr) char CTA_UNIQUE_NAME[expr] +#define CTA_UNIQUE_NAME MAKE_NAME(__LINE__) +#define CTA_MAKE_NAME(line) MAKE_NAME2(line) +#define CTA_MAKE_NAME2(line) constraint_ ## line + +////////////////////////////////////////////////////////////////////// +// Memory leak tracking +////////////////////////////////////////////////////////////////////// + +#include + +#ifdef ENABLE_DEBUG_MALLOC + +namespace buzz { + +void * DebugAllocate(size_t size, const char * fname = 0, int line = 0); +void DebugDeallocate(void * ptr, const char * fname = 0, int line = 0); +bool LeakCheck(); +bool LeakCheckU(); +void LeakMarkBaseline(); +void LeakClearBaseline(); + +}; // namespace buzz + +inline void * operator new(size_t size, const char * fname, int line) { return buzz::DebugAllocate(size, fname, line); } +inline void operator delete(void * ptr, const char * fname, int line) { buzz::DebugDeallocate(ptr, fname, line); } + +#if !(defined(TRACK_ARRAY_ALLOC_PROBLEM) && \ + defined(_MSC_VER) && _MSC_VER <= 1200) // 1200 == VC++ 6.0 + +inline void * operator new[](size_t size, const char * fname, int line) { return buzz::DebugAllocate(size, fname, line); } +inline void operator delete[](void * ptr, const char * fname, int line) { buzz::DebugDeallocate(ptr, fname, line); } + +#endif // TRACK_ARRAY_ALLOC_PROBLEM + + +// If you put "#define new TRACK_NEW" in your .cc file after all includes, it should track the calling function name + +#define TRACK_NEW new(__FILE__,__LINE__) +#define TRACK_DEL delete(__FILE__,__LINE__) + +#else // !ENABLE_DEBUG_MALLOC + +#define TRACK_NEW new +#define TRACK_DEL delete + +#endif // !ENABLE_DEBUG_MALLOC + +////////////////////////////////////////////////////////////////////// + +#endif // _common_h_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/criticalsection.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/criticalsection.h new file mode 100644 index 00000000..b75ad5c7 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/criticalsection.h @@ -0,0 +1,120 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _criticalsection_h_ +#define _criticalsection_h_ + +#ifdef WIN32 +#include "talk/base/win32.h" +#endif + +#ifdef POSIX +#include +#endif + +#ifdef _DEBUG +#define CS_TRACK_OWNER 1 +#endif // _DEBUG + +#if CS_TRACK_OWNER +#define TRACK_OWNER(x) x +#else // !CS_TRACK_OWNER +#define TRACK_OWNER(x) +#endif // !CS_TRACK_OWNER + +namespace cricket { + +#ifdef WIN32 +class CriticalSection { +public: + CriticalSection() { + InitializeCriticalSection(&crit_); + // Windows docs say 0 is not a valid thread id + TRACK_OWNER(thread_ = 0); + } + ~CriticalSection() { + DeleteCriticalSection(&crit_); + } + void Enter() { + EnterCriticalSection(&crit_); + TRACK_OWNER(thread_ = GetCurrentThreadId()); + } + void Leave() { + TRACK_OWNER(thread_ = 0); + LeaveCriticalSection(&crit_); + } + +#if CS_TRACK_OWNER + bool CurrentThreadIsOwner() const { return thread_ == GetCurrentThreadId(); } +#endif // CS_TRACK_OWNER + +private: + CRITICAL_SECTION crit_; + TRACK_OWNER(DWORD thread_); // The section's owning thread id +}; +#endif // WIN32 + +#ifdef POSIX +class CriticalSection { +public: + CriticalSection() { + pthread_mutexattr_t mutex_attribute; + pthread_mutexattr_settype(&mutex_attribute, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mutex_, &mutex_attribute); + } + ~CriticalSection() { + pthread_mutex_destroy(&mutex_); + } + void Enter() { + pthread_mutex_lock(&mutex_); + } + void Leave() { + pthread_mutex_unlock(&mutex_); + } +private: + pthread_mutex_t mutex_; +}; +#endif // POSIX + +// CritScope, for serializing exection through a scope + +class CritScope { +public: + CritScope(CriticalSection *pcrit) { + pcrit_ = pcrit; + pcrit_->Enter(); + } + ~CritScope() { + pcrit_->Leave(); + } +private: + CriticalSection *pcrit_; +}; + +} // namespace cricket + +#endif // _criticalsection_h_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/host.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/host.cc new file mode 100644 index 00000000..7b7490d9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/host.cc @@ -0,0 +1,99 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/base/host.h" +#include "talk/base/logging.h" +#include "talk/base/network.h" +#include "talk/base/socket.h" +#include +#include +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::strerror; + using ::exit; +} +#endif + +#ifdef POSIX +extern "C" { +#include +} +#endif // POSIX + +namespace { + +void FatalError(const std::string& name, int err) { + PLOG(LERROR, err) << name; + std::exit(1); +} + +} + +namespace cricket { + +#ifdef POSIX +std::string GetHostName() { + struct utsname nm; + if (uname(&nm) < 0) + FatalError("uname", errno); + return std::string(nm.nodename); +} +#endif + +#ifdef WIN32 +std::string GetHostName() { + // TODO: fix this + return "cricket"; +} +#endif + +// Records information about the local host. +Host* gLocalHost = 0; + +const Host& LocalHost() { + if (!gLocalHost) { + std::vector* networks = new std::vector; + NetworkManager::CreateNetworks(*networks); +#ifdef WIN32 + // This is sort of problematic... one part of the code (the unittests) wants + // 127.0.0.1 to be present and another part (port allocators) don't. Right + // now, they use different APIs, so we can have different behavior. But + // there is something wrong with this. + networks->push_back(new Network("localhost", + SocketAddress::StringToIP("127.0.0.1"))); +#endif + gLocalHost = new Host(GetHostName(), networks); + assert(gLocalHost->networks().size() > 0); + } + + return *gLocalHost; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/host.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/host.h new file mode 100644 index 00000000..16f31a78 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/host.h @@ -0,0 +1,59 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __HOST_H__ +#define __HOST_H__ + +#include "talk/base/network.h" +#include +#include + +namespace cricket { + +// Provides information about a host in the network. +class Host { +public: + Host(const std::string& name, std::vector* networks) + : name_(name), networks_(networks) { } + + const std::string& name() const { return name_; } + const std::vector& networks() const { return *networks_; } + +private: + std::string name_; + std::vector* networks_; +}; + +// Returns a reference to the description of the local host. +const Host& LocalHost(); + +// Returns the name of the local host. +std::string GetHostName(); + +} // namespace cricket + +#endif // __HOST_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/jtime.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/jtime.cc new file mode 100644 index 00000000..5befe9fd --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/jtime.cc @@ -0,0 +1,77 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/base/jtime.h" +#include +#include +#include + +namespace cricket { + +#ifdef POSIX +#include +uint32 Time() { + struct timeval tv; + gettimeofday(&tv, 0); + return tv.tv_sec * 1000 + tv.tv_usec / 1000; +} +#endif + +#ifdef WIN32 +#include +uint32 Time() { + return GetTickCount(); +} +#endif + +bool TimeIsBetween(uint32 later, uint32 middle, uint32 earlier) { + if (earlier <= later) { + return ((earlier <= middle) && (middle <= later)); + } else { + return !((later < middle) && (middle < earlier)); + } +} + +int32 TimeDiff(uint32 later, uint32 earlier) { + uint32 LAST = 0xFFFFFFFF; + uint32 HALF = 0x80000000; + if (TimeIsBetween(earlier + HALF, later, earlier)) { + if (earlier <= later) { + return static_cast(later - earlier); + } else { + return static_cast(later + (LAST - earlier) + 1); + } + } else { + if (later <= earlier) { + return -static_cast(earlier - later); + } else { + return -static_cast(earlier + (LAST - later) + 1); + } + } +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/jtime.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/jtime.h new file mode 100644 index 00000000..d7dff0fb --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/jtime.h @@ -0,0 +1,47 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __JTIME_H__ +#define __JTIME_H__ + +#include "talk/base/basictypes.h" + +namespace cricket { + +// Returns the current time in milliseconds. +uint32 Time(); + +// TODO: Delete this old version. +#define GetMillisecondCount Time + +// Comparisons between time values, which can wrap around. +bool TimeIsBetween(uint32 later, uint32 middle, uint32 earlier); +int32 TimeDiff(uint32 later, uint32 earlier); + +} // namespace cricket + +#endif // __TIME_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/linked_ptr.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/linked_ptr.h new file mode 100644 index 00000000..94b62ad3 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/linked_ptr.h @@ -0,0 +1,138 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +/* + * linked_ptr - simple reference linked pointer + * (like reference counting, just using a linked list of the references + * instead of their count.) + * + * The implementation stores three pointers for every linked_ptr, but + * does not allocate anything on the free store. + */ + +#ifndef _LINKED_PTR_H_ +#define _LINKED_PTR_H_ + +/* For ANSI-challenged compilers, you may want to #define + * NO_MEMBER_TEMPLATES, explicit or mutable */ +#define NO_MEMBER_TEMPLATES + +template class linked_ptr +{ +public: + +#ifndef NO_MEMBER_TEMPLATES +# define TEMPLATE_FUNCTION template + TEMPLATE_FUNCTION friend class linked_ptr; +#else +# define TEMPLATE_FUNCTION + typedef X Y; +#endif + + typedef X element_type; + + explicit linked_ptr(X* p = 0) throw() + : itsPtr(p) {itsPrev = itsNext = this;} + ~linked_ptr() + {release();} + linked_ptr(const linked_ptr& r) throw() + {acquire(r);} + linked_ptr& operator=(const linked_ptr& r) + { + if (this != &r) { + release(); + acquire(r); + } + return *this; + } + +#ifndef NO_MEMBER_TEMPLATES + template friend class linked_ptr; + template linked_ptr(const linked_ptr& r) throw() + {acquire(r);} + template linked_ptr& operator=(const linked_ptr& r) + { + if (this != &r) { + release(); + acquire(r); + } + return *this; + } +#endif // NO_MEMBER_TEMPLATES + + X& operator*() const throw() {return *itsPtr;} + X* operator->() const throw() {return itsPtr;} + X* get() const throw() {return itsPtr;} + bool unique() const throw() {return itsPrev ? itsPrev==this : true;} + +private: + X* itsPtr; + mutable const linked_ptr* itsPrev; + mutable const linked_ptr* itsNext; + + void acquire(const linked_ptr& r) throw() + { // insert this to the list + itsPtr = r.itsPtr; + itsNext = r.itsNext; + itsNext->itsPrev = this; + itsPrev = &r; +#ifndef mutable + r.itsNext = this; +#else // for ANSI-challenged compilers + (const_cast*>(&r))->itsNext = this; +#endif + } + +#ifndef NO_MEMBER_TEMPLATES + template void acquire(const linked_ptr& r) throw() + { // insert this to the list + itsPtr = r.itsPtr; + itsNext = r.itsNext; + itsNext->itsPrev = this; + itsPrev = &r; +#ifndef mutable + r.itsNext = this; +#else // for ANSI-challenged compilers + (const_cast*>(&r))->itsNext = this; +#endif + } +#endif // NO_MEMBER_TEMPLATES + + void release() + { // erase this from the list, delete if unique + if (unique()) delete itsPtr; + else { + itsPrev->itsNext = itsNext; + itsNext->itsPrev = itsPrev; + itsPrev = itsNext = 0; + } + itsPtr = 0; + } +}; + +#endif // LINKED_PTR_H + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/logging.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/logging.h new file mode 100644 index 00000000..500386ed --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/logging.h @@ -0,0 +1,222 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +// LOG(...) an ostream target that can be used to send formatted +// output to a variety of logging targets, such as debugger console, stderr, +// file, or any StreamInterface. +// The severity level passed as the first argument to the the LOGging +// functions is used as a filter, to limit the verbosity of the logging. +// Static members of LogMessage documented below are used to control the +// verbosity and target of the output. +// There are several variations on the LOG macro which facilitate logging +// of common error conditions, detailed below. + +#ifndef TALK_BASE_LOGGING_H__ +#define TALK_BASE_LOGGING_H__ + +#include +#include "talk/base/basictypes.h" +class StreamInterface; + +/////////////////////////////////////////////////////////////////////////////// +// ConstantLabel can be used to easily generate string names from constant +// values. This can be useful for logging descriptive names of error messages. +// Usage: +// const ConstantLabel LIBRARY_ERRORS[] = { +// KLABEL(SOME_ERROR), +// KLABEL(SOME_OTHER_ERROR), +// ... +// LASTLABEL +// } +// +// int err = LibraryFunc(); +// LOG(LS_ERROR) << "LibraryFunc returned: " +// << ErrorName(err, LIBRARY_ERRORS); + +struct ConstantLabel { int value; const char * label; }; +#define KLABEL(x) { x, #x } +#define TLABEL(x,y) { x, y } +#define LASTLABEL { 0, 0 } + +const char * FindLabel(int value, const ConstantLabel entries[]); +std::string ErrorName(int err, const ConstantLabel * err_table); + +////////////////////////////////////////////////////////////////////// + +// Note that the non-standard LoggingSeverity aliases exist because they are +// still in broad use. The meanings of the levels are: +// LS_SENSITIVE: Information which should only be logged with the consent +// of the user, due to privacy concerns. +// LS_VERBOSE: This level is for data which we do not want to appear in the +// normal debug log, but should appear in diagnostic logs. +// LS_INFO: Chatty level used in debugging for all sorts of things, the default +// in debug builds. +// LS_WARNING: Something that may warrant investigation. +// LS_ERROR: Something that should not have occurred. +enum LoggingSeverity { LS_SENSITIVE, LS_VERBOSE, LS_INFO, LS_WARNING, LS_ERROR, + INFO = LS_INFO, + WARNING = LS_WARNING, + LERROR = LS_ERROR }; + +// LogErrorContext assists in interpreting the meaning of an error value. +// ERRCTX_ERRNO: the value was read from global 'errno' +// ERRCTX_HRESULT: the value is a Windows HRESULT +enum LogErrorContext { ERRCTX_NONE, ERRCTX_ERRNO, ERRCTX_HRESULT }; + +// If LOGGING is not explicitly defined, default to enabled in debug mode +#if !defined(LOGGING) +#if defined(_DEBUG) && !defined(NDEBUG) +#define LOGGING 1 +#else +#define LOGGING 0 +#endif +#endif // !defined(LOGGING) + +#if LOGGING + +#define LOG(sev) \ + if (LogMessage::Loggable(sev)) \ + LogMessage(__FILE__, __LINE__, sev).stream() + +// PLOG and LOG_ERR attempt to provide a string description of an errno derived +// error. LOG_ERR reads errno directly, so care must be taken to call it before +// errno is reset. +#define PLOG(sev, err) \ + if (LogMessage::Loggable(sev)) \ + LogMessage(__FILE__, __LINE__, sev, ERRCTX_ERRNO, err).stream() +#define LOG_ERR(sev) \ + if (LogMessage::Loggable(sev)) \ + LogMessage(__FILE__, __LINE__, sev, ERRCTX_ERRNO, errno).stream() + +// LOG_GLE(M) attempt to provide a string description of the HRESULT returned +// by GetLastError. The second variant allows searching of a dll's string +// table for the error description. +#ifdef WIN32 +#define LOG_GLE(sev) \ + if (LogMessage::Loggable(sev)) \ + LogMessage(__FILE__, __LINE__, sev, ERRCTX_HRESULT, GetLastError()).stream() +#define LOG_GLEM(sev, mod) \ + if (LogMessage::Loggable(sev)) \ + LogMessage(__FILE__, __LINE__, sev, ERRCTX_HRESULT, GetLastError(), mod) \ + .stream() +#endif // WIN32 + +// TODO: Add an "assert" wrapper that logs in the same manner. + +#else // !LOGGING + +// Hopefully, the compiler will optimize away some of this code. +// Note: syntax of "1 ? (void)0 : LogMessage" was causing errors in g++, +// converted to "while (false)" +#define LOG(sev) \ + while (false) LogMessage(NULL, 0, sev).stream() +#define PLOG(sev, err) \ + while (false) LogMessage(NULL, 0, sev, ERRCTX_ERRNO, 0).stream() +#define LOG_ERR(sev) \ + while (false) LogMessage(NULL, 0, sev, ERRCTX_ERRNO, 0).stream() +#ifdef WIN32 +#define LOG_GLE(sev) \ + while (false) LogMessage(NULL, 0, sev, ERRCTX_HRESULT, 0).stream() +#define LOG_GLEM(sev, mod) \ + while (false) LogMessage(NULL, 0, sev, ERRCTX_HRESULT, 0).stream() +#endif // WIN32 + +#endif // !LOGGING + +class LogMessage { + public: + LogMessage(const char* file, int line, LoggingSeverity sev, + LogErrorContext err_ctx = ERRCTX_NONE, int err = 0, + const char* module = NULL); + ~LogMessage(); + + static inline bool Loggable(LoggingSeverity sev) { return (sev >= min_sev_); } + std::ostream& stream() { return print_stream_; } + + enum { NO_LOGGING = LS_ERROR + 1 }; + + // These are attributes which apply to all logging channels + // LogContext: Display the file and line number of the message + static void LogContext(int min_sev); + // LogThreads: Display the thread identifier of the current thread + static void LogThreads(bool on = true); + // LogTimestamps: Display the elapsed time of the program + static void LogTimestamps(bool on = true); + + // Timestamps begin with program execution, but can be reset with this + // function for measuring the duration of an activity, or to synchronize + // timestamps between multiple instances. + static void ResetTimestamps(); + + // These are the available logging channels + // Debug: Debug console on Windows, otherwise stderr + static void LogToDebug(int min_sev); + static int GetLogToDebug() { return dbg_sev_; } + // Stream: Any non-blocking stream interface. LogMessage takes ownership of + // the stream. + static void LogToStream(StreamInterface* stream, int min_sev); + static int GetLogToStream() { return stream_sev_; } + + // Testing against MinLogSeverity allows code to avoid potentially expensive + // logging operations by pre-checking the logging level. + static int GetMinLogSeverity() { return min_sev_; } + + private: + // These assist in formatting some parts of the debug output. + static const char* Describe(LoggingSeverity sev); + static const char* DescribeFile(const char* file); + + // The ostream that buffers the formatted message before output + std::ostringstream print_stream_; + + // The severity level of this message + LoggingSeverity severity_; + + // String data generated in the constructor, that should be appended to + // the message before output. + std::string extra_; + + // dbg_sev_ and stream_sev_ are the thresholds for those output targets + // min_sev_ is the minimum (most verbose) of those levels, and is used + // as a short-circuit in the logging macros to identify messages that won't + // be logged. + // ctx_sev_ is the minimum level at which file context is displayed + static int min_sev_, dbg_sev_, stream_sev_, ctx_sev_; + + // The output stream, if any + static StreamInterface * stream_; + + // Flags for formatting options + static bool thread_, timestamp_; + + // The timestamp at which logging started. + static uint32 start_; + + DISALLOW_EVIL_CONSTRUCTORS(LogMessage); +}; + +#endif // TALK_BASE_LOGGING_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/md5.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/md5.h new file mode 100644 index 00000000..c2e22cc5 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/md5.h @@ -0,0 +1,45 @@ +/* + * This is the header file for the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + * + */ + +#ifndef MD5_H +#define MD5_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef long unsigned int uint32; +typedef struct MD5Context MD5_CTX; + +#define md5byte unsigned char + +struct MD5Context { + uint32 buf[4]; + uint32 bits[2]; + uint32 in[16]; +}; + +void MD5Init(struct MD5Context *context); +void MD5Update(struct MD5Context *context, md5byte const *buf, unsigned len); +void MD5Final(unsigned char digest[16], struct MD5Context *context); +void MD5Transform(uint32 buf[4], uint32 const in[16]); + +#ifdef __cplusplus +}; +#endif + +#endif /* !MD5_H */ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/md5c.c b/kopete/protocols/jabber/jingle/libjingle/talk/base/md5c.c new file mode 100644 index 00000000..eb2c034d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/md5c.c @@ -0,0 +1,256 @@ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ +#include /* for memcpy() */ +#include "md5.h" + +#ifndef HIGHFIRST +#define byteReverse(buf, len) /* Nothing */ +#else +void byteReverse(unsigned char *buf, unsigned longs); + +#ifndef ASM_MD5 +/* + * Note: this code is harmless on little-endian machines. + */ +void byteReverse(unsigned char *buf, unsigned longs) +{ + uint32 t; + do { + t = (uint32)((unsigned)buf[3]<<8 | buf[2]) << 16 | + ((unsigned)buf[1]<<8 | buf[0]); + *(uint32 *)buf = t; + buf += 4; + } while (--longs); +} +#endif +#endif + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void +MD5Init(struct MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void +MD5Update(struct MD5Context *ctx, unsigned char const *buf, unsigned len) +{ + uint32 t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32)len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if ( t ) { + unsigned char *p = (unsigned char *)ctx->in + t; + + t = 64-t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + buf += t; + len -= t; + } + + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void +MD5Final(unsigned char digest[16], struct MD5Context *ctx) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = (unsigned char*)(ctx->in) + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count-8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((uint32 *)ctx->in)[ 14 ] = ctx->bits[0]; + ((uint32 *)ctx->in)[ 15 ] = ctx->bits[1]; + + MD5Transform(ctx->buf, (uint32 *)ctx->in); + byteReverse((unsigned char *)ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ +} + +#ifndef ASM_MD5 + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void +MD5Transform(uint32 buf[4], uint32 const in[16]) +{ + register uint32 a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/messagequeue.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/messagequeue.cc new file mode 100644 index 00000000..f10489f7 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/messagequeue.cc @@ -0,0 +1,321 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/base/messagequeue.h" +#include "talk/base/physicalsocketserver.h" + +#ifdef POSIX +extern "C" { +#include +} +#endif + +namespace cricket { + +//------------------------------------------------------------------ +// MessageQueueManager + +MessageQueueManager* MessageQueueManager::instance_; + +MessageQueueManager* MessageQueueManager::Instance() { + // Note: This is not thread safe, but it is first called before threads are + // spawned. + if (!instance_) + instance_ = new MessageQueueManager; + return instance_; +} + +MessageQueueManager::MessageQueueManager() { +} + +MessageQueueManager::~MessageQueueManager() { +} + +void MessageQueueManager::Add(MessageQueue *message_queue) { + CritScope cs(&crit_); + message_queues_.push_back(message_queue); +} + +void MessageQueueManager::Remove(MessageQueue *message_queue) { + CritScope cs(&crit_); + std::vector::iterator iter; + iter = std::find(message_queues_.begin(), message_queues_.end(), message_queue); + if (iter != message_queues_.end()) + message_queues_.erase(iter); +} + +void MessageQueueManager::Clear(MessageHandler *handler) { + CritScope cs(&crit_); + std::vector::iterator iter; + for (iter = message_queues_.begin(); iter != message_queues_.end(); iter++) + (*iter)->Clear(handler); +} + +//------------------------------------------------------------------ +// MessageQueue + +MessageQueue::MessageQueue(SocketServer* ss) + : ss_(ss), new_ss(false), fStop_(false), fPeekKeep_(false) { + if (!ss_) { + new_ss = true; + ss_ = new PhysicalSocketServer(); + } + MessageQueueManager::Instance()->Add(this); +} + +MessageQueue::~MessageQueue() { + Clear(NULL); + if (new_ss) + delete ss_; + MessageQueueManager::Instance()->Remove(this); +} + +void MessageQueue::set_socketserver(SocketServer* ss) { + if (new_ss) + delete ss_; + new_ss = false; + ss_ = ss; +} + +void MessageQueue::Stop() { + fStop_ = true; + ss_->WakeUp(); +} + +bool MessageQueue::IsStopping() { + return fStop_; +} + +void MessageQueue::Restart() { + fStop_ = false; +} + +bool MessageQueue::Peek(Message *pmsg, int cmsWait) { + if (fStop_) + return false; + if (fPeekKeep_) { + *pmsg = msgPeek_; + return true; + } + if (!Get(pmsg, cmsWait)) + return false; + msgPeek_ = *pmsg; + fPeekKeep_ = true; + return true; +} + +bool MessageQueue::Get(Message *pmsg, int cmsWait) { + // Force stopping + + if (fStop_) + return false; + + // Return and clear peek if present + // Always return the peek if it exists so there is Peek/Get symmetry + + if (fPeekKeep_) { + *pmsg = msgPeek_; + fPeekKeep_ = false; + return true; + } + + // Get w/wait + timer scan / dispatch + socket / event multiplexer dispatch + + int cmsTotal = cmsWait; + int cmsElapsed = 0; + uint32 msStart = GetMillisecondCount(); + uint32 msCurrent = msStart; + while (!fStop_) { + // Check for sent messages + + ReceiveSends(); + + // Check queues + + int cmsDelayNext = -1; + { + CritScope cs(&crit_); + + // Check for delayed messages that have been triggered + // Calc the next trigger too + + while (!dmsgq_.empty()) { + if (msCurrent < dmsgq_.top().msTrigger_) { + cmsDelayNext = dmsgq_.top().msTrigger_ - msCurrent; + break; + } + msgq_.push(dmsgq_.top().msg_); + dmsgq_.pop(); + } + + // Check for posted events + + if (!msgq_.empty()) { + *pmsg = msgq_.front(); + msgq_.pop(); + return true; + } + } + + // Which is shorter, the delay wait or the asked wait? + + int cmsNext; + if (cmsWait == -1) { + cmsNext = cmsDelayNext; + } else { + cmsNext = cmsTotal - cmsElapsed; + if (cmsNext < 0) + cmsNext = 0; + if (cmsDelayNext != -1 && cmsDelayNext < cmsNext) + cmsNext = cmsDelayNext; + } + + // Wait and multiplex in the meantime + ss_->Wait(cmsNext, true); + + // If the specified timeout expired, return + + msCurrent = GetMillisecondCount(); + cmsElapsed = msCurrent - msStart; + if (cmsWait != -1) { + if (cmsElapsed >= cmsWait) + return false; + } + } + return false; +} + +void MessageQueue::ReceiveSends() { +} + +void MessageQueue::Post(MessageHandler *phandler, uint32 id, + MessageData *pdata) { + // Keep thread safe + // Add the message to the end of the queue + // Signal for the multiplexer to return + + CritScope cs(&crit_); + Message msg; + msg.phandler = phandler; + msg.message_id = id; + msg.pdata = pdata; + msgq_.push(msg); + ss_->WakeUp(); +} + +void MessageQueue::PostDelayed(int cmsDelay, MessageHandler *phandler, + uint32 id, MessageData *pdata) { + // Keep thread safe + // Add to the priority queue. Gets sorted soonest first. + // Signal for the multiplexer to return. + + CritScope cs(&crit_); + Message msg; + msg.phandler = phandler; + msg.message_id = id; + msg.pdata = pdata; + dmsgq_.push(DelayedMessage(cmsDelay, &msg)); + ss_->WakeUp(); +} + +int MessageQueue::GetDelay() { + CritScope cs(&crit_); + + if (!msgq_.empty()) + return 0; + + if (!dmsgq_.empty()) { + int delay = dmsgq_.top().msTrigger_ - GetMillisecondCount(); + if (delay < 0) + delay = 0; + return delay; + } + + return -1; +} + +void MessageQueue::Clear(MessageHandler *phandler, uint32 id) { + CritScope cs(&crit_); + + // Remove messages with phandler + + if (fPeekKeep_) { + if (phandler == NULL || msgPeek_.phandler == phandler) { + if (id == (uint32)-1 || msgPeek_.message_id == id) { + delete msgPeek_.pdata; + fPeekKeep_ = false; + } + } + } + + // Remove from ordered message queue + + size_t c = msgq_.size(); + while (c-- != 0) { + Message msg = msgq_.front(); + msgq_.pop(); + if (phandler != NULL && msg.phandler != phandler) { + msgq_.push(msg); + } else { + if (id == (uint32)-1 || msg.message_id == id) { + delete msg.pdata; + } else { + msgq_.push(msg); + } + } + } + + // Remove from priority queue. Not directly iterable, so use this approach + + std::queue dmsgs; + while (!dmsgq_.empty()) { + DelayedMessage dmsg = dmsgq_.top(); + dmsgq_.pop(); + if (phandler != NULL && dmsg.msg_.phandler != phandler) { + dmsgs.push(dmsg); + } else { + if (id == (uint32)-1 || dmsg.msg_.message_id == id) { + delete dmsg.msg_.pdata; + } else { + dmsgs.push(dmsg); + } + } + } + while (!dmsgs.empty()) { + dmsgq_.push(dmsgs.front()); + dmsgs.pop(); + } +} + +void MessageQueue::Dispatch(Message *pmsg) { + pmsg->phandler->OnMessage(pmsg); +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/messagequeue.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/messagequeue.h new file mode 100644 index 00000000..2a9cbed6 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/messagequeue.h @@ -0,0 +1,164 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __MESSAGEQUEUE_H__ +#define __MESSAGEQUEUE_H__ + +#include "talk/base/basictypes.h" +#include "talk/base/criticalsection.h" +#include "talk/base/socketserver.h" +#include "talk/base/jtime.h" +#include +#include +#include + +namespace cricket { + +struct Message; +class MessageQueue; +class MessageHandler; + +// MessageQueueManager does cleanup of of message queues + +class MessageQueueManager { +public: + static MessageQueueManager* Instance(); + + void Add(MessageQueue *message_queue); + void Remove(MessageQueue *message_queue); + void Clear(MessageHandler *handler); + +private: + MessageQueueManager(); + ~MessageQueueManager(); + + static MessageQueueManager* instance_; + std::vector message_queues_; + CriticalSection crit_; +}; + +// Messages get dispatched to a MessageHandler + +class MessageHandler { +public: + virtual ~MessageHandler() { + MessageQueueManager::Instance()->Clear(this); + } + + virtual void OnMessage(Message *pmsg) = 0; +}; + +// Derive from this for specialized data +// App manages lifetime, except when messages are purged + +class MessageData { +public: + MessageData() {} + virtual ~MessageData() {} +}; + +template +class TypedMessageData : public MessageData { +public: + TypedMessageData(arg1_type data) { + data_ = data; + } + arg1_type data() { + return data_; + } +private: + arg1_type data_; +}; + +// No destructor + +struct Message { + Message() { + memset(this, 0, sizeof(*this)); + } + MessageHandler *phandler; + uint32 message_id; + MessageData *pdata; +}; + +// DelayedMessage goes into a priority queue, sorted by trigger time + +class DelayedMessage { +public: + DelayedMessage(int cmsDelay, Message *pmsg) { + cmsDelay_ = cmsDelay; + msTrigger_ = GetMillisecondCount() + cmsDelay; + msg_ = *pmsg; + } + + bool operator< (const DelayedMessage& dmsg) const { + return dmsg.msTrigger_ < msTrigger_; + } + + int cmsDelay_; // for debugging + uint32 msTrigger_; + Message msg_; +}; + +class MessageQueue { +public: + MessageQueue(SocketServer* ss = 0); + virtual ~MessageQueue(); + + SocketServer* socketserver() { return ss_; } + void set_socketserver(SocketServer* ss); + + // Once the queue is stopped, all calls to Get/Peek will return false. + virtual void Stop(); + virtual bool IsStopping(); + virtual void Restart(); + + virtual bool Get(Message *pmsg, int cmsWait = -1); + virtual bool Peek(Message *pmsg, int cmsWait = 0); + virtual void Post(MessageHandler *phandler, uint32 id = 0, + MessageData *pdata = NULL); + virtual void PostDelayed(int cmsDelay, MessageHandler *phandler, + uint32 id = 0, MessageData *pdata = NULL); + virtual void Clear(MessageHandler *phandler, uint32 id = (uint32)-1); + virtual void Dispatch(Message *pmsg); + virtual void ReceiveSends(); + virtual int GetDelay(); + +protected: + SocketServer* ss_; + bool new_ss; + bool fStop_; + bool fPeekKeep_; + Message msgPeek_; + std::queue msgq_; + std::priority_queue dmsgq_; + CriticalSection crit_; +}; + +} // namespace cricket + +#endif // __MESSAGEQUEUE_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/network.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/network.cc new file mode 100644 index 00000000..21b3a08f --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/network.cc @@ -0,0 +1,382 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/base/host.h" +#include "talk/base/logging.h" +#include "talk/base/network.h" +#include "talk/base/socket.h" // this includes something that makes windows happy +#include "talk/base/jtime.h" +#include "talk/base/basicdefs.h" + +#include +#include +#include +#include +#include + +#ifdef POSIX +extern "C" { +#include +#include +#include +#include +#include +} +#endif // POSIX + +#ifdef WIN32 +#include +#endif + +namespace { + +const double kAlpha = 0.5; // weight for data infinitely far in the past +const double kHalfLife = 2000; // half life of exponential decay (in ms) +const double kLog2 = 0.693147180559945309417; +const double kLambda = kLog2 / kHalfLife; + +// assume so-so quality unless data says otherwise +const double kDefaultQuality = cricket::QUALITY_FAIR; + +typedef std::map StrMap; + +void BuildMap(const StrMap& map, std::string& str) { + str.append("{"); + bool first = true; + for (StrMap::const_iterator i = map.begin(); i != map.end(); ++i) { + if (!first) str.append(","); + str.append(i->first); + str.append("="); + str.append(i->second); + first = false; + } + str.append("}"); +} + +void ParseCheck(std::istringstream& ist, char ch) { + if (ist.get() != ch) + LOG(LERROR) << "Expecting '" << ch << "'"; +} + +std::string ParseString(std::istringstream& ist) { + std::string str; + int count = 0; + while (ist) { + char ch = ist.peek(); + if ((count == 0) && ((ch == '=') || (ch == ',') || (ch == '}'))) { + break; + } else if (ch == '{') { + count += 1; + } else if (ch == '}') { + count -= 1; + if (count < 0) + LOG(LERROR) << "mismatched '{' and '}'"; + } + str.append(1, static_cast(ist.get())); + } + return str; +} + +void ParseMap(const std::string& str, StrMap& map) { + if (str.size() == 0) + return; + std::istringstream ist(str); + ParseCheck(ist, '{'); + for (;;) { + std::string key = ParseString(ist); + ParseCheck(ist, '='); + std::string val = ParseString(ist); + map[key] = val; + if (ist.peek() == ',') + ist.get(); + else + break; + } + ParseCheck(ist, '}'); + if (ist.rdbuf()->in_avail() != 0) + LOG(LERROR) << "Unexpected characters at end"; +} + +#if 0 +const std::string TEST_MAP0_IN = ""; +const std::string TEST_MAP0_OUT = "{}"; +const std::string TEST_MAP1 = "{a=12345}"; +const std::string TEST_MAP2 = "{a=12345,b=67890}"; +const std::string TEST_MAP3 = "{a=12345,b=67890,c=13579}"; +const std::string TEST_MAP4 = "{a={d=12345,e=67890}}"; +const std::string TEST_MAP5 = "{a={d=12345,e=67890},b=67890}"; +const std::string TEST_MAP6 = "{a=12345,b={d=12345,e=67890}}"; +const std::string TEST_MAP7 = "{a=12345,b={d=12345,e=67890},c=13579}"; + +class MyTest { +public: + MyTest() { + test(TEST_MAP0_IN, TEST_MAP0_OUT); + test(TEST_MAP1, TEST_MAP1); + test(TEST_MAP2, TEST_MAP2); + test(TEST_MAP3, TEST_MAP3); + test(TEST_MAP4, TEST_MAP4); + test(TEST_MAP5, TEST_MAP5); + test(TEST_MAP6, TEST_MAP6); + test(TEST_MAP7, TEST_MAP7); + } + void test(const std::string& input, const std::string& exp_output) { + StrMap map; + ParseMap(input, map); + std::string output; + BuildMap(map, output); + LOG(INFO) << " ******** " << (output == exp_output); + } +}; + +static MyTest myTest; +#endif + +template +std::string ToString(T val) { + std::ostringstream ost; + ost << val; + return ost.str(); +} + +template +T FromString(std::string str) { + std::istringstream ist(str); + T val; + ist >> val; + return val; +} + +} + +namespace cricket { + +#ifdef POSIX +void NetworkManager::CreateNetworks(std::vector& networks) { + int fd; + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + PLOG(LERROR, errno) << "socket"; + return; + } + + struct ifconf ifc; + ifc.ifc_len = 64 * sizeof(struct ifreq); + ifc.ifc_buf = new char[ifc.ifc_len]; + + if (ioctl(fd, SIOCGIFCONF, &ifc) < 0) { + PLOG(LERROR, errno) << "ioctl"; + return; + } + assert(ifc.ifc_len < static_cast(64 * sizeof(struct ifreq))); + + struct ifreq* ptr = reinterpret_cast(ifc.ifc_buf); + struct ifreq* end = + reinterpret_cast(ifc.ifc_buf + ifc.ifc_len); + + while (ptr < end) { + struct sockaddr_in* inaddr = + reinterpret_cast(&ptr->ifr_ifru.ifru_addr); + if (inaddr->sin_family == AF_INET) { + uint32 ip = ntohl(inaddr->sin_addr.s_addr); + networks.push_back(new Network(std::string(ptr->ifr_name), ip)); + } +#ifdef _SIZEOF_ADDR_IFREQ + ptr = reinterpret_cast( + reinterpret_cast(ptr) + _SIZEOF_ADDR_IFREQ(*ptr)); +#else + ptr++; +#endif + } + + delete [] ifc.ifc_buf; + close(fd); +} +#endif + +#ifdef WIN32 +void NetworkManager::CreateNetworks(std::vector& networks) { + IP_ADAPTER_INFO info_temp; + ULONG len = 0; + + if (GetAdaptersInfo(&info_temp, &len) != ERROR_BUFFER_OVERFLOW) + return; + IP_ADAPTER_INFO *infos = new IP_ADAPTER_INFO[len]; + if (GetAdaptersInfo(infos, &len) != NO_ERROR) + return; + + int count = 0; + for (IP_ADAPTER_INFO *info = infos; info != NULL; info = info->Next) { + if (info->Type == MIB_IF_TYPE_LOOPBACK) + continue; + if (strcmp(info->IpAddressList.IpAddress.String, "0.0.0.0") == 0) + continue; + + // In production, don't transmit the network name because of + // privacy concerns. Transmit a number instead. + + std::string name; +#if defined(PRODUCTION) + std::ostringstream ost; + ost << count; + name = ost.str(); + count++; +#else + name = info->Description; +#endif + + networks.push_back(new Network(name, + SocketAddress::StringToIP(info->IpAddressList.IpAddress.String))); + } + + delete infos; +} +#endif + +void NetworkManager::GetNetworks(std::vector& result) { + std::vector list; + CreateNetworks(list); + + for (uint32 i = 0; i < list.size(); ++i) { + NetworkMap::iterator iter = networks_.find(list[i]->name()); + + Network* network; + if (iter == networks_.end()) { + network = list[i]; + } else { + network = iter->second; + network->set_ip(list[i]->ip()); + delete list[i]; + } + + networks_[network->name()] = network; + result.push_back(network); + } +} + +std::string NetworkManager::GetState() { + StrMap map; + for (NetworkMap::iterator i = networks_.begin(); i != networks_.end(); ++i) + map[i->first] = i->second->GetState(); + + std::string str; + BuildMap(map, str); + return str; +} + +void NetworkManager::SetState(std::string str) { + StrMap map; + ParseMap(str, map); + + for (StrMap::iterator i = map.begin(); i != map.end(); ++i) { + std::string name = i->first; + std::string state = i->second; + + Network* network = new Network(name, 0); + network->SetState(state); + networks_[name] = network; + } +} + +Network::Network(const std::string& name, uint32 ip) + : name_(name), ip_(ip), uniform_numerator_(0), uniform_denominator_(0), + exponential_numerator_(0), exponential_denominator_(0), + quality_(kDefaultQuality) { + + last_data_time_ = Time(); + + // TODO: seed the historical data with one data point based on the link speed + // metric from XP (4.0 if < 50, 3.0 otherwise). +} + +void Network::StartSession(NetworkSession* session) { + assert(std::find(sessions_.begin(), sessions_.end(), session) == sessions_.end()); + sessions_.push_back(session); +} + +void Network::StopSession(NetworkSession* session) { + SessionList::iterator iter = std::find(sessions_.begin(), sessions_.end(), session); + if (iter != sessions_.end()) + sessions_.erase(iter); +} + +void Network::EstimateQuality() { + uint32 now = Time(); + + // Add new data points for the current time. + for (uint32 i = 0; i < sessions_.size(); ++i) { + if (sessions_[i]->HasQuality()) + AddDataPoint(now, sessions_[i]->GetCurrentQuality()); + } + + // Construct the weighted average using both uniform and exponential weights. + + double exp_shift = exp(-kLambda * (now - last_data_time_)); + double numerator = uniform_numerator_ + exp_shift * exponential_numerator_; + double denominator = uniform_denominator_ + exp_shift * exponential_denominator_; + + if (denominator < DBL_EPSILON) + quality_ = kDefaultQuality; + else + quality_ = numerator / denominator; +} + +void Network::AddDataPoint(uint32 time, double quality) { + uniform_numerator_ += kAlpha * quality; + uniform_denominator_ += kAlpha; + + double exp_shift = exp(-kLambda * (time - last_data_time_)); + exponential_numerator_ = (1 - kAlpha) * quality + exp_shift * exponential_numerator_; + exponential_denominator_ = (1 - kAlpha) + exp_shift * exponential_denominator_; + + last_data_time_ = time; +} + +std::string Network::GetState() { + StrMap map; + map["lt"] = ToString(last_data_time_); + map["un"] = ToString(uniform_numerator_); + map["ud"] = ToString(uniform_denominator_); + map["en"] = ToString(exponential_numerator_); + map["ed"] = ToString(exponential_denominator_); + + std::string str; + BuildMap(map, str); + return str; +} + +void Network::SetState(std::string str) { + StrMap map; + ParseMap(str, map); + + last_data_time_ = FromString(map["lt"]); + uniform_numerator_ = FromString(map["un"]); + uniform_denominator_ = FromString(map["ud"]); + exponential_numerator_ = FromString(map["en"]); + exponential_denominator_ = FromString(map["ed"]); +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/network.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/network.h new file mode 100644 index 00000000..2cc9128a --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/network.h @@ -0,0 +1,136 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __NETWORK_H__ +#define __NETWORK_H__ + +#include "talk/base/basictypes.h" + +#include +#include +#include +#include + +namespace cricket { + +class Network; +class NetworkSession; + +// Keeps track of the available network interfaces over time so that quality +// information can be aggregated and recorded. +class NetworkManager { +public: + + // Updates and returns the current list of networks available on this machine. + // This version will make sure that repeated calls return the same object for + // a given network, so that quality is tracked appropriately. + void GetNetworks(std::vector& networks); + + // Reads and writes the state of the quality database in a string format. + std::string GetState(); + void SetState(std::string str); + + // Creates a network object for each network available on the machine. + static void CreateNetworks(std::vector& networks); + +private: + typedef std::map NetworkMap; + + NetworkMap networks_; +}; + +// Represents a Unix-type network interface, with a name and single address. +// It also includes the ability to track and estimate quality. +class Network { +public: + Network(const std::string& name, uint32 ip); + + // Returns the OS name of this network. This is considered the primary key + // that identifies each network. + const std::string& name() const { return name_; } + + // Identifies the current IP address used by this network. + uint32 ip() const { return ip_; } + void set_ip(uint32 ip) { ip_ = ip; } + + // Updates the list of sessions that are ongoing. + void StartSession(NetworkSession* session); + void StopSession(NetworkSession* session); + + // Re-computes the estimate of near-future quality based on the information + // as of this exact moment. + void EstimateQuality(); + + // Returns the current estimate of the near-future quality of connections + // that use this local interface. + double quality() { return quality_; } + +private: + typedef std::vector SessionList; + + std::string name_; + uint32 ip_; + SessionList sessions_; + double uniform_numerator_; + double uniform_denominator_; + double exponential_numerator_; + double exponential_denominator_; + uint32 last_data_time_; + double quality_; + + // Updates the statistics maintained to include the given estimate. + void AddDataPoint(uint32 time, double quality); + + // Converts the internal state to and from a string. This is used to record + // quality information into a permanent store. + void SetState(std::string str); + std::string GetState(); + + friend class NetworkManager; +}; + +// Represents a session that is in progress using a particular network and can +// provide data about the quality of the network at any given moment. +class NetworkSession { +public: + // Determines whether this session has an estimate at this moment. We will + // only call GetCurrentQuality when this returns true. + virtual bool HasQuality() = 0; + + // Returns an estimate of the quality at this exact moment. The result should + // be a MOS (mean opinion score) value. + virtual float GetCurrentQuality() = 0; + +}; + +const double QUALITY_BAD = 3.0; +const double QUALITY_FAIR = 3.35; +const double QUALITY_GOOD = 3.7; + +} // namespace cricket + +#endif // __NETWORK_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/physicalsocketserver.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/physicalsocketserver.cc new file mode 100644 index 00000000..91d2daad --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/physicalsocketserver.cc @@ -0,0 +1,1117 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif + +#include + +#ifdef POSIX +extern "C" { +#include +#include +#include +#include +} +#endif + +#include "talk/base/basictypes.h" +#include "talk/base/byteorder.h" +#include "talk/base/common.h" +#include "talk/base/logging.h" +#include "talk/base/physicalsocketserver.h" +#include "talk/base/jtime.h" +#include "talk/base/winping.h" + +#ifdef __linux +#define IP_MTU 14 // Until this is integrated from linux/in.h to netinet/in.h +#endif // __linux + +#ifdef WIN32 +#include +#include +#define _WINSOCKAPI_ +#include +#undef SetPort + +#include +#include + +class WinsockInitializer { +public: + WinsockInitializer() { + WSADATA wsaData; + WORD wVersionRequested = MAKEWORD(1, 0); + err_ = WSAStartup(wVersionRequested, &wsaData); + } + ~WinsockInitializer() { + WSACleanup(); + } + int error() { + return err_; + } +private: + int err_; +}; +WinsockInitializer g_winsockinit; +#endif + +namespace cricket { + +const int kfRead = 0x0001; +const int kfWrite = 0x0002; +const int kfConnect = 0x0004; +const int kfClose = 0x0008; + + +// Standard MTUs +const uint16 PACKET_MAXIMUMS[] = { + 65535, // Theoretical maximum, Hyperchannel + 32000, // Nothing + 17914, // 16Mb IBM Token Ring + 8166, // IEEE 802.4 + //4464, // IEEE 802.5 (4Mb max) + 4352, // FDDI + //2048, // Wideband Network + 2002, // IEEE 802.5 (4Mb recommended) + //1536, // Expermental Ethernet Networks + //1500, // Ethernet, Point-to-Point (default) + 1492, // IEEE 802.3 + 1006, // SLIP, ARPANET + //576, // X.25 Networks + //544, // DEC IP Portal + //512, // NETBIOS + 508, // IEEE 802/Source-Rt Bridge, ARCNET + 296, // Point-to-Point (low delay) + 68, // Official minimum + 0, // End of list marker +}; + +const uint32 IP_HEADER_SIZE = 20; +const uint32 ICMP_HEADER_SIZE = 8; + +class PhysicalSocket : public AsyncSocket { +public: + PhysicalSocket(PhysicalSocketServer* ss, SOCKET s = INVALID_SOCKET) + : ss_(ss), s_(s), enabled_events_(0), error_(0), + state_((s == INVALID_SOCKET) ? CS_CLOSED : CS_CONNECTED) { + if (s != INVALID_SOCKET) + enabled_events_ = kfRead | kfWrite; + } + + virtual ~PhysicalSocket() { + Close(); + } + + // Creates the underlying OS socket (same as the "socket" function). + virtual bool Create(int type) { + Close(); + s_ = ::socket(AF_INET, type, 0); + UpdateLastError(); + enabled_events_ = kfRead | kfWrite; + return s_ != INVALID_SOCKET; + } + + SocketAddress GetLocalAddress() const { + struct sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + int result = ::getsockname(s_, (struct sockaddr*)&addr, &addrlen); + assert(addrlen == sizeof(addr)); + if (result >= 0) { + return SocketAddress(NetworkToHost32(addr.sin_addr.s_addr), + NetworkToHost16(addr.sin_port)); + } else { + return SocketAddress(); + } + } + + SocketAddress GetRemoteAddress() const { + struct sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + int result = ::getpeername(s_, (struct sockaddr*)&addr, &addrlen); + assert(addrlen == sizeof(addr)); + if (result >= 0) { + return SocketAddress( + NetworkToHost32(addr.sin_addr.s_addr), + NetworkToHost16(addr.sin_port)); + } else { + assert(errno == ENOTCONN); + return SocketAddress(); + } + } + + int Bind(const SocketAddress& addr) { + struct sockaddr_in saddr; + IP2SA(&addr, &saddr); + int err = ::bind(s_, (struct sockaddr*)&saddr, sizeof(saddr)); + UpdateLastError(); + return err; + } + + int Connect(const SocketAddress& addr) { + // TODO: Implicit creation is required to reconnect... + // ...but should we make it more explicit? + if ((s_ == INVALID_SOCKET) && !Create(SOCK_STREAM)) + return SOCKET_ERROR; + SocketAddress addr2(addr); + if (addr2.IsUnresolved()) { + LOG(INFO) << "Resolving addr in PhysicalSocket::Connect"; + addr2.Resolve(); // TODO: Do this async later? + } + struct sockaddr_in saddr; + IP2SA(&addr2, &saddr); + int err = ::connect(s_, (struct sockaddr*)&saddr, sizeof(saddr)); + UpdateLastError(); + //LOG(INFO) << "SOCK[" << static_cast(s_) << "] Connect(" << addr2.ToString() << ") Ret: " << err << " Error: " << error_; + if (err == 0) { + state_ = CS_CONNECTED; + } else if (IsBlockingError(error_)) { + state_ = CS_CONNECTING; + enabled_events_ |= kfConnect; + } + return err; + } + + int GetError() const { + return error_; + } + + void SetError(int error) { + error_ = error; + } + + ConnState GetState() const { + return state_; + } + + int SetOption(Option opt, int value) { + assert(opt == OPT_DONTFRAGMENT); +#ifdef WIN32 + value = (value == 0) ? 0 : 1; + return ::setsockopt( + s_, IPPROTO_IP, IP_DONTFRAGMENT, reinterpret_cast(&value), + sizeof(value)); +#endif +#ifdef __linux + value = (value == 0) ? IP_PMTUDISC_DONT : IP_PMTUDISC_DO; + return ::setsockopt( + s_, IPPROTO_IP, IP_MTU_DISCOVER, &value, sizeof(value)); +#endif +#ifdef OSX + // This is not possible on OSX. + return -1; +#endif + } + + int Send(const void *pv, size_t cb) { + int sent = ::send(s_, reinterpret_cast(pv), (int)cb, 0); + UpdateLastError(); + //LOG(INFO) << "SOCK[" << static_cast(s_) << "] Send(" << cb << ") Ret: " << sent << " Error: " << error_; + ASSERT(sent <= static_cast(cb)); // We have seen minidumps where this may be false + if ((sent < 0) && IsBlockingError(error_)) { + enabled_events_ |= kfWrite; + } + return sent; + } + + int SendTo(const void *pv, size_t cb, const SocketAddress& addr) { + struct sockaddr_in saddr; + IP2SA(&addr, &saddr); + int sent = ::sendto( + s_, (const char *)pv, (int)cb, 0, (struct sockaddr*)&saddr, + sizeof(saddr)); + UpdateLastError(); + ASSERT(sent <= static_cast(cb)); // We have seen minidumps where this may be false + if ((sent < 0) && IsBlockingError(error_)) { + enabled_events_ |= kfWrite; + } + return sent; + } + + int Recv(void *pv, size_t cb) { + int received = ::recv(s_, (char *)pv, (int)cb, 0); + UpdateLastError(); + if ((received >= 0) || IsBlockingError(error_)) { + enabled_events_ |= kfRead; + } + return received; + } + + int RecvFrom(void *pv, size_t cb, SocketAddress *paddr) { + struct sockaddr saddr; + socklen_t cbAddr = sizeof(saddr); + int received = ::recvfrom(s_, (char *)pv, (int)cb, 0, &saddr, &cbAddr); + UpdateLastError(); + if ((received >= 0) && (paddr != NULL)) + SA2IP(&saddr, paddr); + if ((received >= 0) || IsBlockingError(error_)) { + enabled_events_ |= kfRead; + } + return received; + } + + int Listen(int backlog) { + int err = ::listen(s_, backlog); + UpdateLastError(); + if (err == 0) + state_ = CS_CONNECTING; + return err; + } + + Socket* Accept(SocketAddress *paddr) { + struct sockaddr saddr; + socklen_t cbAddr = sizeof(saddr); + SOCKET s = ::accept(s_, &saddr, &cbAddr); + UpdateLastError(); + if (s == INVALID_SOCKET) + return NULL; + if (paddr != NULL) + SA2IP(&saddr, paddr); + return ss_->WrapSocket(s); + } + + int Close() { + if (s_ == INVALID_SOCKET) + return 0; + int err = ::closesocket(s_); + UpdateLastError(); + //LOG(INFO) << "SOCK[" << static_cast(s_) << "] Close() Ret: " << err << " Error: " << error_; + s_ = INVALID_SOCKET; + state_ = CS_CLOSED; + enabled_events_ = 0; + return err; + } + + int EstimateMTU(uint16* mtu) { + SocketAddress addr = GetRemoteAddress(); + if (addr.IsAny()) { + error_ = ENOTCONN; + return -1; + } + +#ifdef WIN32 + + WinPing ping; + if (!ping.IsValid()) { + error_ = EINVAL; // can't think of a better error ID + return -1; + } + + for (int level = 0; PACKET_MAXIMUMS[level + 1] > 0; ++level) { + int32 size = PACKET_MAXIMUMS[level] - IP_HEADER_SIZE - ICMP_HEADER_SIZE; + if (ping.Ping(addr.ip(), size, 0, 1, false) != WinPing::PING_TOO_LARGE) { + *mtu = PACKET_MAXIMUMS[level]; + return 0; + } + } + + assert(false); + return 0; + +#endif // WIN32 + +#ifdef __linux + + int value; + socklen_t vlen = sizeof(value); + int err = getsockopt(s_, IPPROTO_IP, IP_MTU, &value, &vlen); + if (err < 0) { + UpdateLastError(); + return err; + } + + assert((0 <= value) && (value <= 65536)); + *mtu = uint16(value); + return 0; + +#endif // __linux + + // TODO: OSX support + } + + SocketServer* socketserver() { return ss_; } + +protected: + PhysicalSocketServer* ss_; + SOCKET s_; + uint32 enabled_events_; + int error_; + ConnState state_; + + void UpdateLastError() { +#ifdef WIN32 + error_ = WSAGetLastError(); +#endif +#ifdef POSIX + error_ = errno; +#endif + } + + void IP2SA(const SocketAddress *paddr, struct sockaddr_in *psaddr) { + memset(psaddr, 0, sizeof(*psaddr)); + psaddr->sin_family = AF_INET; + psaddr->sin_port = HostToNetwork16(paddr->port()); + if (paddr->ip() == 0) + psaddr->sin_addr.s_addr = INADDR_ANY; + else + psaddr->sin_addr.s_addr = HostToNetwork32(paddr->ip()); + } + + void SA2IP(const struct sockaddr *psaddr, SocketAddress *paddr) { + const struct sockaddr_in *psaddr_in = + reinterpret_cast(psaddr); + paddr->SetIP(NetworkToHost32(psaddr_in->sin_addr.s_addr)); + paddr->SetPort(NetworkToHost16(psaddr_in->sin_port)); + } +}; + +#ifdef POSIX +class Dispatcher { +public: + virtual uint32 GetRequestedEvents() = 0; + virtual void OnPreEvent(uint32 ff) = 0; + virtual void OnEvent(uint32 ff, int err) = 0; + virtual int GetDescriptor() = 0; +}; + +class EventDispatcher : public Dispatcher { +public: + EventDispatcher(PhysicalSocketServer* ss) : ss_(ss), fSignaled_(false) { + if (pipe(afd_) < 0) + LOG(LERROR) << "pipe failed"; + ss_->Add(this); + } + + virtual ~EventDispatcher() { + ss_->Remove(this); + close(afd_[0]); + close(afd_[1]); + } + + virtual void Signal() { + CritScope cs(&crit_); + if (!fSignaled_) { + uint8 b = 0; + if (write(afd_[1], &b, sizeof(b)) < 0) + LOG(LERROR) << "write failed"; + fSignaled_ = true; + } + } + + virtual uint32 GetRequestedEvents() { + return kfRead; + } + + virtual void OnPreEvent(uint32 ff) { + // It is not possible to perfectly emulate an auto-resetting event with + // pipes. This simulates it by resetting before the event is handled. + + CritScope cs(&crit_); + if (fSignaled_) { + uint8 b; + read(afd_[0], &b, sizeof(b)); + fSignaled_ = false; + } + } + + virtual void OnEvent(uint32 ff, int err) { + assert(false); + } + + virtual int GetDescriptor() { + return afd_[0]; + } + +private: + PhysicalSocketServer *ss_; + int afd_[2]; + bool fSignaled_; + CriticalSection crit_; +}; + +class SocketDispatcher : public Dispatcher, public PhysicalSocket { +public: + SocketDispatcher(PhysicalSocketServer *ss) : PhysicalSocket(ss) { + ss_->Add(this); + } + SocketDispatcher(SOCKET s, PhysicalSocketServer *ss) : PhysicalSocket(ss, s) { + ss_->Add(this); + } + + virtual ~SocketDispatcher() { + ss_->Remove(this); + } + + bool Initialize() { + fcntl(s_, F_SETFL, fcntl(s_, F_GETFL, 0) | O_NONBLOCK); + return true; + } + + virtual bool Create(int type) { + // Change the socket to be non-blocking. + if (!PhysicalSocket::Create(type)) + return false; + + return Initialize(); + } + + virtual int GetDescriptor() { + return s_; + } + + virtual uint32 GetRequestedEvents() { + return enabled_events_; + } + + virtual void OnPreEvent(uint32 ff) { + } + + virtual void OnEvent(uint32 ff, int err) { + if ((ff & kfRead) != 0) { + enabled_events_ &= ~kfRead; + SignalReadEvent(this); + } + if ((ff & kfWrite) != 0) { + enabled_events_ &= ~kfWrite; + SignalWriteEvent(this); + } + if ((ff & kfConnect) != 0) { + enabled_events_ &= ~kfConnect; + SignalConnectEvent(this); + } + if ((ff & kfClose) != 0) + SignalCloseEvent(this, err); + } +}; + +class FileDispatcher: public Dispatcher, public AsyncFile { +public: + FileDispatcher(int fd, PhysicalSocketServer *ss) : ss_(ss), fd_(fd) { + set_readable(true); + + ss_->Add(this); + + fcntl(fd_, F_SETFL, fcntl(fd_, F_GETFL, 0) | O_NONBLOCK); + } + + virtual ~FileDispatcher() { + ss_->Remove(this); + } + + SocketServer* socketserver() { return ss_; } + + virtual int GetDescriptor() { + return fd_; + } + + virtual uint32 GetRequestedEvents() { + return flags_; + } + + virtual void OnPreEvent(uint32 ff) { + } + + virtual void OnEvent(uint32 ff, int err) { + if ((ff & kfRead) != 0) + SignalReadEvent(this); + if ((ff & kfWrite) != 0) + SignalWriteEvent(this); + if ((ff & kfClose) != 0) + SignalCloseEvent(this, err); + } + + virtual bool readable() { + return (flags_ & kfRead) != 0; + } + + virtual void set_readable(bool value) { + flags_ = value ? (flags_ | kfRead) : (flags_ & ~kfRead); + } + + virtual bool writable() { + return (flags_ & kfWrite) != 0; + } + + virtual void set_writable(bool value) { + flags_ = value ? (flags_ | kfWrite) : (flags_ & ~kfWrite); + } + +private: + PhysicalSocketServer* ss_; + int fd_; + int flags_; +}; + +AsyncFile* PhysicalSocketServer::CreateFile(int fd) { + return new FileDispatcher(fd, this); +} + +#endif // POSIX + +#ifdef WIN32 +class Dispatcher { +public: + virtual uint32 GetRequestedEvents() = 0; + virtual void OnPreEvent(uint32 ff) = 0; + virtual void OnEvent(uint32 ff, int err) = 0; + virtual WSAEVENT GetWSAEvent() = 0; + virtual SOCKET GetSocket() = 0; + virtual bool CheckSignalClose() = 0; +}; + +uint32 FlagsToEvents(uint32 events) { + uint32 ffFD = FD_CLOSE | FD_ACCEPT; + if (events & kfRead) + ffFD |= FD_READ; + if (events & kfWrite) + ffFD |= FD_WRITE; + if (events & kfConnect) + ffFD |= FD_CONNECT; + return ffFD; +} + +class EventDispatcher : public Dispatcher { +public: + EventDispatcher(PhysicalSocketServer *ss) : ss_(ss) { + if (hev_ = WSACreateEvent()) { + ss_->Add(this); + } + } + + ~EventDispatcher() { + if (hev_ != NULL) { + ss_->Remove(this); + WSACloseEvent(hev_); + hev_ = NULL; + } + } + + virtual void Signal() { + if (hev_ != NULL) + WSASetEvent(hev_); + } + + virtual uint32 GetRequestedEvents() { + return 0; + } + + virtual void OnPreEvent(uint32 ff) { + WSAResetEvent(hev_); + } + + virtual void OnEvent(uint32 ff, int err) { + } + + virtual WSAEVENT GetWSAEvent() { + return hev_; + } + + virtual SOCKET GetSocket() { + return INVALID_SOCKET; + } + + virtual bool CheckSignalClose() { return false; } + +private: + PhysicalSocketServer* ss_; + WSAEVENT hev_; +}; + +class SocketDispatcher : public Dispatcher, public PhysicalSocket { +public: + static int next_id_; + int id_; + bool signal_close_; + int signal_err_; + + SocketDispatcher(PhysicalSocketServer* ss) : PhysicalSocket(ss), id_(0), signal_close_(false) { + } + SocketDispatcher(SOCKET s, PhysicalSocketServer* ss) : PhysicalSocket(ss, s), id_(0), signal_close_(false) { + } + + virtual ~SocketDispatcher() { + Close(); + } + + bool Initialize() { + assert(s_ != INVALID_SOCKET); + // Must be a non-blocking + u_long argp = 1; + ioctlsocket(s_, FIONBIO, &argp); + ss_->Add(this); + return true; + } + + virtual bool Create(int type) { + // Create socket + if (!PhysicalSocket::Create(type)) + return false; + + if (!Initialize()) + return false; + + do { id_ = ++next_id_; } while (id_ == 0); + return true; + } + + virtual int Close() { + if (s_ == INVALID_SOCKET) + return 0; + + id_ = 0; + signal_close_ = false; + ss_->Remove(this); + return PhysicalSocket::Close(); + } + + virtual uint32 GetRequestedEvents() { + return enabled_events_; + } + + virtual void OnPreEvent(uint32 ff) { + if ((ff & kfConnect) != 0) + state_ = CS_CONNECTED; + } + + virtual void OnEvent(uint32 ff, int err) { + int cache_id = id_; + if ((ff & kfRead) != 0) { + enabled_events_ &= ~kfRead; + SignalReadEvent(this); + } + if (((ff & kfWrite) != 0) && (id_ == cache_id)) { + enabled_events_ &= ~kfWrite; + SignalWriteEvent(this); + } + if (((ff & kfConnect) != 0) && (id_ == cache_id)) { + enabled_events_ &= ~kfConnect; + SignalConnectEvent(this); + } + if (((ff & kfClose) != 0) && (id_ == cache_id)) { + //LOG(INFO) << "SOCK[" << static_cast(s_) << "] OnClose() Error: " << err; + signal_close_ = true; + signal_err_ = err; + } + } + + virtual WSAEVENT GetWSAEvent() { + return WSA_INVALID_EVENT; + } + + virtual SOCKET GetSocket() { + return s_; + } + + virtual bool CheckSignalClose() { + if (!signal_close_) + return false; + + char ch; + if (recv(s_, &ch, 1, MSG_PEEK) > 0) + return false; + + signal_close_ = false; + SignalCloseEvent(this, signal_err_); + return true; + } +}; + +int SocketDispatcher::next_id_ = 0; + +#endif // WIN32 + +// Sets the value of a boolean value to false when signaled. +class Signaler : public EventDispatcher { +public: + Signaler(PhysicalSocketServer* ss, bool* pf) + : EventDispatcher(ss), pf_(pf) { + } + virtual ~Signaler() { } + + void OnEvent(uint32 ff, int err) { + if (pf_) + *pf_ = false; + } + +private: + bool *pf_; +}; + +PhysicalSocketServer::PhysicalSocketServer() : fWait_(false), + last_tick_tracked_(0), last_tick_dispatch_count_(0) { + signal_wakeup_ = new Signaler(this, &fWait_); +} + +PhysicalSocketServer::~PhysicalSocketServer() { + delete signal_wakeup_; +} + +void PhysicalSocketServer::WakeUp() { + signal_wakeup_->Signal(); +} + +Socket* PhysicalSocketServer::CreateSocket(int type) { + PhysicalSocket* socket = new PhysicalSocket(this); + if (socket->Create(type)) { + return socket; + } else { + delete socket; + return 0; + } +} + +AsyncSocket* PhysicalSocketServer::CreateAsyncSocket(int type) { + SocketDispatcher* dispatcher = new SocketDispatcher(this); + if (dispatcher->Create(type)) { + return dispatcher; + } else { + delete dispatcher; + return 0; + } +} + +AsyncSocket* PhysicalSocketServer::WrapSocket(SOCKET s) { + SocketDispatcher* dispatcher = new SocketDispatcher(s, this); + if (dispatcher->Initialize()) { + return dispatcher; + } else { + delete dispatcher; + return 0; + } +} + +void PhysicalSocketServer::Add(Dispatcher *pdispatcher) { + CritScope cs(&crit_); + dispatchers_.push_back(pdispatcher); +} + +void PhysicalSocketServer::Remove(Dispatcher *pdispatcher) { + CritScope cs(&crit_); + dispatchers_.erase(std::remove(dispatchers_.begin(), dispatchers_.end(), pdispatcher), dispatchers_.end()); +} + +#ifdef POSIX +bool PhysicalSocketServer::Wait(int cmsWait, bool process_io) { + // Calculate timing information + + struct timeval *ptvWait = NULL; + struct timeval tvWait; + struct timeval tvStop; + if (cmsWait != -1) { + // Calculate wait timeval + tvWait.tv_sec = cmsWait / 1000; + tvWait.tv_usec = (cmsWait % 1000) * 1000; + ptvWait = &tvWait; + + // Calculate when to return in a timeval + gettimeofday(&tvStop, NULL); + tvStop.tv_sec += tvWait.tv_sec; + tvStop.tv_usec += tvWait.tv_usec; + if (tvStop.tv_usec >= 1000000) { + tvStop.tv_usec -= 1000000; + tvStop.tv_sec += 1; + } + } + + // Zero all fd_sets. Don't need to do this inside the loop since + // select() zeros the descriptors not signaled + + fd_set fdsRead; + FD_ZERO(&fdsRead); + fd_set fdsWrite; + FD_ZERO(&fdsWrite); + + fWait_ = true; + + while (fWait_) { + int fdmax = -1; + { + CritScope cr(&crit_); + for (unsigned i = 0; i < dispatchers_.size(); i++) { + // Query dispatchers for read and write wait state + + Dispatcher *pdispatcher = dispatchers_[i]; + assert(pdispatcher); + if (!process_io && (pdispatcher != signal_wakeup_)) + continue; + int fd = pdispatcher->GetDescriptor(); + if (fd > fdmax) + fdmax = fd; + uint32 ff = pdispatcher->GetRequestedEvents(); + if (ff & kfRead) + FD_SET(fd, &fdsRead); + if (ff & (kfWrite | kfConnect)) + FD_SET(fd, &fdsWrite); + } + } + + // Wait then call handlers as appropriate + // < 0 means error + // 0 means timeout + // > 0 means count of descriptors ready + int n = select(fdmax + 1, &fdsRead, &fdsWrite, NULL, ptvWait); + + // If error, return error + // todo: do something intelligent + + if (n < 0) + return false; + + // If timeout, return success + + if (n == 0) + return true; + + // We have signaled descriptors + + { + CritScope cr(&crit_); + for (unsigned i = 0; i < dispatchers_.size(); i++) { + Dispatcher *pdispatcher = dispatchers_[i]; + int fd = pdispatcher->GetDescriptor(); + uint32 ff = 0; + if (FD_ISSET(fd, &fdsRead)) { + FD_CLR(fd, &fdsRead); + ff |= kfRead; + } + if (FD_ISSET(fd, &fdsWrite)) { + FD_CLR(fd, &fdsWrite); + if (pdispatcher->GetRequestedEvents() & kfConnect) { + ff |= kfConnect; + } else { + ff |= kfWrite; + } + } + if (ff != 0) { + pdispatcher->OnPreEvent(ff); + pdispatcher->OnEvent(ff, 0); + } + } + } + + // Recalc the time remaining to wait. Doing it here means it doesn't get + // calced twice the first time through the loop + + if (cmsWait != -1) { + ptvWait->tv_sec = 0; + ptvWait->tv_usec = 0; + struct timeval tvT; + gettimeofday(&tvT, NULL); + if (tvStop.tv_sec >= tvT.tv_sec) { + ptvWait->tv_sec = tvStop.tv_sec - tvT.tv_sec; + ptvWait->tv_usec = tvStop.tv_usec - tvT.tv_usec; + if (ptvWait->tv_usec < 0) { + ptvWait->tv_usec += 1000000; + ptvWait->tv_sec -= 1; + } + } + } + } + + return true; +} +#endif // POSIX + +#ifdef WIN32 +bool PhysicalSocketServer::Wait(int cmsWait, bool process_io) +{ + int cmsTotal = cmsWait; + int cmsElapsed = 0; + uint32 msStart = GetMillisecondCount(); + +#if LOGGING + if (last_tick_dispatch_count_ == 0) { + last_tick_tracked_ = msStart; + } +#endif + + WSAEVENT socket_ev = WSACreateEvent(); + + fWait_ = true; + while (fWait_) { + std::vector events; + std::vector event_owners; + + events.push_back(socket_ev); + + { + CritScope cr(&crit_); + for (size_t i = 0; i < dispatchers_.size(); ++i) { + Dispatcher * disp = dispatchers_[i]; + if (!process_io && (disp != signal_wakeup_)) + continue; + SOCKET s = disp->GetSocket(); + if (disp->CheckSignalClose()) { + // We just signalled close, don't poll this socket + } else if (s != INVALID_SOCKET) { + WSAEventSelect(s, events[0], FlagsToEvents(disp->GetRequestedEvents())); + } else { + events.push_back(disp->GetWSAEvent()); + event_owners.push_back(disp); + } + } + } + + // Which is shorter, the delay wait or the asked wait? + + int cmsNext; + if (cmsWait == -1) { + cmsNext = cmsWait; + } else { + cmsNext = cmsTotal - cmsElapsed; + if (cmsNext < 0) + cmsNext = 0; + } + + // Wait for one of the events to signal + DWORD dw = WSAWaitForMultipleEvents(static_cast(events.size()), &events[0], false, cmsNext, false); + +#if 0 // LOGGING + // we track this information purely for logging purposes. + last_tick_dispatch_count_++; + if (last_tick_dispatch_count_ >= 1000) { + uint32 now = GetMillisecondCount(); + LOG(INFO) << "PhysicalSocketServer took " << TimeDiff(now, last_tick_tracked_) << "ms for 1000 events"; + + // If we get more than 1000 events in a second, we are spinning badly + // (normally it should take about 8-20 seconds). + assert(TimeDiff(now, last_tick_tracked_) > 1000); + + last_tick_tracked_ = now; + last_tick_dispatch_count_ = 0; + } +#endif + + // Failed? + // todo: need a better strategy than this! + + if (dw == WSA_WAIT_FAILED) { + int error = WSAGetLastError(); + assert(false); + WSACloseEvent(socket_ev); + return false; + } + + // Timeout? + + if (dw == WSA_WAIT_TIMEOUT) { + WSACloseEvent(socket_ev); + return true; + } + + // Figure out which one it is and call it + + { + CritScope cr(&crit_); + int index = dw - WSA_WAIT_EVENT_0; + if (index > 0) { + --index; // The first event is the socket event + event_owners[index]->OnPreEvent(0); + event_owners[index]->OnEvent(0, 0); + } else if (process_io) { + for (size_t i = 0; i < dispatchers_.size(); ++i) { + Dispatcher * disp = dispatchers_[i]; + SOCKET s = disp->GetSocket(); + if (s == INVALID_SOCKET) + continue; + + WSANETWORKEVENTS wsaEvents; + int err = WSAEnumNetworkEvents(s, events[0], &wsaEvents); + if (err == 0) { + +#if LOGGING + { + if ((wsaEvents.lNetworkEvents & FD_READ) && wsaEvents.iErrorCode[FD_READ_BIT] != 0) { + LOG(WARNING) << "PhysicalSocketServer got FD_READ_BIT error " << wsaEvents.iErrorCode[FD_READ_BIT]; + } + if ((wsaEvents.lNetworkEvents & FD_WRITE) && wsaEvents.iErrorCode[FD_WRITE_BIT] != 0) { + LOG(WARNING) << "PhysicalSocketServer got FD_WRITE_BIT error " << wsaEvents.iErrorCode[FD_WRITE_BIT]; + } + if ((wsaEvents.lNetworkEvents & FD_CONNECT) && wsaEvents.iErrorCode[FD_CONNECT_BIT] != 0) { + LOG(WARNING) << "PhysicalSocketServer got FD_CONNECT_BIT error " << wsaEvents.iErrorCode[FD_CONNECT_BIT]; + } + if ((wsaEvents.lNetworkEvents & FD_ACCEPT) && wsaEvents.iErrorCode[FD_ACCEPT_BIT] != 0) { + LOG(WARNING) << "PhysicalSocketServer got FD_ACCEPT_BIT error " << wsaEvents.iErrorCode[FD_ACCEPT_BIT]; + } + if ((wsaEvents.lNetworkEvents & FD_CLOSE) && wsaEvents.iErrorCode[FD_CLOSE_BIT] != 0) { + LOG(WARNING) << "PhysicalSocketServer got FD_CLOSE_BIT error " << wsaEvents.iErrorCode[FD_CLOSE_BIT]; + } + } +#endif + uint32 ff = 0; + int errcode = 0; + if (wsaEvents.lNetworkEvents & FD_READ) + ff |= kfRead; + if (wsaEvents.lNetworkEvents & FD_WRITE) + ff |= kfWrite; + if (wsaEvents.lNetworkEvents & FD_CONNECT) { + if (wsaEvents.iErrorCode[FD_CONNECT_BIT] == 0) { + ff |= kfConnect; + } else { + // TODO: Decide whether we want to signal connect, but with an error code + ff |= kfClose; + errcode = wsaEvents.iErrorCode[FD_CONNECT_BIT]; + } + } + if (wsaEvents.lNetworkEvents & FD_ACCEPT) + ff |= kfRead; + if (wsaEvents.lNetworkEvents & FD_CLOSE) { + ff |= kfClose; + errcode = wsaEvents.iErrorCode[FD_CLOSE_BIT]; + } + if (ff != 0) { + disp->OnPreEvent(ff); + disp->OnEvent(ff, errcode); + } + } + } + } + + // Reset the network event until new activity occurs + WSAResetEvent(socket_ev); + } + + // Break? + + if (!fWait_) + break; + cmsElapsed = GetMillisecondCount() - msStart; + if (cmsWait != -1) { + if (cmsElapsed >= cmsWait) + break; + } + } + + // Done + + WSACloseEvent(socket_ev); + return true; +} +#endif // WIN32 + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/physicalsocketserver.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/physicalsocketserver.h new file mode 100644 index 00000000..305b64d9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/physicalsocketserver.h @@ -0,0 +1,80 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __PHYSICALSOCKETSERVER_H__ +#define __PHYSICALSOCKETSERVER_H__ + +#include "talk/base/asyncfile.h" +#include "talk/base/socketserver.h" +#include "talk/base/criticalsection.h" +#include + +#ifdef POSIX +typedef int SOCKET; +#endif // POSIX + +namespace cricket { + +class Dispatcher; +class Signaler; + +// A socket server that provides the real sockets of the underlying OS. +class PhysicalSocketServer : public SocketServer { +public: + PhysicalSocketServer(); + virtual ~PhysicalSocketServer(); + + // SocketFactory: + virtual Socket* CreateSocket(int type); + virtual AsyncSocket* CreateAsyncSocket(int type); + + // Internal Factory for Accept + AsyncSocket* WrapSocket(SOCKET s); + + // SocketServer: + virtual bool Wait(int cms, bool process_io); + virtual void WakeUp(); + + void Add(Dispatcher* dispatcher); + void Remove(Dispatcher* dispatcher); + +#ifdef POSIX + AsyncFile* CreateFile(int fd); +#endif + +private: + std::vector dispatchers_; + Signaler* signal_wakeup_; + CriticalSection crit_; + bool fWait_; + uint32 last_tick_tracked_; + int last_tick_dispatch_count_; +}; + +} // namespace cricket + +#endif // __PHYSICALSOCKETSERVER_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/proxyinfo.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/proxyinfo.h new file mode 100644 index 00000000..1bd817b9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/proxyinfo.h @@ -0,0 +1,52 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __PROXYINFO_H__ +#define __PROXYINFO_H__ + +#include +#include "talk/base/socketaddress.h" +// TODO: move xmpppassword into base +#include "talk/xmpp/xmpppassword.h" + +namespace cricket { + +enum ProxyType { PROXY_NONE, PROXY_HTTPS, PROXY_SOCKS5, PROXY_UNKNOWN }; +const char * ProxyToString(ProxyType proxy); + +struct ProxyInfo { + ProxyType type; + SocketAddress address; + std::string username; + buzz::XmppPassword password; + + ProxyInfo() : type(PROXY_NONE) { } +}; + +} // namespace cricket + +#endif // __PROXYINFO_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/scoped_ptr.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/scoped_ptr.h new file mode 100644 index 00000000..0470ff83 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/scoped_ptr.h @@ -0,0 +1,259 @@ +#ifndef SCOPED_PTR_H +#define SCOPED_PTR_H + +// (C) Copyright Greg Colvin and Beman Dawes 1998, 1999. +// Copyright (c) 2001, 2002 Peter Dimov +// +// Permission to copy, use, modify, sell and distribute this software +// is granted provided this copyright notice appears in all copies. +// This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// See http://www.boost.org/libs/smart_ptr/scoped_ptr.htm for documentation. +// + +// scoped_ptr mimics a built-in pointer except that it guarantees deletion +// of the object pointed to, either on destruction of the scoped_ptr or via +// an explicit reset(). scoped_ptr is a simple solution for simple needs; +// use shared_ptr or std::auto_ptr if your needs are more complex. + +// scoped_ptr_malloc added in by Google. When one of +// these goes out of scope, instead of doing a delete or delete[], it +// calls free(). scoped_ptr_malloc is likely to see much more +// use than any other specializations. + +// release() added in by Google. Use this to conditionally +// transfer ownership of a heap-allocated object to the caller, usually on +// method success. + +#include // for std::ptrdiff_t +#include // for assert +#include // for free() decl + +#ifdef _WIN32 +namespace std { using ::ptrdiff_t; }; +#endif // _WIN32 + +namespace buzz { + +template +class scoped_ptr { + private: + + T* ptr; + + scoped_ptr(scoped_ptr const &); + scoped_ptr & operator=(scoped_ptr const &); + + public: + + typedef T element_type; + + explicit scoped_ptr(T* p = 0): ptr(p) {} + + ~scoped_ptr() { + typedef char type_must_be_complete[sizeof(T)]; + delete ptr; + } + + void reset(T* p = 0) { + typedef char type_must_be_complete[sizeof(T)]; + + if (ptr != p) { + delete ptr; + ptr = p; + } + } + + T& operator*() const { + assert(ptr != 0); + return *ptr; + } + + T* operator->() const { + assert(ptr != 0); + return ptr; + } + + T* get() const { + return ptr; + } + + void swap(scoped_ptr & b) { + T* tmp = b.ptr; + b.ptr = ptr; + ptr = tmp; + } + + T* release() { + T* tmp = ptr; + ptr = 0; + return tmp; + } + + T** accept() { + if (ptr) { + delete ptr; + ptr = 0; + } + return &ptr; + } + + T** use() { + return &ptr; + } +}; + +template inline +void swap(scoped_ptr& a, scoped_ptr& b) { + a.swap(b); +} + + + + +// scoped_array extends scoped_ptr to arrays. Deletion of the array pointed to +// is guaranteed, either on destruction of the scoped_array or via an explicit +// reset(). Use shared_array or std::vector if your needs are more complex. + +template +class scoped_array { + private: + + T* ptr; + + scoped_array(scoped_array const &); + scoped_array & operator=(scoped_array const &); + + public: + + typedef T element_type; + + explicit scoped_array(T* p = 0) : ptr(p) {} + + ~scoped_array() { + typedef char type_must_be_complete[sizeof(T)]; + delete[] ptr; + } + + void reset(T* p = 0) { + typedef char type_must_be_complete[sizeof(T)]; + + if (ptr != p) { + delete [] ptr; + ptr = p; + } + } + + T& operator[](std::ptrdiff_t i) const { + assert(ptr != 0); + assert(i >= 0); + return ptr[i]; + } + + T* get() const { + return ptr; + } + + void swap(scoped_array & b) { + T* tmp = b.ptr; + b.ptr = ptr; + ptr = tmp; + } + + T* release() { + T* tmp = ptr; + ptr = 0; + return tmp; + } + + T** accept() { + if (ptr) { + delete [] ptr; + ptr = 0; + } + return &ptr; + } +}; + +template inline +void swap(scoped_array& a, scoped_array& b) { + a.swap(b); +} + +// scoped_ptr_malloc<> is similar to scoped_ptr<>, but it accepts a +// second template argument, the function used to free the object. + +template class scoped_ptr_malloc { + private: + + T* ptr; + + scoped_ptr_malloc(scoped_ptr_malloc const &); + scoped_ptr_malloc & operator=(scoped_ptr_malloc const &); + + public: + + typedef T element_type; + + explicit scoped_ptr_malloc(T* p = 0): ptr(p) {} + + ~scoped_ptr_malloc() { + typedef char type_must_be_complete[sizeof(T)]; + FF(static_cast(ptr)); + } + + void reset(T* p = 0) { + typedef char type_must_be_complete[sizeof(T)]; + + if (ptr != p) { + FF(static_cast(ptr)); + ptr = p; + } + } + + T& operator*() const { + assert(ptr != 0); + return *ptr; + } + + T* operator->() const { + assert(ptr != 0); + return ptr; + } + + T* get() const { + return ptr; + } + + void swap(scoped_ptr_malloc & b) { + T* tmp = b.ptr; + b.ptr = ptr; + ptr = tmp; + } + + T* release() { + T* tmp = ptr; + ptr = 0; + return tmp; + } + + T** accept() { + if (ptr) { + FF(static_cast(ptr)); + ptr = 0; + } + return &ptr; + } +}; + +template inline +void swap(scoped_ptr_malloc& a, scoped_ptr_malloc& b) { + a.swap(b); +} + +} + +using buzz::scoped_ptr; + +#endif // #ifndef SCOPED_PTR_H diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/sigslot.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/sigslot.h new file mode 100644 index 00000000..446516b8 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/sigslot.h @@ -0,0 +1,2700 @@ +// sigslot.h: Signal/Slot classes +// +// Written by Sarah Thompson (sarah@telergy.com) 2002. +// +// License: Public domain. You are free to use this code however you like, with the proviso that +// the author takes on no responsibility or liability for any use. +// +// QUICK DOCUMENTATION +// +// (see also the full documentation at http://sigslot.sourceforge.net/) +// +// #define switches +// SIGSLOT_PURE_ISO - Define this to force ISO C++ compliance. This also disables +// all of the thread safety support on platforms where it is +// available. +// +// SIGSLOT_USE_POSIX_THREADS - Force use of Posix threads when using a C++ compiler other than +// gcc on a platform that supports Posix threads. (When using gcc, +// this is the default - use SIGSLOT_PURE_ISO to disable this if +// necessary) +// +// SIGSLOT_DEFAULT_MT_POLICY - Where thread support is enabled, this defaults to multi_threaded_global. +// Otherwise, the default is single_threaded. #define this yourself to +// override the default. In pure ISO mode, anything other than +// single_threaded will cause a compiler error. +// +// PLATFORM NOTES +// +// Win32 - On Win32, the WIN32 symbol must be #defined. Most mainstream +// compilers do this by default, but you may need to define it +// yourself if your build environment is less standard. This causes +// the Win32 thread support to be compiled in and used automatically. +// +// Unix/Linux/BSD, etc. - If you're using gcc, it is assumed that you have Posix threads +// available, so they are used automatically. You can override this +// (as under Windows) with the SIGSLOT_PURE_ISO switch. If you're using +// something other than gcc but still want to use Posix threads, you +// need to #define SIGSLOT_USE_POSIX_THREADS. +// +// ISO C++ - If none of the supported platforms are detected, or if +// SIGSLOT_PURE_ISO is defined, all multithreading support is turned off, +// along with any code that might cause a pure ISO C++ environment to +// complain. Before you ask, gcc -ansi -pedantic won't compile this +// library, but gcc -ansi is fine. Pedantic mode seems to throw a lot of +// errors that aren't really there. If you feel like investigating this, +// please contact the author. +// +// +// THREADING MODES +// +// single_threaded - Your program is assumed to be single threaded from the point of view +// of signal/slot usage (i.e. all objects using signals and slots are +// created and destroyed from a single thread). Behaviour if objects are +// destroyed concurrently is undefined (i.e. you'll get the occasional +// segmentation fault/memory exception). +// +// multi_threaded_global - Your program is assumed to be multi threaded. Objects using signals and +// slots can be safely created and destroyed from any thread, even when +// connections exist. In multi_threaded_global mode, this is achieved by a +// single global mutex (actually a critical section on Windows because they +// are faster). This option uses less OS resources, but results in more +// opportunities for contention, possibly resulting in more context switches +// than are strictly necessary. +// +// multi_threaded_local - Behaviour in this mode is essentially the same as multi_threaded_global, +// except that each signal, and each object that inherits has_slots, all +// have their own mutex/critical section. In practice, this means that +// mutex collisions (and hence context switches) only happen if they are +// absolutely essential. However, on some platforms, creating a lot of +// mutexes can slow down the whole OS, so use this option with care. +// +// USING THE LIBRARY +// +// See the full documentation at http://sigslot.sourceforge.net/ +// +// + +#ifndef SIGSLOT_H__ +#define SIGSLOT_H__ + +#include +#include + +// On our copy of sigslot.h, we force single threading +#define SIGSLOT_PURE_ISO + +#if defined(SIGSLOT_PURE_ISO) || (!defined(WIN32) && !defined(__GNUG__) && !defined(SIGSLOT_USE_POSIX_THREADS)) +# define _SIGSLOT_SINGLE_THREADED +#elif defined(WIN32) +# define _SIGSLOT_HAS_WIN32_THREADS +# include +#elif defined(__GNUG__) || defined(SIGSLOT_USE_POSIX_THREADS) +# define _SIGSLOT_HAS_POSIX_THREADS +# include +#else +# define _SIGSLOT_SINGLE_THREADED +#endif + +#ifndef SIGSLOT_DEFAULT_MT_POLICY +# ifdef _SIGSLOT_SINGLE_THREADED +# define SIGSLOT_DEFAULT_MT_POLICY single_threaded +# else +# define SIGSLOT_DEFAULT_MT_POLICY multi_threaded_local +# endif +#endif + + +namespace sigslot { + + class single_threaded + { + public: + single_threaded() + { + ; + } + + virtual ~single_threaded() + { + ; + } + + virtual void lock() + { + ; + } + + virtual void unlock() + { + ; + } + }; + +#ifdef _SIGSLOT_HAS_WIN32_THREADS + // The multi threading policies only get compiled in if they are enabled. + class multi_threaded_global + { + public: + multi_threaded_global() + { + static bool isinitialised = false; + + if(!isinitialised) + { + InitializeCriticalSection(get_critsec()); + isinitialised = true; + } + } + + multi_threaded_global(const multi_threaded_global&) + { + ; + } + + virtual ~multi_threaded_global() + { + ; + } + + virtual void lock() + { + EnterCriticalSection(get_critsec()); + } + + virtual void unlock() + { + LeaveCriticalSection(get_critsec()); + } + + private: + CRITICAL_SECTION* get_critsec() + { + static CRITICAL_SECTION g_critsec; + return &g_critsec; + } + }; + + class multi_threaded_local + { + public: + multi_threaded_local() + { + InitializeCriticalSection(&m_critsec); + } + + multi_threaded_local(const multi_threaded_local&) + { + InitializeCriticalSection(&m_critsec); + } + + virtual ~multi_threaded_local() + { + DeleteCriticalSection(&m_critsec); + } + + virtual void lock() + { + EnterCriticalSection(&m_critsec); + } + + virtual void unlock() + { + LeaveCriticalSection(&m_critsec); + } + + private: + CRITICAL_SECTION m_critsec; + }; +#endif // _SIGSLOT_HAS_WIN32_THREADS + +#ifdef _SIGSLOT_HAS_POSIX_THREADS + // The multi threading policies only get compiled in if they are enabled. + class multi_threaded_global + { + public: + multi_threaded_global() + { + pthread_mutex_init(get_mutex(), NULL); + } + + multi_threaded_global(const multi_threaded_global&) + { + ; + } + + virtual ~multi_threaded_global() + { + ; + } + + virtual void lock() + { + pthread_mutex_lock(get_mutex()); + } + + virtual void unlock() + { + pthread_mutex_unlock(get_mutex()); + } + + private: + pthread_mutex_t* get_mutex() + { + static pthread_mutex_t g_mutex; + return &g_mutex; + } + }; + + class multi_threaded_local + { + public: + multi_threaded_local() + { + pthread_mutex_init(&m_mutex, NULL); + } + + multi_threaded_local(const multi_threaded_local&) + { + pthread_mutex_init(&m_mutex, NULL); + } + + virtual ~multi_threaded_local() + { + pthread_mutex_destroy(&m_mutex); + } + + virtual void lock() + { + pthread_mutex_lock(&m_mutex); + } + + virtual void unlock() + { + pthread_mutex_unlock(&m_mutex); + } + + private: + pthread_mutex_t m_mutex; + }; +#endif // _SIGSLOT_HAS_POSIX_THREADS + + template + class lock_block + { + public: + mt_policy *m_mutex; + + lock_block(mt_policy *mtx) + : m_mutex(mtx) + { + m_mutex->lock(); + } + + ~lock_block() + { + m_mutex->unlock(); + } + }; + + template + class has_slots; + + template + class _connection_base0 + { + public: + virtual has_slots* getdest() const = 0; + virtual void emit() = 0; + virtual _connection_base0* clone() = 0; + virtual _connection_base0* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base1 + { + public: + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type) = 0; + virtual _connection_base1* clone() = 0; + virtual _connection_base1* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base2 + { + public: + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type, arg2_type) = 0; + virtual _connection_base2* clone() = 0; + virtual _connection_base2* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base3 + { + public: + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type) = 0; + virtual _connection_base3* clone() = 0; + virtual _connection_base3* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base4 + { + public: + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type) = 0; + virtual _connection_base4* clone() = 0; + virtual _connection_base4* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base5 + { + public: + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type) = 0; + virtual _connection_base5* clone() = 0; + virtual _connection_base5* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base6 + { + public: + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, + arg6_type) = 0; + virtual _connection_base6* clone() = 0; + virtual _connection_base6* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base7 + { + public: + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, + arg6_type, arg7_type) = 0; + virtual _connection_base7* clone() = 0; + virtual _connection_base7* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _connection_base8 + { + public: + virtual has_slots* getdest() const = 0; + virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, + arg6_type, arg7_type, arg8_type) = 0; + virtual _connection_base8* clone() = 0; + virtual _connection_base8* duplicate(has_slots* pnewdest) = 0; + }; + + template + class _signal_base : public mt_policy + { + public: + virtual void slot_disconnect(has_slots* pslot) = 0; + virtual void slot_duplicate(const has_slots* poldslot, has_slots* pnewslot) = 0; + }; + + template + class has_slots : public mt_policy + { + private: + typedef typename std::set<_signal_base *> sender_set; + typedef typename sender_set::const_iterator const_iterator; + + public: + has_slots() + { + ; + } + + has_slots(const has_slots& hs) + : mt_policy(hs) + { + lock_block lock(this); + const_iterator it = hs.m_senders.begin(); + const_iterator itEnd = hs.m_senders.end(); + + while(it != itEnd) + { + (*it)->slot_duplicate(&hs, this); + m_senders.insert(*it); + ++it; + } + } + + void signal_connect(_signal_base* sender) + { + lock_block lock(this); + m_senders.insert(sender); + } + + void signal_disconnect(_signal_base* sender) + { + lock_block lock(this); + m_senders.erase(sender); + } + + virtual ~has_slots() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + const_iterator it = m_senders.begin(); + const_iterator itEnd = m_senders.end(); + + while(it != itEnd) + { + (*it)->slot_disconnect(this); + ++it; + } + + m_senders.erase(m_senders.begin(), m_senders.end()); + } + + private: + sender_set m_senders; + }; + + template + class _signal_base0 : public _signal_base + { + public: + typedef std::list<_connection_base0 *> connections_list; + + _signal_base0() + { + ; + } + + _signal_base0(const _signal_base0& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + ~_signal_base0() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base1 : public _signal_base + { + public: + typedef std::list<_connection_base1 *> connections_list; + + _signal_base1() + { + ; + } + + _signal_base1(const _signal_base1& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base1() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base2 : public _signal_base + { + public: + typedef std::list<_connection_base2 *> + connections_list; + + _signal_base2() + { + ; + } + + _signal_base2(const _signal_base2& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base2() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base3 : public _signal_base + { + public: + typedef std::list<_connection_base3 *> + connections_list; + + _signal_base3() + { + ; + } + + _signal_base3(const _signal_base3& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base3() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base4 : public _signal_base + { + public: + typedef std::list<_connection_base4 *> connections_list; + + _signal_base4() + { + ; + } + + _signal_base4(const _signal_base4& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base4() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base5 : public _signal_base + { + public: + typedef std::list<_connection_base5 *> connections_list; + + _signal_base5() + { + ; + } + + _signal_base5(const _signal_base5& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base5() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base6 : public _signal_base + { + public: + typedef std::list<_connection_base6 *> connections_list; + + _signal_base6() + { + ; + } + + _signal_base6(const _signal_base6& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base6() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base7 : public _signal_base + { + public: + typedef std::list<_connection_base7 *> connections_list; + + _signal_base7() + { + ; + } + + _signal_base7(const _signal_base7& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base7() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + template + class _signal_base8 : public _signal_base + { + public: + typedef std::list<_connection_base8 *> + connections_list; + + _signal_base8() + { + ; + } + + _signal_base8(const _signal_base8& s) + : _signal_base(s) + { + lock_block lock(this); + typename connections_list::const_iterator it = s.m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = s.m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_connect(this); + m_connected_slots.push_back((*it)->clone()); + + ++it; + } + } + + void slot_duplicate(const has_slots* oldtarget, has_slots* newtarget) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == oldtarget) + { + m_connected_slots.push_back((*it)->duplicate(newtarget)); + } + + ++it; + } + } + + ~_signal_base8() + { + disconnect_all(); + } + + void disconnect_all() + { + lock_block lock(this); + typename connections_list::const_iterator it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + (*it)->getdest()->signal_disconnect(this); + delete *it; + + ++it; + } + + m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end()); + } + +#ifdef _DEBUG + bool connected(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + while(it != itEnd) + { + itNext = it; + ++itNext; + if ((*it)->getdest() == pclass) + return true; + it = itNext; + } + return false; + } +#endif + + void disconnect(has_slots* pclass) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + if((*it)->getdest() == pclass) + { + delete *it; + m_connected_slots.erase(it); + pclass->signal_disconnect(this); + return; + } + + ++it; + } + } + + void slot_disconnect(has_slots* pslot) + { + lock_block lock(this); + typename connections_list::iterator it = m_connected_slots.begin(); + typename connections_list::iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + typename connections_list::iterator itNext = it; + ++itNext; + + if((*it)->getdest() == pslot) + { + m_connected_slots.erase(it); + // delete *it; + } + + it = itNext; + } + } + + protected: + connections_list m_connected_slots; + }; + + + template + class _connection0 : public _connection_base0 + { + public: + _connection0() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection0(dest_type* pobject, void (dest_type::*pmemfun)()) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual _connection_base0* clone() + { + return new _connection0(*this); + } + + virtual _connection_base0* duplicate(has_slots* pnewdest) + { + return new _connection0((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit() + { + (m_pobject->*m_pmemfun)(); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(); + }; + + template + class _connection1 : public _connection_base1 + { + public: + _connection1() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection1(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual _connection_base1* clone() + { + return new _connection1(*this); + } + + virtual _connection_base1* duplicate(has_slots* pnewdest) + { + return new _connection1((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1) + { + (m_pobject->*m_pmemfun)(a1); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type); + }; + + template + class _connection2 : public _connection_base2 + { + public: + _connection2() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection2(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual _connection_base2* clone() + { + return new _connection2(*this); + } + + virtual _connection_base2* duplicate(has_slots* pnewdest) + { + return new _connection2((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2) + { + (m_pobject->*m_pmemfun)(a1, a2); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type); + }; + + template + class _connection3 : public _connection_base3 + { + public: + _connection3() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection3(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual _connection_base3* clone() + { + return new _connection3(*this); + } + + virtual _connection_base3* duplicate(has_slots* pnewdest) + { + return new _connection3((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3) + { + (m_pobject->*m_pmemfun)(a1, a2, a3); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type); + }; + + template + class _connection4 : public _connection_base4 + { + public: + _connection4() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection4(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual _connection_base4* clone() + { + return new _connection4(*this); + } + + virtual _connection_base4* duplicate(has_slots* pnewdest) + { + return new _connection4((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, + arg4_type a4) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, + arg4_type); + }; + + template + class _connection5 : public _connection_base5 + { + public: + _connection5() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection5(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual _connection_base5* clone() + { + return new _connection5(*this); + } + + virtual _connection_base5* duplicate(has_slots* pnewdest) + { + return new _connection5((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type); + }; + + template + class _connection6 : public _connection_base6 + { + public: + _connection6() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection6(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual _connection_base6* clone() + { + return new _connection6(*this); + } + + virtual _connection_base6* duplicate(has_slots* pnewdest) + { + return new _connection6((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type, arg6_type); + }; + + template + class _connection7 : public _connection_base7 + { + public: + _connection7() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection7(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, arg7_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual _connection_base7* clone() + { + return new _connection7(*this); + } + + virtual _connection_base7* duplicate(has_slots* pnewdest) + { + return new _connection7((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6, a7); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type, arg6_type, arg7_type); + }; + + template + class _connection8 : public _connection_base8 + { + public: + _connection8() + { + m_pobject = NULL; + m_pmemfun = NULL; + } + + _connection8(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, + arg7_type, arg8_type)) + { + m_pobject = pobject; + m_pmemfun = pmemfun; + } + + virtual _connection_base8* clone() + { + return new _connection8(*this); + } + + virtual _connection_base8* duplicate(has_slots* pnewdest) + { + return new _connection8((dest_type *)pnewdest, m_pmemfun); + } + + virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8) + { + (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6, a7, a8); + } + + virtual has_slots* getdest() const + { + return m_pobject; + } + + private: + dest_type* m_pobject; + void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type, + arg5_type, arg6_type, arg7_type, arg8_type); + }; + + template + class signal0 : public _signal_base0 + { + public: + typedef _signal_base0 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal0() + { + ; + } + + signal0(const signal0& s) + : _signal_base0(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)()) + { + lock_block lock(this); + _connection0* conn = + new _connection0(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit() + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(); + + it = itNext; + } + } + + void operator()() + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(); + + it = itNext; + } + } + }; + + template + class signal1 : public _signal_base1 + { + public: + typedef _signal_base1 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal1() + { + ; + } + + signal1(const signal1& s) + : _signal_base1(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type)) + { + lock_block lock(this); + _connection1* conn = + new _connection1(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1); + + it = itNext; + } + } + + void operator()(arg1_type a1) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1); + + it = itNext; + } + } + }; + + template + class signal2 : public _signal_base2 + { + public: + typedef _signal_base2 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal2() + { + ; + } + + signal2(const signal2& s) + : _signal_base2(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type)) + { + lock_block lock(this); + _connection2* conn = new + _connection2(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2); + + it = itNext; + } + } + }; + + template + class signal3 : public _signal_base3 + { + public: + typedef _signal_base3 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal3() + { + ; + } + + signal3(const signal3& s) + : _signal_base3(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type)) + { + lock_block lock(this); + _connection3* conn = + new _connection3(pclass, + pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3); + + it = itNext; + } + } + }; + + template + class signal4 : public _signal_base4 + { + public: + typedef _signal_base4 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal4() + { + ; + } + + signal4(const signal4& s) + : _signal_base4(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type)) + { + lock_block lock(this); + _connection4* + conn = new _connection4(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4); + + it = itNext; + } + } + }; + + template + class signal5 : public _signal_base5 + { + public: + typedef _signal_base5 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal5() + { + ; + } + + signal5(const signal5& s) + : _signal_base5(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type)) + { + lock_block lock(this); + _connection5* conn = new _connection5(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5); + + it = itNext; + } + } + }; + + + template + class signal6 : public _signal_base6 + { + public: + typedef _signal_base6 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal6() + { + ; + } + + signal6(const signal6& s) + : _signal_base6(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type)) + { + lock_block lock(this); + _connection6* conn = + new _connection6(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6); + + it = itNext; + } + } + }; + + template + class signal7 : public _signal_base7 + { + public: + typedef _signal_base7 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal7() + { + ; + } + + signal7(const signal7& s) + : _signal_base7(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, + arg7_type)) + { + lock_block lock(this); + _connection7* conn = + new _connection7(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7); + + it = itNext; + } + } + }; + + template + class signal8 : public _signal_base8 + { + public: + typedef _signal_base8 base; + typedef typename base::connections_list connections_list; + using base::m_connected_slots; + + signal8() + { + ; + } + + signal8(const signal8& s) + : _signal_base8(s) + { + ; + } + + template + void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type, + arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, + arg7_type, arg8_type)) + { + lock_block lock(this); + _connection8* conn = + new _connection8(pclass, pmemfun); + m_connected_slots.push_back(conn); + pclass->signal_connect(this); + } + + void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7, a8); + + it = itNext; + } + } + + void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4, + arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8) + { + lock_block lock(this); + typename connections_list::const_iterator itNext, it = m_connected_slots.begin(); + typename connections_list::const_iterator itEnd = m_connected_slots.end(); + + while(it != itEnd) + { + itNext = it; + ++itNext; + + (*it)->emit(a1, a2, a3, a4, a5, a6, a7, a8); + + it = itNext; + } + } + }; + +}; // namespace sigslot + +#endif // SIGSLOT_H__ + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/socket.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/socket.h new file mode 100644 index 00000000..d4a49d96 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/socket.h @@ -0,0 +1,158 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _socket_h_ +#define _socket_h_ + +#include "talk/base/basictypes.h" +#include "talk/base/socketaddress.h" + +#ifdef POSIX +#include +#include +#include +#include +#include +#endif + +#ifdef WIN32 +#include "talk/base/win32.h" +#endif + +// Rather than converting errors into a private namespace, +// Reuse the POSIX socket api errors. Note this depends on +// Win32 compatibility. + +#ifdef WIN32 +#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 +#undef ENAMETOOLONG // remove errno.h's definition +#define ENAMETOOLONG WSAENAMETOOLONG +#define EHOSTDOWN WSAEHOSTDOWN +#define EHOSTUNREACH WSAEHOSTUNREACH +#undef ENOTEMPTY // remove errno.h's definition +#define ENOTEMPTY WSAENOTEMPTY +#define EPROCLIM WSAEPROCLIM +#define EUSERS WSAEUSERS +#define EDQUOT WSAEDQUOT +#define ESTALE WSAESTALE +#define EREMOTE WSAEREMOTE +#undef EACCES +#define EACCES WSAEACCES +#endif // WIN32 + +#ifdef POSIX +#define INVALID_SOCKET (-1) +#define SOCKET_ERROR (-1) +#define closesocket(s) close(s) +#endif // POSIX + +namespace cricket { + +inline bool IsBlockingError(int e) { + return (e == EWOULDBLOCK) || (e == EAGAIN) || (e == EINPROGRESS); +} + +// General interface for the socket implementations of various networks. The +// methods match those of normal UNIX sockets very closely. +class Socket { +public: + virtual ~Socket() {} + + // Returns the address to which the socket is bound. If the socket is not + // bound, then the any-address is returned. + virtual SocketAddress GetLocalAddress() const = 0; + + // Returns the address to which the socket is connected. If the socket is + // not connected, then the any-address is returned. + virtual SocketAddress GetRemoteAddress() const = 0; + + virtual int Bind(const SocketAddress& addr) = 0; + virtual int Connect(const SocketAddress& addr) = 0; + virtual int Send(const void *pv, size_t cb) = 0; + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr) = 0; + virtual int Recv(void *pv, size_t cb) = 0; + virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr) = 0; + virtual int Listen(int backlog) = 0; + virtual Socket *Accept(SocketAddress *paddr) = 0; + virtual int Close() = 0; + virtual int GetError() const = 0; + virtual void SetError(int error) = 0; + inline bool IsBlocking() const { return IsBlockingError(GetError()); } + + enum ConnState { + CS_CLOSED, + CS_CONNECTING, + CS_CONNECTED + }; + virtual ConnState GetState() const = 0; + + // Fills in the given uint16 with the current estimate of the MTU along the + // path to the address to which this socket is connected. + virtual int EstimateMTU(uint16* mtu) = 0; + + enum Option { + OPT_DONTFRAGMENT + }; + virtual int SetOption(Option opt, int value) = 0; + +protected: + Socket() {} + +private: + DISALLOW_EVIL_CONSTRUCTORS(Socket); +}; + +} // namespace cricket + +#endif // _socket_h_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/socketadapters.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketadapters.cc new file mode 100644 index 00000000..049e923c --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketadapters.cc @@ -0,0 +1,1130 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif + +#include + +#ifdef WIN32 +#include +#include +#define _WINSOCKAPI_ +#include +#include // HTTP_STATUS_PROXY_AUTH_REQ +#define SECURITY_WIN32 +#include +#endif + +#include + +#include "talk/base/base64.h" +#include "talk/base/basicdefs.h" +#include "talk/base/bytebuffer.h" +#include "talk/base/common.h" +#include "talk/base/logging.h" +#include "talk/base/md5.h" +#include "talk/base/socketadapters.h" +#include "talk/base/stringutils.h" + +#include + + +#ifdef WIN32 +#include "talk/base/sec_buffer.h" +#endif // WIN32 + +namespace cricket { + +#ifdef WIN32 +extern const ConstantLabel SECURITY_ERRORS[]; +#endif + +BufferedReadAdapter::BufferedReadAdapter(AsyncSocket* socket, size_t buffer_size) + : AsyncSocketAdapter(socket), buffer_size_(buffer_size), data_len_(0), buffering_(false) { + buffer_ = new char[buffer_size_]; +} + +BufferedReadAdapter::~BufferedReadAdapter() { + delete [] buffer_; +} + +int BufferedReadAdapter::Send(const void *pv, size_t cb) { + if (buffering_) { + // TODO: Spoof error better; Signal Writeable + socket_->SetError(EWOULDBLOCK); + return -1; + } + return AsyncSocketAdapter::Send(pv, cb); +} + +int BufferedReadAdapter::Recv(void *pv, size_t cb) { + if (buffering_) { + socket_->SetError(EWOULDBLOCK); + return -1; + } + + size_t read = 0; + + if (data_len_) { + read = _min(cb, data_len_); + memcpy(pv, buffer_, read); + data_len_ -= read; + if (data_len_ > 0) { + memmove(buffer_, buffer_ + read, data_len_); + } + pv = static_cast(pv) + read; + cb -= read; + } + + // FIX: If cb == 0, we won't generate another read event + + int res = AsyncSocketAdapter::Recv(pv, cb); + if (res < 0) + return res; + + return res + static_cast(read); +} + +void BufferedReadAdapter::BufferInput(bool on) { + buffering_ = on; +} + +void BufferedReadAdapter::OnReadEvent(AsyncSocket * socket) { + assert(socket == socket_); + + if (!buffering_) { + AsyncSocketAdapter::OnReadEvent(socket); + return; + } + + if (data_len_ >= buffer_size_) { + LOG(INFO) << "Input buffer overflow"; + assert(false); + data_len_ = 0; + } + + int len = socket_->Recv(buffer_ + data_len_, buffer_size_ - data_len_); + if (len < 0) { + // TODO: Do something better like forwarding the error to the user. + LOG(INFO) << "Recv: " << errno << " " << std::strerror(errno); + return; + } + + data_len_ += len; + + ProcessInput(buffer_, data_len_); +} + +/////////////////////////////////////////////////////////////////////////////// + +const uint8 SSL_SERVER_HELLO[] = { + 22,3,1,0,74,2,0,0,70,3,1,66,133,69,167,39,169,93,160, + 179,197,231,83,218,72,43,63,198,90,202,137,193,88,82, + 161,120,60,91,23,70,0,133,63,32,14,211,6,114,91,91, + 27,95,21,172,19,249,136,83,157,155,232,61,123,12,48, + 50,110,56,77,162,117,87,65,108,52,92,0,4,0 +}; + +const char SSL_CLIENT_HELLO[] = { + -128,70,1,3,1,0,45,0,0,0,16,1,0,-128,3,0,-128,7,0,-64,6,0,64,2,0, + -128,4,0,-128,0,0,4,0,-2,-1,0,0,10,0,-2,-2,0,0,9,0,0,100,0,0,98,0, + 0,3,0,0,6,31,23,12,-90,47,0,120,-4,70,85,46,-79,-125,57,-15,-22 +}; + +AsyncSSLSocket::AsyncSSLSocket(AsyncSocket* socket) : BufferedReadAdapter(socket, 1024) { +} + +int AsyncSSLSocket::Connect(const SocketAddress& addr) { + // Begin buffering before we connect, so that there isn't a race condition between + // potential senders and receiving the OnConnectEvent signal + BufferInput(true); + return BufferedReadAdapter::Connect(addr); +} + +void AsyncSSLSocket::OnConnectEvent(AsyncSocket * socket) { + assert(socket == socket_); + + // TODO: we could buffer output too... + int res = DirectSend(SSL_CLIENT_HELLO, sizeof(SSL_CLIENT_HELLO)); + assert(res == sizeof(SSL_CLIENT_HELLO)); +} + +void AsyncSSLSocket::ProcessInput(char * data, size_t& len) { + if (len < sizeof(SSL_SERVER_HELLO)) + return; + + if (memcmp(SSL_SERVER_HELLO, data, sizeof(SSL_SERVER_HELLO)) != 0) { + Close(); + SignalCloseEvent(this, 0); // TODO: error code? + return; + } + + len -= sizeof(SSL_SERVER_HELLO); + if (len > 0) { + memmove(data, data + sizeof(SSL_SERVER_HELLO), len); + } + + bool remainder = (len > 0); + BufferInput(false); + SignalConnectEvent(this); + + // FIX: if SignalConnect causes the socket to be destroyed, we are in trouble + if (remainder) + SignalReadEvent(this); +} + +/////////////////////////////////////////////////////////////////////////////// + +#define TEST_DIGEST 0 +#if TEST_DIGEST +/* +const char * const DIGEST_CHALLENGE = + "Digest realm=\"testrealm@host.com\"," + " qop=\"auth,auth-int\"," + " nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\"," + " opaque=\"5ccc069c403ebaf9f0171e9517f40e41\""; +const char * const DIGEST_METHOD = "GET"; +const char * const DIGEST_URI = + "/dir/index.html";; +const char * const DIGEST_CNONCE = + "0a4f113b"; +const char * const DIGEST_RESPONSE = + "6629fae49393a05397450978507c4ef1"; +//user_ = "Mufasa"; +//pass_ = "Circle Of Life"; +*/ +const char * const DIGEST_CHALLENGE = + "Digest realm=\"Squid proxy-caching web server\"," + " nonce=\"Nny4QuC5PwiSDixJ\"," + " qop=\"auth\"," + " stale=false"; +const char * const DIGEST_URI = + "/"; +const char * const DIGEST_CNONCE = + "6501d58e9a21cee1e7b5fec894ded024"; +const char * const DIGEST_RESPONSE = + "edffcb0829e755838b073a4a42de06bc"; +#endif + +static std::string MD5(const std::string& data) { + MD5_CTX ctx; + MD5Init(&ctx); + MD5Update(&ctx, const_cast(reinterpret_cast(data.data())), static_cast(data.size())); + unsigned char digest[16]; + MD5Final(digest, &ctx); + std::string hex_digest; + const char HEX[] = "0123456789abcdef"; + for (int i=0; i<16; ++i) { + hex_digest += HEX[digest[i] >> 4]; + hex_digest += HEX[digest[i] & 0xf]; + } + return hex_digest; +} + +static std::string Quote(const std::string& str) { + std::string result; + result.push_back('"'); + for (size_t i=0; i args; + ParseAuth(challenge, len, auth_method, args); + + if (context && (context->auth_method != auth_method)) + return AR_IGNORE; + + // BASIC + if (stricmp(auth_method.c_str(), "basic") == 0) { + if (context) + return AR_CREDENTIALS; // Bad credentials + if (username.empty()) + return AR_CREDENTIALS; // Missing credentials + + context = new AuthContext(auth_method); + + // TODO: convert sensitive to a secure buffer that gets securely deleted + //std::string decoded = username + ":" + password; + size_t len = username.size() + password.GetLength() + 2; + char * sensitive = new char[len]; + size_t pos = strcpyn(sensitive, len, username.data(), username.size()); + pos += strcpyn(sensitive + pos, len - pos, ":"); + password.CopyTo(sensitive + pos, true); + + response = auth_method; + response.append(" "); + // TODO: create a sensitive-source version of Base64::encode + response.append(Base64::encode(sensitive)); + memset(sensitive, 0, len); + delete [] sensitive; + return AR_RESPONSE; + } + + // DIGEST + if (stricmp(auth_method.c_str(), "digest") == 0) { + if (context) + return AR_CREDENTIALS; // Bad credentials + if (username.empty()) + return AR_CREDENTIALS; // Missing credentials + + context = new AuthContext(auth_method); + + std::string cnonce, ncount; +#if TEST_DIGEST + method = DIGEST_METHOD; + uri = DIGEST_URI; + cnonce = DIGEST_CNONCE; +#else + char buffer[256]; + sprintf(buffer, "%d", time(0)); + cnonce = MD5(buffer); +#endif + ncount = "00000001"; + + // TODO: convert sensitive to be secure buffer + //std::string A1 = username + ":" + args["realm"] + ":" + password; + size_t len = username.size() + args["realm"].size() + password.GetLength() + 3; + char * sensitive = new char[len]; // A1 + size_t pos = strcpyn(sensitive, len, username.data(), username.size()); + pos += strcpyn(sensitive + pos, len - pos, ":"); + pos += strcpyn(sensitive + pos, len - pos, args["realm"].c_str()); + pos += strcpyn(sensitive + pos, len - pos, ":"); + password.CopyTo(sensitive + pos, true); + + std::string A2 = method + ":" + uri; + std::string middle; + if (args.find("qop") != args.end()) { + args["qop"] = "auth"; + middle = args["nonce"] + ":" + ncount + ":" + cnonce + ":" + args["qop"]; + } else { + middle = args["nonce"]; + } + std::string HA1 = MD5(sensitive); + memset(sensitive, 0, len); + delete [] sensitive; + std::string HA2 = MD5(A2); + std::string dig_response = MD5(HA1 + ":" + middle + ":" + HA2); + +#if TEST_DIGEST + assert(strcmp(dig_response.c_str(), DIGEST_RESPONSE) == 0); +#endif + + std::stringstream ss; + ss << auth_method; + ss << " username=" << Quote(username); + ss << ", realm=" << Quote(args["realm"]); + ss << ", nonce=" << Quote(args["nonce"]); + ss << ", uri=" << Quote(uri); + if (args.find("qop") != args.end()) { + ss << ", qop=" << args["qop"]; + ss << ", nc=" << ncount; + ss << ", cnonce=" << Quote(cnonce); + } + ss << ", response=\"" << dig_response << "\""; + if (args.find("opaque") != args.end()) { + ss << ", opaque=" << Quote(args["opaque"]); + } + response = ss.str(); + return AR_RESPONSE; + } + +#ifdef WIN32 +#if 1 + bool want_negotiate = (stricmp(auth_method.c_str(), "negotiate") == 0); + bool want_ntlm = (stricmp(auth_method.c_str(), "ntlm") == 0); + // SPNEGO & NTLM + if (want_negotiate || want_ntlm) { + const size_t MAX_MESSAGE = 12000, MAX_SPN = 256; + char out_buf[MAX_MESSAGE], spn[MAX_SPN]; + +#if 0 // Requires funky windows versions + DWORD len = MAX_SPN; + if (DsMakeSpn("HTTP", server.IPAsString().c_str(), NULL, server.port(), 0, &len, spn) != ERROR_SUCCESS) { + LOG(WARNING) << "AsyncHttpsProxySocket::Authenticate(Negotiate) - DsMakeSpn failed"; + return AR_IGNORE; + } +#else + sprintfn(spn, MAX_SPN, "HTTP/%s", server.ToString().c_str()); +#endif + + SecBuffer out_sec; + out_sec.pvBuffer = out_buf; + out_sec.cbBuffer = sizeof(out_buf); + out_sec.BufferType = SECBUFFER_TOKEN; + + SecBufferDesc out_buf_desc; + out_buf_desc.ulVersion = 0; + out_buf_desc.cBuffers = 1; + out_buf_desc.pBuffers = &out_sec; + + const ULONG NEG_FLAGS_DEFAULT = + //ISC_REQ_ALLOCATE_MEMORY + ISC_REQ_CONFIDENTIALITY + //| ISC_REQ_EXTENDED_ERROR + //| ISC_REQ_INTEGRITY + | ISC_REQ_REPLAY_DETECT + | ISC_REQ_SEQUENCE_DETECT + //| ISC_REQ_STREAM + //| ISC_REQ_USE_SUPPLIED_CREDS + ; + + TimeStamp lifetime; + SECURITY_STATUS ret = S_OK; + ULONG ret_flags = 0, flags = NEG_FLAGS_DEFAULT; + + bool specify_credentials = !username.empty(); + size_t steps = 0; + + //uint32 now = cricket::Time(); + + NegotiateAuthContext * neg = static_cast(context); + if (neg) { + const size_t max_steps = 10; + if (++neg->steps >= max_steps) { + LOG(WARNING) << "AsyncHttpsProxySocket::Authenticate(Negotiate) too many retries"; + return AR_ERROR; + } + steps = neg->steps; + + std::string decoded_challenge = Base64::decode(args[""]); + if (!decoded_challenge.empty()) { + SecBuffer in_sec; + in_sec.pvBuffer = const_cast(decoded_challenge.data()); + in_sec.cbBuffer = static_cast(decoded_challenge.size()); + in_sec.BufferType = SECBUFFER_TOKEN; + + SecBufferDesc in_buf_desc; + in_buf_desc.ulVersion = 0; + in_buf_desc.cBuffers = 1; + in_buf_desc.pBuffers = &in_sec; + + ret = InitializeSecurityContextA(&neg->cred, &neg->ctx, spn, flags, 0, SECURITY_NATIVE_DREP, &in_buf_desc, 0, &neg->ctx, &out_buf_desc, &ret_flags, &lifetime); + //LOG(INFO) << "$$$ InitializeSecurityContext @ " << cricket::TimeDiff(cricket::Time(), now); + if (FAILED(ret)) { + LOG(LS_ERROR) << "InitializeSecurityContext returned: " + << ErrorName(ret, SECURITY_ERRORS); + return AR_ERROR; + } + } else if (neg->specified_credentials) { + // Try again with default credentials + specify_credentials = false; + delete context; + context = neg = 0; + } else { + return AR_CREDENTIALS; + } + } + + if (!neg) { + unsigned char userbuf[256], passbuf[256], domainbuf[16]; + SEC_WINNT_AUTH_IDENTITY_A auth_id, * pauth_id = 0; + if (specify_credentials) { + memset(&auth_id, 0, sizeof(auth_id)); + size_t len = password.GetLength()+1; + char * sensitive = new char[len]; + password.CopyTo(sensitive, true); + std::string::size_type pos = username.find('\\'); + if (pos == std::string::npos) { + auth_id.UserLength = static_cast( + _min(sizeof(userbuf) - 1, username.size())); + memcpy(userbuf, username.c_str(), auth_id.UserLength); + userbuf[auth_id.UserLength] = 0; + auth_id.DomainLength = 0; + domainbuf[auth_id.DomainLength] = 0; + auth_id.PasswordLength = static_cast( + _min(sizeof(passbuf) - 1, password.GetLength())); + memcpy(passbuf, sensitive, auth_id.PasswordLength); + passbuf[auth_id.PasswordLength] = 0; + } else { + auth_id.UserLength = static_cast( + _min(sizeof(userbuf) - 1, username.size() - pos - 1)); + memcpy(userbuf, username.c_str() + pos + 1, auth_id.UserLength); + userbuf[auth_id.UserLength] = 0; + auth_id.DomainLength = static_cast( + _min(sizeof(domainbuf) - 1, pos)); + memcpy(domainbuf, username.c_str(), auth_id.DomainLength); + domainbuf[auth_id.DomainLength] = 0; + auth_id.PasswordLength = static_cast( + _min(sizeof(passbuf) - 1, password.GetLength())); + memcpy(passbuf, sensitive, auth_id.PasswordLength); + passbuf[auth_id.PasswordLength] = 0; + } + memset(sensitive, 0, len); + delete [] sensitive; + auth_id.User = userbuf; + auth_id.Domain = domainbuf; + auth_id.Password = passbuf; + auth_id.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI; + pauth_id = &auth_id; + LOG(LS_VERBOSE) << "Negotiate protocol: Using specified credentials"; + } else { + LOG(LS_VERBOSE) << "Negotiate protocol: Using default credentials"; + } + + CredHandle cred; + ret = AcquireCredentialsHandleA(0, want_negotiate ? NEGOSSP_NAME_A : NTLMSP_NAME_A, SECPKG_CRED_OUTBOUND, 0, pauth_id, 0, 0, &cred, &lifetime); + //LOG(INFO) << "$$$ AcquireCredentialsHandle @ " << cricket::TimeDiff(cricket::Time(), now); + if (ret != SEC_E_OK) { + LOG(LS_ERROR) << "AcquireCredentialsHandle error: " + << ErrorName(ret, SECURITY_ERRORS); + return AR_IGNORE; + } + + //CSecBufferBundle<5, CSecBufferBase::FreeSSPI> sb_out; + + CtxtHandle ctx; + ret = InitializeSecurityContextA(&cred, 0, spn, flags, 0, SECURITY_NATIVE_DREP, 0, 0, &ctx, &out_buf_desc, &ret_flags, &lifetime); + //LOG(INFO) << "$$$ InitializeSecurityContext @ " << cricket::TimeDiff(cricket::Time(), now); + if (FAILED(ret)) { + LOG(LS_ERROR) << "InitializeSecurityContext returned: " + << ErrorName(ret, SECURITY_ERRORS); + FreeCredentialsHandle(&cred); + return AR_IGNORE; + } + + assert(!context); + context = neg = new NegotiateAuthContext(auth_method, cred, ctx); + neg->specified_credentials = specify_credentials; + neg->steps = steps; + } + + if ((ret == SEC_I_COMPLETE_NEEDED) || (ret == SEC_I_COMPLETE_AND_CONTINUE)) { + ret = CompleteAuthToken(&neg->ctx, &out_buf_desc); + //LOG(INFO) << "$$$ CompleteAuthToken @ " << cricket::TimeDiff(cricket::Time(), now); + LOG(LS_VERBOSE) << "CompleteAuthToken returned: " + << ErrorName(ret, SECURITY_ERRORS); + if (FAILED(ret)) { + return AR_ERROR; + } + } + + //LOG(INFO) << "$$$ NEGOTIATE took " << cricket::TimeDiff(cricket::Time(), now) << "ms"; + + std::string decoded(out_buf, out_buf + out_sec.cbBuffer); + response = auth_method; + response.append(" "); + response.append(Base64::encode(decoded)); + return AR_RESPONSE; + } +#endif +#endif // WIN32 + + return AR_IGNORE; +} + +inline bool end_of_name(size_t pos, size_t len, const char * data) { + if (pos >= len) + return true; + if (isspace(data[pos])) + return true; + // The reason for this complexity is that some non-compliant auth schemes (like Negotiate) + // use base64 tokens in the challenge instead of name=value. This could confuse us when the + // base64 ends in equal signs. + if ((pos+1 < len) && (data[pos] == '=') && !isspace(data[pos+1]) && (data[pos+1] != '=')) + return true; + return false; +} + +void AsyncHttpsProxySocket::ParseAuth(const char * data, size_t len, std::string& method, std::map& args) { + size_t pos = 0; + while ((pos < len) && isspace(data[pos])) ++pos; + size_t start = pos; + while ((pos < len) && !isspace(data[pos])) ++pos; + method.assign(data + start, data + pos); + while (pos < len) { + while ((pos < len) && isspace(data[pos])) ++pos; + if (pos >= len) + return; + + start = pos; + while (!end_of_name(pos, len, data)) ++pos; + //while ((pos < len) && !isspace(data[pos]) && (data[pos] != '=')) ++pos; + std::string name(data + start, data + pos), value; + + if ((pos < len) && (data[pos] == '=')) { + ++pos; // Skip '=' + // Check if quoted value + if ((pos < len) && (data[pos] == '"')) { + while (++pos < len) { + if (data[pos] == '"') { + ++pos; + break; + } + if ((data[pos] == '\\') && (pos + 1 < len)) + ++pos; + value.append(1, data[pos]); + } + } else { + while ((pos < len) && !isspace(data[pos]) && (data[pos] != ',')) + value.append(1, data[pos++]); + } + } else { + value = name; + name.clear(); + } + + args.insert(std::make_pair(name, value)); + if ((pos < len) && (data[pos] == ',')) ++pos; // Skip ',' + } +} + +AsyncHttpsProxySocket::AsyncHttpsProxySocket(AsyncSocket* socket, const SocketAddress& proxy, + const std::string& username, const buzz::XmppPassword& password) + : BufferedReadAdapter(socket, 1024), proxy_(proxy), user_(username), pass_(password), + state_(PS_ERROR), context_(0) { +} + +AsyncHttpsProxySocket::~AsyncHttpsProxySocket() { + delete context_; +} + +int AsyncHttpsProxySocket::Connect(const SocketAddress& addr) { + LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::Connect(" << proxy_.ToString() << ")"; + dest_ = addr; + if (dest_.port() != 80) { + BufferInput(true); + } + return BufferedReadAdapter::Connect(proxy_); +} + +SocketAddress AsyncHttpsProxySocket::GetRemoteAddress() const { + return dest_; +} + +int AsyncHttpsProxySocket::Close() { + headers_.clear(); + state_ = PS_ERROR; + delete context_; + context_ = 0; + return BufferedReadAdapter::Close(); +} + +void AsyncHttpsProxySocket::OnConnectEvent(AsyncSocket * socket) { + LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::OnConnectEvent"; + // TODO: Decide whether tunneling or not should be explicitly set, + // or indicated by destination port (as below) + if (dest_.port() == 80) { + state_ = PS_TUNNEL; + BufferedReadAdapter::OnConnectEvent(socket); + return; + } + SendRequest(); +} + +void AsyncHttpsProxySocket::OnCloseEvent(AsyncSocket * socket, int err) { + LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::OnCloseEvent(" << err << ")"; + if ((state_ == PS_WAIT_CLOSE) && (err == 0)) { + state_ = PS_ERROR; + Connect(dest_); + } else { + BufferedReadAdapter::OnCloseEvent(socket, err); + } +} + +void AsyncHttpsProxySocket::ProcessInput(char * data, size_t& len) { + size_t start = 0; + for (size_t pos = start; (state_ < PS_TUNNEL) && (pos < len); ) { + if (state_ == PS_SKIP_BODY) { + size_t consume = _min(len - pos, content_length_); + pos += consume; + start = pos; + content_length_ -= consume; + if (content_length_ == 0) { + EndResponse(); + } + continue; + } + + if (data[pos++] != '\n') + continue; + + size_t len = pos - start - 1; + if ((len > 0) && (data[start + len - 1] == '\r')) + --len; + + data[start + len] = 0; + ProcessLine(data + start, len); + start = pos; + } + + len -= start; + if (len > 0) { + memmove(data, data + start, len); + } + + if (state_ != PS_TUNNEL) + return; + + bool remainder = (len > 0); + BufferInput(false); + SignalConnectEvent(this); + + // FIX: if SignalConnect causes the socket to be destroyed, we are in trouble + if (remainder) + SignalReadEvent(this); // TODO: signal this?? +} + +void AsyncHttpsProxySocket::SendRequest() { + std::stringstream ss; + ss << "CONNECT " << dest_.ToString() << " HTTP/1.0\r\n"; + ss << "User-Agent: " USERAGENT_STRING "\r\n"; + ss << "Host: " << dest_.IPAsString() << "\r\n"; + ss << "Content-Length: 0\r\n"; + ss << "Proxy-Connection: Keep-Alive\r\n"; + ss << headers_; + ss << "\r\n"; + std::string str = ss.str(); + DirectSend(str.c_str(), str.size()); + state_ = PS_LEADER; + expect_close_ = true; + content_length_ = 0; + headers_.clear(); + + LOG(LS_VERBOSE) << "AsyncHttpsProxySocket >> " << str; +} + +void AsyncHttpsProxySocket::ProcessLine(char * data, size_t len) { + LOG(LS_VERBOSE) << "AsyncHttpsProxySocket << " << data; + + if (len == 0) { + if (state_ == PS_TUNNEL_HEADERS) { + state_ = PS_TUNNEL; + } else if (state_ == PS_ERROR_HEADERS) { + Error(defer_error_); + return; + } else if (state_ == PS_SKIP_HEADERS) { + if (content_length_) { + state_ = PS_SKIP_BODY; + } else { + EndResponse(); + return; + } + } else { + static bool report = false; + if (!unknown_mechanisms_.empty() && !report) { + report = true; + std::string msg( + "Unable to connect to the Google Talk service due to an incompatibility " + "with your proxy.\r\nPlease help us resolve this issue by submitting the " + "following information to us using our technical issue submission form " + "at:\r\n\r\n" + "http://www.google.com/support/talk/bin/request.py\r\n\r\n" + "We apologize for the inconvenience.\r\n\r\n" + "Information to submit to Google: " + ); + //std::string msg("Please report the following information to foo@bar.com:\r\nUnknown methods: "); + msg.append(unknown_mechanisms_); +#ifdef WIN32 + MessageBoxA(0, msg.c_str(), "Oops!", MB_OK); +#endif +#ifdef POSIX + //TODO: Raise a signal or something so the UI can be separated. + LOG(LS_ERROR) << "Oops!\n\n" << msg; +#endif + } + // Unexpected end of headers + Error(0); + return; + } + } else if (state_ == PS_LEADER) { + uint32 code; + if (sscanf(data, "HTTP/%*lu.%*lu %lu", &code) != 1) { + Error(0); + return; + } + switch (code) { + case 200: + // connection good! + state_ = PS_TUNNEL_HEADERS; + return; +#if defined(HTTP_STATUS_PROXY_AUTH_REQ) && (HTTP_STATUS_PROXY_AUTH_REQ != 407) +#error Wrong code for HTTP_STATUS_PROXY_AUTH_REQ +#endif + case 407: // HTTP_STATUS_PROXY_AUTH_REQ + state_ = PS_AUTHENTICATE; + return; + default: + defer_error_ = 0; + state_ = PS_ERROR_HEADERS; + return; + } + } else if ((state_ == PS_AUTHENTICATE) && (strnicmp(data, "Proxy-Authenticate:", 19) == 0)) { + std::string response, auth_method; + switch (Authenticate(data + 19, len - 19, proxy_, "CONNECT", "/", user_, pass_, context_, response, auth_method)) { + case AR_IGNORE: + LOG(LS_VERBOSE) << "Ignoring Proxy-Authenticate: " << auth_method; + if (!unknown_mechanisms_.empty()) + unknown_mechanisms_.append(", "); + unknown_mechanisms_.append(auth_method); + break; + case AR_RESPONSE: + headers_ = "Proxy-Authorization: "; + headers_.append(response); + headers_.append("\r\n"); + state_ = PS_SKIP_HEADERS; + unknown_mechanisms_.clear(); + break; + case AR_CREDENTIALS: + defer_error_ = EACCES; + state_ = PS_ERROR_HEADERS; + unknown_mechanisms_.clear(); + break; + case AR_ERROR: + defer_error_ = 0; + state_ = PS_ERROR_HEADERS; + unknown_mechanisms_.clear(); + break; + } + } else if (strnicmp(data, "Content-Length:", 15) == 0) { + content_length_ = strtoul(data + 15, 0, 0); + } else if (strnicmp(data, "Proxy-Connection: Keep-Alive", 28) == 0) { + expect_close_ = false; + /* + } else if (strnicmp(data, "Connection: close", 17) == 0) { + expect_close_ = true; + */ + } +} + +void AsyncHttpsProxySocket::EndResponse() { + if (!expect_close_) { + SendRequest(); + return; + } + + // No point in waiting for the server to close... let's close now + // TODO: Refactor out PS_WAIT_CLOSE + state_ = PS_WAIT_CLOSE; + BufferedReadAdapter::Close(); + OnCloseEvent(this, 0); +} + +void AsyncHttpsProxySocket::Error(int error) { + BufferInput(false); + Close(); + SetError(error); + SignalCloseEvent(this, error); +} + +/////////////////////////////////////////////////////////////////////////////// + +AsyncSocksProxySocket::AsyncSocksProxySocket(AsyncSocket* socket, const SocketAddress& proxy, + const std::string& username, const buzz::XmppPassword& password) + : BufferedReadAdapter(socket, 1024), proxy_(proxy), user_(username), pass_(password), + state_(SS_ERROR) { +} + +int AsyncSocksProxySocket::Connect(const SocketAddress& addr) { + dest_ = addr; + BufferInput(true); + return BufferedReadAdapter::Connect(proxy_); +} + +SocketAddress AsyncSocksProxySocket::GetRemoteAddress() const { + return dest_; +} + +void AsyncSocksProxySocket::OnConnectEvent(AsyncSocket * socket) { + SendHello(); +} + +void AsyncSocksProxySocket::ProcessInput(char * data, size_t& len) { + assert(state_ < SS_TUNNEL); + + ByteBuffer response(data, len); + + if (state_ == SS_HELLO) { + uint8 ver, method; + if (!response.ReadUInt8(ver) || + !response.ReadUInt8(method)) + return; + + if (ver != 5) { + Error(0); + return; + } + + if (method == 0) { + SendConnect(); + } else if (method == 2) { + SendAuth(); + } else { + Error(0); + return; + } + } else if (state_ == SS_AUTH) { + uint8 ver, status; + if (!response.ReadUInt8(ver) || + !response.ReadUInt8(status)) + return; + + if ((ver != 1) || (status != 0)) { + Error(EACCES); + return; + } + + SendConnect(); + } else if (state_ == SS_CONNECT) { + uint8 ver, rep, rsv, atyp; + if (!response.ReadUInt8(ver) || + !response.ReadUInt8(rep) || + !response.ReadUInt8(rsv) || + !response.ReadUInt8(atyp)) + return; + + if ((ver != 5) || (rep != 0)) { + Error(0); + return; + } + + uint16 port; + if (atyp == 1) { + uint32 addr; + if (!response.ReadUInt32(addr) || + !response.ReadUInt16(port)) + return; + LOG(LS_VERBOSE) << "Bound on " << addr << ":" << port; + } else if (atyp == 3) { + uint8 len; + std::string addr; + if (!response.ReadUInt8(len) || + !response.ReadString(addr, len) || + !response.ReadUInt16(port)) + return; + LOG(LS_VERBOSE) << "Bound on " << addr << ":" << port; + } else if (atyp == 4) { + std::string addr; + if (!response.ReadString(addr, 16) || + !response.ReadUInt16(port)) + return; + LOG(LS_VERBOSE) << "Bound on :" << port; + } else { + Error(0); + return; + } + + state_ = SS_TUNNEL; + } + + // Consume parsed data + len = response.Length(); + memcpy(data, response.Data(), len); + + if (state_ != SS_TUNNEL) + return; + + bool remainder = (len > 0); + BufferInput(false); + SignalConnectEvent(this); + + // FIX: if SignalConnect causes the socket to be destroyed, we are in trouble + if (remainder) + SignalReadEvent(this); // TODO: signal this?? +} + +void AsyncSocksProxySocket::SendHello() { + ByteBuffer request; + request.WriteUInt8(5); // Socks Version + if (user_.empty()) { + request.WriteUInt8(1); // Authentication Mechanisms + request.WriteUInt8(0); // No authentication + } else { + request.WriteUInt8(2); // Authentication Mechanisms + request.WriteUInt8(0); // No authentication + request.WriteUInt8(2); // Username/Password + } + DirectSend(request.Data(), request.Length()); + state_ = SS_HELLO; +} + +void AsyncSocksProxySocket::SendAuth() { + ByteBuffer request; + request.WriteUInt8(1); // Negotiation Version + request.WriteUInt8(static_cast(user_.size())); + request.WriteString(user_); // Username + request.WriteUInt8(static_cast(pass_.GetLength())); + size_t len = pass_.GetLength() + 1; + char * sensitive = new char[len]; + pass_.CopyTo(sensitive, true); + request.WriteString(sensitive); // Password + memset(sensitive, 0, len); + delete [] sensitive; + DirectSend(request.Data(), request.Length()); + state_ = SS_AUTH; +} + +void AsyncSocksProxySocket::SendConnect() { + ByteBuffer request; + request.WriteUInt8(5); // Socks Version + request.WriteUInt8(1); // CONNECT + request.WriteUInt8(0); // Reserved + if (dest_.IsUnresolved()) { + std::string hostname = dest_.IPAsString(); + request.WriteUInt8(3); // DOMAINNAME + request.WriteUInt8(static_cast(hostname.size())); + request.WriteString(hostname); // Destination Hostname + } else { + request.WriteUInt8(1); // IPV4 + request.WriteUInt32(dest_.ip()); // Destination IP + } + request.WriteUInt16(dest_.port()); // Destination Port + DirectSend(request.Data(), request.Length()); + state_ = SS_CONNECT; +} + +void AsyncSocksProxySocket::Error(int error) { + state_ = SS_ERROR; + BufferInput(false); + Close(); + SetError(EACCES); + SignalCloseEvent(this, error); +} + +/////////////////////////////////////////////////////////////////////////////// + +LoggingAdapter::LoggingAdapter(AsyncSocket* socket, LoggingSeverity level, + const char * label) + : AsyncSocketAdapter(socket), level_(level) +{ + label_.append("["); + label_.append(label); + label_.append("]"); +} + +int +LoggingAdapter::Send(const void *pv, size_t cb) { + int res = AsyncSocketAdapter::Send(pv, cb); + if (res > 0) + LogMultiline(false, static_cast(pv), res); + return res; +} + +int +LoggingAdapter::SendTo(const void *pv, size_t cb, const SocketAddress& addr) { + int res = AsyncSocketAdapter::SendTo(pv, cb, addr); + if (res > 0) + LogMultiline(false, static_cast(pv), res); + return res; +} + +int +LoggingAdapter::Recv(void *pv, size_t cb) { + int res = AsyncSocketAdapter::Recv(pv, cb); + if (res > 0) + LogMultiline(true, static_cast(pv), res); + return res; +} + +int +LoggingAdapter::RecvFrom(void *pv, size_t cb, SocketAddress *paddr) { + int res = AsyncSocketAdapter::RecvFrom(pv, cb, paddr); + if (res > 0) + LogMultiline(true, static_cast(pv), res); + return res; +} + +void +LoggingAdapter::OnConnectEvent(AsyncSocket * socket) { + LOG(level_) << label_ << " Connected"; + AsyncSocketAdapter::OnConnectEvent(socket); +} + +void +LoggingAdapter::OnCloseEvent(AsyncSocket * socket, int err) { + LOG(level_) << label_ << " Closed with error: " << err; + AsyncSocketAdapter::OnCloseEvent(socket, err); +} + +void +LoggingAdapter::LogMultiline(bool input, const char * data, size_t len) { + const char * direction = (input ? " << " : " >> "); + std::string str(data, len); + while (!str.empty()) { + std::string::size_type pos = str.find('\n'); + std::string substr = str; + if (pos == std::string::npos) { + substr = str; + str.clear(); + } else if ((pos > 0) && (str[pos-1] == '\r')) { + substr = str.substr(0, pos - 1); + str = str.substr(pos + 1); + } else { + substr = str.substr(0, pos); + str = str.substr(pos + 1); + } + + // Filter out any private data + std::string::size_type pos_private = substr.find("Email"); + if (pos_private == std::string::npos) { + pos_private = substr.find("Passwd"); + } + if (pos_private == std::string::npos) { + LOG(level_) << label_ << direction << substr; + } else { + LOG(level_) << label_ << direction << "## TEXT REMOVED ##"; + } + } +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/socketadapters.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketadapters.h new file mode 100644 index 00000000..1c65aa79 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketadapters.h @@ -0,0 +1,181 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __SOCKETADAPTERS_H__ +#define __SOCKETADAPTERS_H__ + +#include + +#include "talk/base/asyncsocket.h" +#include "talk/base/logging.h" +#include "talk/xmpp/xmpppassword.h" // TODO: move xmpppassword to base + +namespace cricket { + +/////////////////////////////////////////////////////////////////////////////// + +class BufferedReadAdapter : public AsyncSocketAdapter { +public: + BufferedReadAdapter(AsyncSocket* socket, size_t buffer_size); + virtual ~BufferedReadAdapter(); + + virtual int Send(const void *pv, size_t cb); + virtual int Recv(void *pv, size_t cb); + +protected: + int DirectSend(const void *pv, size_t cb) { return AsyncSocketAdapter::Send(pv, cb); } + + void BufferInput(bool on = true); + virtual void ProcessInput(char * data, size_t& len) = 0; + + virtual void OnReadEvent(AsyncSocket * socket); + +private: + char * buffer_; + size_t buffer_size_, data_len_; + bool buffering_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class AsyncSSLSocket : public BufferedReadAdapter { +public: + AsyncSSLSocket(AsyncSocket* socket); + + virtual int Connect(const SocketAddress& addr); + +protected: + virtual void OnConnectEvent(AsyncSocket * socket); + virtual void ProcessInput(char * data, size_t& len); +}; + +/////////////////////////////////////////////////////////////////////////////// + +class AsyncHttpsProxySocket : public BufferedReadAdapter { +public: + AsyncHttpsProxySocket(AsyncSocket* socket, const SocketAddress& proxy, + const std::string& username, const buzz::XmppPassword& password); + virtual ~AsyncHttpsProxySocket(); + + virtual int Connect(const SocketAddress& addr); + virtual SocketAddress GetRemoteAddress() const; + virtual int Close(); + + struct AuthContext { + std::string auth_method; + AuthContext(const std::string& auth) : auth_method(auth) { } + virtual ~AuthContext() { } + }; + + // 'context' is used by this function to record information between calls. + // Start by passing a null pointer, then pass the same pointer each additional + // call. When the authentication attempt is finished, delete the context. + enum AuthResult { AR_RESPONSE, AR_IGNORE, AR_CREDENTIALS, AR_ERROR }; + static AuthResult Authenticate(const char * challenge, size_t len, + const SocketAddress& server, + const std::string& method, const std::string& uri, + const std::string& username, const buzz::XmppPassword& password, + AuthContext *& context, std::string& response, std::string& auth_method); + +protected: + virtual void OnConnectEvent(AsyncSocket * socket); + virtual void OnCloseEvent(AsyncSocket * socket, int err); + virtual void ProcessInput(char * data, size_t& len); + + void SendRequest(); + void ProcessLine(char * data, size_t len); + void EndResponse(); + void Error(int error); + + static void ParseAuth(const char * data, size_t len, std::string& method, std::map& args); + +private: + SocketAddress proxy_, dest_; + std::string user_, headers_; + buzz::XmppPassword pass_; + size_t content_length_; + int defer_error_; + bool expect_close_; + enum ProxyState { PS_LEADER, PS_AUTHENTICATE, PS_SKIP_HEADERS, PS_ERROR_HEADERS, PS_TUNNEL_HEADERS, PS_SKIP_BODY, PS_TUNNEL, PS_WAIT_CLOSE, PS_ERROR } state_; + AuthContext * context_; + std::string unknown_mechanisms_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class AsyncSocksProxySocket : public BufferedReadAdapter { +public: + AsyncSocksProxySocket(AsyncSocket* socket, const SocketAddress& proxy, + const std::string& username, const buzz::XmppPassword& password); + + virtual int Connect(const SocketAddress& addr); + virtual SocketAddress GetRemoteAddress() const; + +protected: + virtual void OnConnectEvent(AsyncSocket * socket); + virtual void ProcessInput(char * data, size_t& len); + + void SendHello(); + void SendConnect(); + void SendAuth(); + void Error(int error); + +private: + SocketAddress proxy_, dest_; + std::string user_; + buzz::XmppPassword pass_; + enum SocksState { SS_HELLO, SS_AUTH, SS_CONNECT, SS_TUNNEL, SS_ERROR } state_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class LoggingAdapter : public AsyncSocketAdapter { +public: + LoggingAdapter(AsyncSocket* socket, LoggingSeverity level, + const char * label); + + virtual int Send(const void *pv, size_t cb); + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr); + virtual int Recv(void *pv, size_t cb); + virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr); + +protected: + virtual void OnConnectEvent(AsyncSocket * socket); + virtual void OnCloseEvent(AsyncSocket * socket, int err); + +private: + void LogMultiline(bool input, const char * data, size_t len); + + LoggingSeverity level_; + std::string label_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace cricket + +#endif // __SOCKETADAPTERS_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddress.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddress.cc new file mode 100644 index 00000000..f0228fbd --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddress.cc @@ -0,0 +1,267 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/base/socketaddress.h" +#include "talk/base/byteorder.h" +#include "talk/base/logging.h" +#include +#include +#include + +#ifdef WIN32 +#undef SetPort +int inet_aton(const char * cp, struct in_addr * inp) { + inp->s_addr = inet_addr(cp); + return (inp->s_addr == INADDR_NONE) ? 0 : 1; +} +#endif // WIN32 + +#ifdef POSIX +#include +#include +#include +#include +#include +#endif + +#ifdef _DEBUG +#define DISABLE_DNS 0 +#else // !_DEBUG +#define DISABLE_DNS 0 +#endif // !_DEBUG + +namespace cricket { + +SocketAddress::SocketAddress() { + Zero(); +} + +SocketAddress::SocketAddress(const std::string& hostname, int port, bool use_dns) { + Zero(); + SetIP(hostname, use_dns); + SetPort(port); +} + +SocketAddress::SocketAddress(uint32 ip, int port) { + Zero(); + SetIP(ip); + SetPort(port); +} + +SocketAddress::SocketAddress(const SocketAddress& addr) { + Zero(); + this->operator=(addr); +} + +void SocketAddress::Zero() { + ip_ = 0; + port_ = 0; +} + +SocketAddress& SocketAddress::operator =(const SocketAddress& addr) { + hostname_ = addr.hostname_; + ip_ = addr.ip_; + port_ = addr.port_; + return *this; +} + +void SocketAddress::SetIP(uint32 ip) { + hostname_.clear(); + ip_ = ip; +} + +bool SocketAddress::SetIP(const std::string& hostname, bool use_dns) { + hostname_ = hostname; + ip_ = 0; + return Resolve(true, use_dns); +} + +void SocketAddress::SetResolvedIP(uint32 ip) { + ip_ = ip; +} + +void SocketAddress::SetPort(int port) { + assert((0 <= port) && (port < 65536)); + port_ = port; +} + +uint32 SocketAddress::ip() const { + return ip_; +} + +uint16 SocketAddress::port() const { + return port_; +} + +std::string SocketAddress::IPAsString() const { + if (!hostname_.empty()) + return hostname_; + return IPToString(ip_); +} + +std::string SocketAddress::PortAsString() const { + std::ostringstream ost; + ost << port_; + return ost.str(); +} + +std::string SocketAddress::ToString() const { + std::ostringstream ost; + ost << IPAsString(); + ost << ":"; + ost << port(); + return ost.str(); +} + +bool SocketAddress::IsAny() const { + return (ip_ == 0); +} + +bool SocketAddress::IsLocalIP() const { + return (ip_ >> 24) == 127; +} + +bool SocketAddress::IsPrivateIP() const { + return ((ip_ >> 24) == 127) || + ((ip_ >> 24) == 10) || + ((ip_ >> 20) == ((172 << 4) | 1)) || + ((ip_ >> 16) == ((192 << 8) | 168)); +} + +bool SocketAddress::IsUnresolved() const { + return IsAny() && !hostname_.empty(); +} + +bool SocketAddress::Resolve(bool force, bool use_dns) { + if (hostname_.empty()) { + // nothing to resolve + } else if (!force && !IsAny()) { + // already resolved + } else if (uint32 ip = StringToIP(hostname_, use_dns)) { + ip_ = ip; + } else { + return false; + } + return true; +} + +bool SocketAddress::operator ==(const SocketAddress& addr) const { + return EqualIPs(addr) && EqualPorts(addr); +} + +bool SocketAddress::operator <(const SocketAddress& addr) const { + if (ip_ < addr.ip_) + return true; + else if (addr.ip_ < ip_) + return false; + + // We only check hostnames if both IPs are zero. This matches EqualIPs() + if (addr.ip_ == 0) { + if (hostname_ < addr.hostname_) + return true; + else if (addr.hostname_ < hostname_) + return false; + } + + return port_ < addr.port_; +} + +bool SocketAddress::EqualIPs(const SocketAddress& addr) const { + return (ip_ == addr.ip_) && ((ip_ != 0) || (hostname_ == addr.hostname_)); +} + +bool SocketAddress::EqualPorts(const SocketAddress& addr) const { + return (port_ == addr.port_); +} + +size_t SocketAddress::Hash() const { + size_t h = 0; + h ^= ip_; + h ^= port_ | (port_ << 16); + return h; +} + +size_t SocketAddress::Size_() const { + return sizeof(ip_) + sizeof(port_); +} + +void SocketAddress::Write_(char* buf, int len) const { + // TODO: Depending on how this is used, we may want/need to write hostname + assert((size_t)len >= Size_()); + reinterpret_cast(buf)[0] = ip_; + buf += sizeof(ip_); + reinterpret_cast(buf)[0] = port_; +} + +void SocketAddress::Read_(const char* buf, int len) { + assert((size_t)len >= Size_()); + ip_ = reinterpret_cast(buf)[0]; + buf += sizeof(ip_); + port_ = reinterpret_cast(buf)[0]; +} + +std::string SocketAddress::IPToString(uint32 ip) { + std::ostringstream ost; + ost << ((ip >> 24) & 0xff); + ost << '.'; + ost << ((ip >> 16) & 0xff); + ost << '.'; + ost << ((ip >> 8) & 0xff); + ost << '.'; + ost << ((ip >> 0) & 0xff); + return ost.str(); +} + +uint32 SocketAddress::StringToIP(const std::string& hostname, bool use_dns) { + uint32 ip = 0; + in_addr addr; + if (inet_aton(hostname.c_str(), &addr) != 0) { + ip = NetworkToHost32(addr.s_addr); + } else if (use_dns) { + // Note: this is here so we can spot spurious DNS resolutions for a while + LOG(INFO) << "=== DNS RESOLUTION (" << hostname << ") ==="; +#if DISABLE_DNS + LOG(WARNING) << "*** DNS DISABLED ***"; +#if WIN32 + WSASetLastError(WSAHOST_NOT_FOUND); +#endif // WIN32 +#endif // DISABLE_DNS + if (hostent * pHost = gethostbyname(hostname.c_str())) { + ip = NetworkToHost32(*reinterpret_cast(pHost->h_addr_list[0])); + } else { +#if WIN32 + LOG(LS_ERROR) << "gethostbyname error: " << WSAGetLastError(); +#else + LOG(LS_ERROR) << "gethostbyname error: " << strerror(h_errno); +#endif + } + LOG(INFO) << hostname << " resolved to " << IPToString(ip); + } + return ip; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddress.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddress.h new file mode 100644 index 00000000..b8a165d3 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddress.h @@ -0,0 +1,154 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __SOCKETADDRESS_H__ +#define __SOCKETADDRESS_H__ + +#include "talk/base/basictypes.h" +#include +#undef SetPort + +namespace cricket { + +// Records an IP address and port, which are 32 and 16 bit integers, +// respectively, both in host byte-order. +class SocketAddress { +public: + // Creates a missing / unknown address. + SocketAddress(); + + // Creates the address with the given host and port. If use_dns is true, + // the hostname will be immediately resolved to an IP (which may block for + // several seconds if DNS is not available). Alternately, set use_dns to + // false, and then call Resolve() to complete resolution later, or use + // SetResolvedIP to set the IP explictly. + SocketAddress(const std::string& hostname, int port = 0, bool use_dns = true); + + // Creates the address with the given IP and port. + SocketAddress(uint32 ip, int port); + + // Creates a copy of the given address. + SocketAddress(const SocketAddress& addr); + + // Replaces our address with the given one. + SocketAddress& operator =(const SocketAddress& addr); + + // Changes the IP of this address to the given one, and clears the hostname. + void SetIP(uint32 ip); + + // Changes the hostname of this address to the given one. + // Calls Resolve and returns the result. + bool SetIP(const std::string& hostname, bool use_dns = true); + + // Sets the IP address while retaining the hostname. Useful for bypassing + // DNS for a pre-resolved IP. + void SetResolvedIP(uint32 ip); + + // Changes the port of this address to the given one. + void SetPort(int port); + + // Returns the IP address. + uint32 ip() const; + + // Returns the port part of this address. + uint16 port() const; + + // Returns the IP address in dotted form. + std::string IPAsString() const; + + // Returns the port as a string + std::string PortAsString() const; + + // Returns a display version of the IP/port. + std::string ToString() const; + + // Determines whether this represents a missing / any address. + bool IsAny() const; + + // Synomym for missing / any. + bool IsNil() const { return IsAny(); } + + // Determines whether the IP address refers to the local host, i.e. within + // the range 127.0.0.0/8. + bool IsLocalIP() const; + + // Determines whether the IP address is in one of the private ranges: + // 127.0.0.0/8 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12. + bool IsPrivateIP() const; + + // Determines whether the hostname has been resolved to an IP + bool IsUnresolved() const; + + // Attempt to resolve a hostname to IP address. + // Returns false if resolution is required but failed. + // 'force' will cause re-resolution of hostname. + // + bool Resolve(bool force = false, bool use_dns = true); + + // Determines whether this address is identical to the given one. + bool operator ==(const SocketAddress& addr) const; + + // Compares based on IP and then port. + bool operator <(const SocketAddress& addr) const; + + // Determines whether this address has the same IP as the one given. + bool EqualIPs(const SocketAddress& addr) const; + + // Deteremines whether this address has the same port as the one given. + bool EqualPorts(const SocketAddress& addr) const; + + // Hashes this address into a small number. + size_t Hash() const; + + // Returns the size of this address when written. + size_t Size_() const; + + // Writes this address into the given buffer. + void Write_(char* buf, int len) const; + + // Reads this address from the given buffer. + void Read_(const char* buf, int len); + + // Converts the IP address given in compact form into dotted form. + static std::string IPToString(uint32 ip); + + // Converts the IP address given in dotted form into compact form. + // Without 'use_dns', only dotted names (A.B.C.D) are resolved. + static uint32 StringToIP(const std::string& str, bool use_dns = true); + +private: + std::string hostname_; + uint32 ip_; + uint16 port_; + + // Initializes the address to missing / any. + void Zero(); +}; + +} // namespace cricket + +#endif // __SOCKETADDRESS_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddresspair.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddresspair.cc new file mode 100644 index 00000000..2166be09 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddresspair.cc @@ -0,0 +1,58 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/base/socketaddresspair.h" + +namespace cricket { + +SocketAddressPair::SocketAddressPair( + const SocketAddress& src, const SocketAddress& dest) + : src_(src), dest_(dest) { +} + + +bool SocketAddressPair::operator ==(const SocketAddressPair& p) const { + return (src_ == p.src_) && (dest_ == p.dest_); +} + +bool SocketAddressPair::operator <(const SocketAddressPair& p) const { + if (src_ < p.src_) + return true; + if (p.src_ < src_) + return false; + if (dest_ < p.dest_) + return true; + if (p.dest_ < dest_) + return false; + return false; +} + +size_t SocketAddressPair::Hash() const { + return src_.Hash() ^ dest_.Hash(); +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddresspair.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddresspair.h new file mode 100644 index 00000000..098bafdb --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketaddresspair.h @@ -0,0 +1,58 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __SOCKETADDRESSPAIR_H__ +#define __SOCKETADDRESSPAIR_H__ + +#include "talk/base/socketaddress.h" + +namespace cricket { + +// Records a pair (source,destination) of socket addresses. The two addresses +// identify a connection between two machines. (For UDP, this "connection" is +// not maintained explicitly in a socket.) +class SocketAddressPair { +public: + SocketAddressPair() {} + SocketAddressPair(const SocketAddress& srs, const SocketAddress& dest); + + const SocketAddress& source() const { return src_; } + const SocketAddress& destination() const { return dest_; } + + bool operator ==(const SocketAddressPair& r) const; + bool operator <(const SocketAddressPair& r) const; + + size_t Hash() const; + +private: + SocketAddress src_; + SocketAddress dest_; +}; + +} // namespace cricket + +#endif // __SOCKETADDRESSPAIR_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/socketfactory.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketfactory.h new file mode 100644 index 00000000..67386160 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketfactory.h @@ -0,0 +1,50 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __SOCKETFACTORY_H__ +#define __SOCKETFACTORY_H__ + +#include "talk/base/socket.h" +#include "talk/base/asyncsocket.h" + +namespace cricket { + +class SocketFactory { +public: + + // Returns a new socket for blocking communication. The type can be + // SOCK_DGRAM and SOCK_STREAM. + virtual Socket* CreateSocket(int type) = 0; + + // Returns a new socket for nonblocking communication. The type can be + // SOCK_DGRAM and SOCK_STREAM. + virtual AsyncSocket* CreateAsyncSocket(int type) = 0; +}; + +} // namespace cricket + +#endif // __SOCKETFACTORY_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/socketserver.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketserver.h new file mode 100644 index 00000000..d0e7a22a --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/socketserver.h @@ -0,0 +1,53 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __SOCKETSERVER_H__ +#define __SOCKETSERVER_H__ + +#include "talk/base/socketfactory.h" + +namespace cricket { + +// Provides the ability to wait for activity on a set of sockets. The Thread +// class provides a nice wrapper on a socket server. +// +// The server is also a socket factory. The sockets it creates will be +// notified of asynchronous I/O from this server's Wait method. +class SocketServer : public SocketFactory { +public: + + // Performs I/O or sleeps for the given number of milliseconds. + // If process_io is false, just sleeps until WakeUp. + virtual bool Wait(int cms, bool process_io) = 0; + + // Causes the current wait (if one is in progress) to wake up. + virtual void WakeUp() = 0; +}; + +} // namespace cricket + +#endif // __SOCKETSERVER_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/stl_decl.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/stl_decl.h new file mode 100644 index 00000000..9c2506f1 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/stl_decl.h @@ -0,0 +1,85 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _STL_DECL_H +#define _STL_DECL_H + +#if defined(_MSC_VER) && _MSC_VER <= 1200 // 1200 == VC++ 6.0 +#pragma warning(disable:4786) +#endif + +#include + +namespace std { + template struct hash; + template struct equal_to; + template struct less; + template class allocator; + template class map; + template class vector; + template class list; + template class slist; + template class deque; + template class stack; + template class queue; + template class priority_queue; + template struct pair; + template class set; +} + +///////////////////////////////////////////////////////////////////////////// +// Workaround declaration problem with defaults +///////////////////////////////////////////////////////////////////////////// + +#if defined(_MSC_VER) && _MSC_VER <= 1200 // 1200 == VC++ 6.0 + +#define STD_MAP(T1, T2) \ + std::map, std::allocator > + +#define STD_VECTOR(T1) \ + std::vector > + +#define STD_SET(T1) \ + std::set, std::allocator > + +#else + +#define STD_MAP(T1, T2) \ + std::map, std::allocator > > + +#define STD_VECTOR(T1) \ + std::vector > + +#define STD_SET(T1) \ + std::set, std::allocator > + +#endif + + +#endif // _STL_DECL_H diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/stringutils.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/stringutils.h new file mode 100644 index 00000000..a23132dd --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/stringutils.h @@ -0,0 +1,266 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __STRINGUTILS_H__ +#define __STRINGUTILS_H__ + +#include +#include +#include +#ifdef WIN32 +#include +#endif // WIN32 + +#include + +/////////////////////////////////////////////////////////////////////////////// +// Rename a bunch of common string functions so they are consistent across +// platforms and between char and wchar_t variants. +// Here is the full list of functions that are unified: +// strlen, strcmp, stricmp, strncmp, strnicmp +// strchr, vsnprintf, strtoul, tolowercase +// tolowercase is like tolower, but not compatible with end-of-file value +// Note that the wchar_t versions are not available on Linux +/////////////////////////////////////////////////////////////////////////////// + +inline char tolowercase(char c) { + return static_cast(tolower(c)); +} + +#ifdef WIN32 + +inline size_t strlen(const wchar_t* s) { + return wcslen(s); +} +inline int strcmp(const wchar_t* s1, const wchar_t* s2) { + return wcscmp(s1, s2); +} +inline int stricmp(const wchar_t* s1, const wchar_t* s2) { + return wcsicmp(s1, s2); +} +inline int strncmp(const wchar_t* s1, const wchar_t* s2, size_t n) { + return wcsncmp(s1, s2, n); +} +inline int strnicmp(const wchar_t* s1, const wchar_t* s2, size_t n) { + return wcsnicmp(s1, s2, n); +} +inline const wchar_t* strchr(const wchar_t* s, wchar_t c) { + return wcschr(s, c); +} +inline int vsnprintf(char* buf, size_t n, const char* fmt, va_list args) { + return _vsnprintf(buf, n, fmt, args); +} +inline int vsnprintf(wchar_t* buf, size_t n, const wchar_t* fmt, va_list args) { + return _vsnwprintf(buf, n, fmt, args); +} +inline unsigned long strtoul(const wchar_t* snum, wchar_t** end, int base) { + return wcstoul(snum, end, base); +} +inline wchar_t tolowercase(wchar_t c) { + return static_cast(towlower(c)); +} + +#endif // WIN32 + +#ifdef POSIX + +inline int stricmp(const char* s1, const char* s2) { + return strcasecmp(s1, s2); +} +inline int strnicmp(const char* s1, const char* s2, size_t n) { + return strncasecmp(s1, s2, n); +} + +#endif // POSIX + +/////////////////////////////////////////////////////////////////////////////// +// Traits simplifies porting string functions to be CTYPE-agnostic +/////////////////////////////////////////////////////////////////////////////// + +namespace cricket { + +const size_t SIZE_UNKNOWN = static_cast(-1); + +template +struct Traits { + // STL string type + //typedef XXX string; + // Null-terminated string + //inline static const CTYPE* empty_str(); +}; + +/////////////////////////////////////////////////////////////////////////////// +// String utilities which work with char or wchar_t +/////////////////////////////////////////////////////////////////////////////// + +template +inline const CTYPE* nonnull(const CTYPE* str, const CTYPE* def_str = NULL) { + return str ? str : (def_str ? def_str : Traits::empty_str()); +} + +template +const CTYPE* strchr(const CTYPE* str, const CTYPE* chs) { + for (size_t i=0; str[i]; ++i) { + for (size_t j=0; chs[j]; ++j) { + if (str[i] == chs[j]) { + return str + i; + } + } + } + return 0; +} + +template +const CTYPE* strchrn(const CTYPE* str, size_t slen, CTYPE ch) { + for (size_t i=0; i +size_t strlenn(const CTYPE* buffer, size_t buflen) { + size_t bufpos = 0; + while (buffer[bufpos] && (bufpos < buflen)) { + ++bufpos; + } + return bufpos; +} + +template +size_t strcpyn(CTYPE* buffer, size_t buflen, + const CTYPE* source, size_t srclen = SIZE_UNKNOWN) { + if (buflen <= 0) + return 0; + + if (srclen == SIZE_UNKNOWN) { + srclen = strlenn(source, buflen - 1); + } else if (srclen >= buflen) { + srclen = buflen - 1; + } + memcpy(buffer, source, srclen * sizeof(CTYPE)); + buffer[srclen] = 0; + return srclen; +} + +// Safe versions of snprintf and vsnprintf that always null-terminate + +template +size_t sprintfn(CTYPE* buffer, size_t buflen, const CTYPE* format, ...) { + va_list args; + va_start(args, format); + size_t len = vsprintfn(buffer, buflen, format, args); + va_end(args); + return len; +} + +template +size_t vsprintfn(CTYPE* buffer, size_t buflen, const CTYPE* format, + va_list args) { + int len = vsnprintf(buffer, buflen, format, args); + if ((len < 0) || (static_cast(len) >= buflen)) { + len = static_cast(buflen - 1); + buffer[len] = 0; + } + return len; +} + +/////////////////////////////////////////////////////////////////////////////// +// Allow safe comparing and copying ascii (not UTF-8) with both wide and +// non-wide character strings. +/////////////////////////////////////////////////////////////////////////////// + +inline int asccmp(const char* s1, const char* s2) { + return strcmp(s1, s2); +} +inline int ascicmp(const char* s1, const char* s2) { + return stricmp(s1, s2); +} +inline int ascncmp(const char* s1, const char* s2, size_t n) { + return strncmp(s1, s2, n); +} +inline int ascnicmp(const char* s1, const char* s2, size_t n) { + return strnicmp(s1, s2, n); +} +inline size_t asccpyn(char* buffer, size_t buflen, + const char* source, size_t srclen = SIZE_UNKNOWN) { + return strcpyn(buffer, buflen, source, srclen); +} + +#ifdef WIN32 + +typedef wchar_t(*CharacterTransformation)(wchar_t); +inline wchar_t identity(wchar_t c) { return c; } +int ascii_string_compare(const wchar_t* s1, const char* s2, size_t n, + CharacterTransformation transformation); + +inline int asccmp(const wchar_t* s1, const char* s2) { + return ascii_string_compare(s1, s2, static_cast(-1), identity); +} +inline int ascicmp(const wchar_t* s1, const char* s2) { + return ascii_string_compare(s1, s2, static_cast(-1), tolowercase); +} +inline int ascncmp(const wchar_t* s1, const char* s2, size_t n) { + return ascii_string_compare(s1, s2, n, identity); +} +inline int ascnicmp(const wchar_t* s1, const char* s2, size_t n) { + return ascii_string_compare(s1, s2, n, tolowercase); +} +size_t asccpyn(wchar_t* buffer, size_t buflen, + const char* source, size_t srclen = SIZE_UNKNOWN); + +#endif // WIN32 + +/////////////////////////////////////////////////////////////////////////////// +// Traits specializations +/////////////////////////////////////////////////////////////////////////////// + +template<> +struct Traits { + typedef std::string string; + inline static const char* empty_str() { return ""; } +}; + +/////////////////////////////////////////////////////////////////////////////// +// Traits specializations (Windows only, currently) +/////////////////////////////////////////////////////////////////////////////// + +#ifdef WIN32 + +template<> +struct Traits { + typedef std::wstring string; + inline static const wchar_t* Traits::empty_str() { return L""; } +}; + +#endif // WIN32 + +} // namespace cricket + +#endif // __STRINGUTILS_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/task.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/task.cc new file mode 100644 index 00000000..a5a94941 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/task.cc @@ -0,0 +1,238 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "task.h" +#include "taskrunner.h" + +#include + +namespace buzz { + +Task::Task(Task * parent) : + state_(STATE_INIT), + parent_(parent), + blocked_(false), + done_(false), + aborted_(false), + busy_(false), + error_(false), + child_error_(false), + start_time_(0) { + runner_ = ((parent == NULL) ? (TaskRunner *)this : parent->GetRunner()); + if (parent_ != NULL) { + parent_->AddChild(this); + } +} + +unsigned long long +Task::CurrentTime() { + return runner_->CurrentTime(); +} + +unsigned long long +Task::ElapsedTime() { + return CurrentTime() - start_time_; +} + +void +Task::Start() { + if (state_ != STATE_INIT) + return; + GetRunner()->StartTask(this); + start_time_ = CurrentTime(); +} + +void +Task::Step() { + if (done_) { +#ifdef DEBUG + // we do not know how !blocked_ happens when done_ - should be impossible. + // But it causes problems, so in retail build, we force blocked_, and + // under debug we assert. + assert(blocked_); +#else + blocked_ = true; +#endif + return; + } + + // Async Error() was called + if (error_) { + done_ = true; + state_ = STATE_ERROR; + blocked_ = true; +// obsolete - an errored task is not considered done now +// SignalDone(); + Stop(); + return; + } + + busy_ = true; + int new_state = Process(state_); + busy_ = false; + + if (aborted_) { + Abort(true); // no need to wake because we're awake + return; + } + + if (new_state == STATE_BLOCKED) { + blocked_ = true; + } + else { + state_ = new_state; + blocked_ = false; + } + + if (new_state == STATE_DONE) { + done_ = true; + } + else if (new_state == STATE_ERROR) { + done_ = true; + error_ = true; + } + + if (done_) { +// obsolete - call this yourself +// SignalDone(); + Stop(); + blocked_ = true; + } +} + +void +Task::Abort(bool nowake) { + if (aborted_ || done_) + return; + aborted_ = true; + if (!busy_) { + done_ = true; + blocked_ = true; + error_ = true; + Stop(); + if (!nowake) + Wake(); // to self-delete + } +} + +void +Task::Wake() { + if (done_) + return; + if (blocked_) { + blocked_ = false; + GetRunner()->WakeTasks(); + } +} + +void +Task::Error() { + if (error_ || done_) + return; + error_ = true; + Wake(); +} + +std::string +Task::GetStateName(int state) const { + static const std::string STR_BLOCKED("BLOCKED"); + static const std::string STR_INIT("INIT"); + static const std::string STR_START("START"); + static const std::string STR_DONE("DONE"); + static const std::string STR_ERROR("ERROR"); + static const std::string STR_RESPONSE("RESPONSE"); + static const std::string STR_HUH("??"); + switch (state) { + case STATE_BLOCKED: return STR_BLOCKED; + case STATE_INIT: return STR_INIT; + case STATE_START: return STR_START; + case STATE_DONE: return STR_DONE; + case STATE_ERROR: return STR_ERROR; + case STATE_RESPONSE: return STR_RESPONSE; + } + return STR_HUH; +} + +int Task::Process(int state) { + switch (state) { + case STATE_INIT: + return STATE_START; + case STATE_START: + return ProcessStart(); + case STATE_RESPONSE: + return ProcessResponse(); + case STATE_DONE: + case STATE_ERROR: + return STATE_BLOCKED; + } + return STATE_ERROR; +} + +void +Task::AddChild(Task * child) { + children_.insert(child); +} + +bool +Task::AllChildrenDone() { + for (ChildSet::iterator it = children_.begin(); it != children_.end(); ++it) { + if (!(*it)->IsDone()) + return false; + } + return true; +} + +bool +Task::AnyChildError() { + return child_error_; +} + +void +Task::AbortAllChildren() { + if (children_.size() > 0) { + ChildSet copy = children_; + for (ChildSet::iterator it = copy.begin(); it != copy.end(); ++it) { + (*it)->Abort(true); // Note we do not wake + } + } +} + +void +Task::Stop() { + AbortAllChildren(); // No need to wake because we're either awake or in abort + parent_->OnChildStopped(this); +} + +void +Task::OnChildStopped(Task * child) { + if (child->HasError()) + child_error_ = true; + children_.erase(child); +} + + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/task.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/task.h new file mode 100644 index 00000000..5a486198 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/task.h @@ -0,0 +1,186 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _TASK_H_ +#define _TASK_H_ + +#include +#include + +#include "talk/base/sigslot.h" + +///////////////////////////////////////////////////////////////////// +// +// TASK +// +///////////////////////////////////////////////////////////////////// +// +// Task is a state machine infrastructure. States are pushed forward by +// pushing forwards a TaskRunner that holds on to all Tasks. The purpose +// of Task is threefold: +// +// (1) It manages ongoing work on the UI thread. Multitasking without +// threads, keeping it easy, keeping it real. :-) It does this by +// organizing a set of states for each task. When you return from your +// Process*() function, you return an integer for the next state. You do +// not go onto the next state yourself. Every time you enter a state, +// you check to see if you can do anything yet. If not, you return +// STATE_BLOCKED. If you _could_ do anything, do not return +// STATE_BLOCKED - even if you end up in the same state, return +// STATE_mysamestate. When you are done, return STATE_DONE and then the +// task will self-delete sometimea afterwards. +// +// (2) It helps you avoid all those reentrancy problems when you chain +// too many triggers on one thread. Basically if you want to tell a task +// to process something for you, you feed your task some information and +// then you Wake() it. Don't tell it to process it right away. If it +// might be working on something as you send it infomration, you may want +// to have a queue in the task. +// +// (3) Finally it helps manage parent tasks and children. If a parent +// task gets aborted, all the children tasks are too. The nice thing +// about this, for example, is if you have one parent task that +// represents, say, and Xmpp connection, then you can spawn a whole bunch +// of infinite lifetime child tasks and now worry about cleaning them up. +// When the parent task goes to STATE_DONE, the task engine will make +// sure all those children are aborted and get deleted. +// +// Notice that Task has a few built-in states, e.g., +// +// STATE_INIT - the task isn't running yet +// STATE_START - the task is in its first state +// STATE_RESPONSE - the task is in its second state +// STATE_DONE - the task is done +// +// STATE_ERROR - indicates an error - we should audit the error code in +// light of any usage of it to see if it should be improved. When I +// first put down the task stuff I didn't have a good sense of what was +// needed for Abort and Error, and now the subclasses of Task will ground +// the design in a stronger way. +// +// STATE_NEXT - the first undefined state number. (like WM_USER) - you +// can start defining more task states there. +// +// When you define more task states, just override Process(int state) and +// add your own switch statement. If you want to delegate to +// Task::Process, you can effectively delegate to its switch statement. +// No fancy method pointers or such - this is all just pretty low tech, +// easy to debug, and fast. +// + +namespace buzz { + +class TaskRunner; + +// A task executes a sequence of steps + +class Task; +class RootTask; + +class Task { +public: + Task(Task * parent); + virtual ~Task() {} + + void Start(); + void Step(); + int GetState() const { return state_; } + bool HasError() const { return (GetState() == STATE_ERROR); } + bool Blocked() const { return blocked_; } + bool IsDone() const { return done_; } + unsigned long long ElapsedTime(); + virtual void Poll() {} + + Task * GetParent() { return parent_; } + TaskRunner * GetRunner() { return runner_; } + virtual Task * GetParent(int code) { return parent_->GetParent(code); } + + // Called from outside to stop task without any more callbacks + void Abort(bool nowake = false); + + // For managing children + bool AllChildrenDone(); + bool AnyChildError(); + + +protected: + + enum { + STATE_BLOCKED = -1, + STATE_INIT = 0, + STATE_START = 1, + STATE_DONE = 2, + STATE_ERROR = 3, + STATE_RESPONSE = 4, + STATE_NEXT = 5, // Subclasses which need more states start here and higher + }; + + // Called inside the task to signal that the task may be unblocked + void Wake(); + + // Called inside to advise that the task should wake and signal an error + void Error(); + + unsigned long long CurrentTime(); + + virtual std::string GetStateName(int state) const; + virtual int Process(int state); + virtual void Stop(); + virtual int ProcessStart() = 0; + virtual int ProcessResponse() { return STATE_DONE; } + + // for managing children (if any) + void AddChild(Task * child); + void AbortAllChildren(); + +private: + void Done(); + void OnChildStopped(Task * child); + + int state_; + Task * parent_; + TaskRunner * runner_; + bool blocked_; + bool done_; + bool aborted_; + bool busy_; + bool error_; + bool child_error_; + unsigned long long start_time_; + + // for managing children + typedef std::set ChildSet; + ChildSet children_; + +}; + + + + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/taskrunner.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/taskrunner.cc new file mode 100644 index 00000000..b5ecc55e --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/taskrunner.cc @@ -0,0 +1,92 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "taskrunner.h" +#include "task.h" +#include + + +namespace buzz { + +TaskRunner::~TaskRunner() { + // this kills and deletes children silently! + AbortAllChildren(); + RunTasks(); +} + +void +TaskRunner::StartTask(Task * task) { + tasks_.push_back(task); + WakeTasks(); +} + +void +TaskRunner::RunTasks() { + // Running continues until all tasks are Blocked (ok for a small # of tasks) + if (tasks_running_) { + return; // don't reenter + } + + tasks_running_ = true; + + int did_run = true; + while (did_run) { + did_run = false; + // use indexing instead of iterators because tasks_ may grow + for (size_t i = 0; i < tasks_.size(); ++i) { + while (!tasks_[i]->Blocked()) { + tasks_[i]->Step(); + did_run = true; + } + } + } + // Tasks are deleted when running has paused + for (size_t i = 0; i < tasks_.size(); ++i) { + if (tasks_[i]->IsDone()) { + Task* task = tasks_[i]; + delete task; + tasks_[i] = NULL; + } + } + // Finally, remove nulls + tasks_.erase(std::remove(tasks_.begin(), tasks_.end(), (Task *)NULL), tasks_.end()); + + tasks_running_ = false; +} + +void +TaskRunner::PollTasks() { + // every task gets hit once with a poll - they wake if needed + for (size_t i = 0; i < tasks_.size(); ++i) { + if (!tasks_[i]->IsDone()) { + tasks_[i]->Poll(); + } + } +} + + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/taskrunner.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/taskrunner.h new file mode 100644 index 00000000..eab16eb9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/taskrunner.h @@ -0,0 +1,64 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _TASKRUNNER_H_ +#define _TASKRUNNER_H_ + +#include + +#include "talk/base/sigslot.h" +#include "talk/base/task.h" + + +namespace buzz { + + +class Task; + +class TaskRunner : public Task, public sigslot::has_slots<> { +public: + TaskRunner() : Task(NULL), tasks_running_(false) {} + virtual ~TaskRunner(); + + virtual void WakeTasks() = 0; + virtual unsigned long long CurrentTime() = 0 ; + + void StartTask(Task * task); + void RunTasks(); + void PollTasks(); + + // dummy state machine - never run. + virtual int ProcessStart() { return STATE_DONE; } + +private: + std::vector tasks_; + bool tasks_running_; +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/thread.cc b/kopete/protocols/jabber/jingle/libjingle/talk/base/thread.cc new file mode 100644 index 00000000..8f18a992 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/thread.cc @@ -0,0 +1,273 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifdef POSIX +extern "C" { +#include +} +#endif + +#include "talk/base/common.h" +#include "talk/base/logging.h" +#include "talk/base/thread.h" +#include "talk/base/jtime.h" + +namespace cricket { + +ThreadManager g_thmgr; + +#ifdef POSIX +pthread_key_t ThreadManager::key_; + +ThreadManager::ThreadManager() { + pthread_key_create(&key_, NULL); + main_thread_ = new Thread(); + SetCurrent(main_thread_); +} + +ThreadManager::~ThreadManager() { + pthread_key_delete(key_); + delete main_thread_; +} + +Thread *ThreadManager::CurrentThread() { + return (Thread *)pthread_getspecific(key_); +} + +void ThreadManager::SetCurrent(Thread *thread) { + pthread_setspecific(key_, thread); +} +#endif + +#ifdef WIN32 +DWORD ThreadManager::key_; + +ThreadManager::ThreadManager() { + key_ = TlsAlloc(); + main_thread_ = new Thread(); + SetCurrent(main_thread_); +} + +ThreadManager::~ThreadManager() { + TlsFree(key_); + delete main_thread_; +} + +Thread *ThreadManager::CurrentThread() { + return (Thread *)TlsGetValue(key_); +} + +void ThreadManager::SetCurrent(Thread *thread) { + TlsSetValue(key_, thread); +} +#endif + +void ThreadManager::Add(Thread *thread) { + CritScope cs(&crit_); + threads_.push_back(thread); +} + +void ThreadManager::Remove(Thread *thread) { + CritScope cs(&crit_); + threads_.erase(std::remove(threads_.begin(), threads_.end(), thread), threads_.end()); +} + +Thread::Thread(SocketServer* ss) : MessageQueue(ss) { + g_thmgr.Add(this); + started_ = false; + has_sends_ = false; +} + +Thread::~Thread() { + Stop(); + Clear(NULL); + g_thmgr.Remove(this); +} + +#ifdef POSIX +void Thread::Start() { + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_create(&thread_, &attr, PreLoop, this); + started_ = true; +} + +void Thread::Join() { + if (started_) { + void *pv; + pthread_join(thread_, &pv); + } +} +#endif + +#ifdef WIN32 +void Thread::Start() { + thread_ = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PreLoop, this, 0, NULL); + started_ = true; +} + +void Thread::Join() { + if (started_) { + WaitForSingleObject(thread_, INFINITE); + CloseHandle(thread_); + started_ = false; + } +} +#endif + +void *Thread::PreLoop(void *pv) { + Thread *thread = (Thread *)pv; + ThreadManager::SetCurrent(thread); + thread->Loop(); + return NULL; +} + +void Thread::Loop(int cmsLoop) { + uint32 msEnd; + if (cmsLoop != -1) + msEnd = GetMillisecondCount() + cmsLoop; + int cmsNext = cmsLoop; + + while (true) { + Message msg; + if (!Get(&msg, cmsNext)) + return; + Dispatch(&msg); + + if (cmsLoop != -1) { + uint32 msCur = GetMillisecondCount(); + if (msCur >= msEnd) + return; + cmsNext = msEnd - msCur; + } + } +} + +void Thread::Stop() { + MessageQueue::Stop(); + Join(); +} + +void Thread::Send(MessageHandler *phandler, uint32 id, MessageData *pdata) { + // Sent messages are sent to the MessageHandler directly, in the context + // of "thread", like Win32 SendMessage. If in the right context, + // call the handler directly. + + Message msg; + msg.phandler = phandler; + msg.message_id = id; + msg.pdata = pdata; + if (IsCurrent()) { + phandler->OnMessage(&msg); + return; + } + + AutoThread thread; + Thread *current_thread = Thread::Current(); + ASSERT(current_thread != NULL); // AutoThread ensures this + + crit_.Enter(); + bool ready = false; + _SendMessage smsg; + smsg.thread = current_thread; + smsg.msg = msg; + smsg.ready = &ready; + sendlist_.push_back(smsg); + has_sends_ = true; + crit_.Leave(); + + // Wait for a reply + + ss_->WakeUp(); + while (!ready) { + current_thread->ReceiveSends(); + current_thread->socketserver()->Wait(-1, false); + } +} + +void Thread::ReceiveSends() { + // Before entering critical section, check boolean. + + if (!has_sends_) + return; + + // Receive a sent message. Cleanup scenarios: + // - thread sending exits: We don't allow this, since thread can exit + // only via Join, so Send must complete. + // - thread receiving exits: Wakeup/set ready in Thread::Clear() + // - object target cleared: Wakeup/set ready in Thread::Clear() + crit_.Enter(); + while (!sendlist_.empty()) { + _SendMessage smsg = sendlist_.front(); + sendlist_.pop_front(); + crit_.Leave(); + smsg.msg.phandler->OnMessage(&smsg.msg); + crit_.Enter(); + *smsg.ready = true; + smsg.thread->socketserver()->WakeUp(); + } + has_sends_ = false; + crit_.Leave(); +} + +void Thread::Clear(MessageHandler *phandler, uint32 id) { + CritScope cs(&crit_); + + // Remove messages on sendlist_ with phandler + // Object target cleared: remove from send list, wakeup/set ready + // if sender not NULL. + + std::list<_SendMessage>::iterator iter = sendlist_.begin(); + while (iter != sendlist_.end()) { + _SendMessage smsg = *iter; + if (phandler == NULL || smsg.msg.phandler == phandler) { + if (id == (uint32)-1 || smsg.msg.message_id == id) { + iter = sendlist_.erase(iter); + *smsg.ready = true; + smsg.thread->socketserver()->WakeUp(); + continue; + } + } + ++iter; + } + + MessageQueue::Clear(phandler, id); +} + +AutoThread::AutoThread(SocketServer* ss) : Thread(ss) { + if (!ThreadManager::CurrentThread()) { + ThreadManager::SetCurrent(this); + } +} + +AutoThread::~AutoThread() { + if (ThreadManager::CurrentThread() == this) { + ThreadManager::SetCurrent(NULL); + } +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/thread.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/thread.h new file mode 100644 index 00000000..56c23384 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/thread.h @@ -0,0 +1,141 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __THREAD_H__ +#define __THREAD_H__ + +#include "talk/base/messagequeue.h" + +#include +#include +#include + +#ifdef POSIX +#include +#endif + +#ifdef WIN32 +#include "talk/base/win32.h" +#endif + +namespace cricket { + +class Thread; + +class ThreadManager { +public: + ThreadManager(); + ~ThreadManager(); + + static Thread *CurrentThread(); + static void SetCurrent(Thread *thread); + void Add(Thread *thread); + void Remove(Thread *thread); + +private: + Thread *main_thread_; + std::vector threads_; + CriticalSection crit_; + +#ifdef POSIX + static pthread_key_t key_; +#endif + +#ifdef WIN32 + static DWORD key_; +#endif +}; + +class Thread; + +struct _SendMessage { + _SendMessage() {} + Thread *thread; + Message msg; + bool *ready; +}; + +class Thread : public MessageQueue { +public: + Thread(SocketServer* ss = 0); + virtual ~Thread(); + + static inline Thread* Current() { + return ThreadManager::CurrentThread(); + } + inline bool IsCurrent() const { + return (ThreadManager::CurrentThread() == this); + } + + virtual void Start(); + virtual void Stop(); + virtual void Loop(int cms = -1); + virtual void Send(MessageHandler *phandler, uint32 id = 0, + MessageData *pdata = NULL); + + // From MessageQueue + virtual void Clear(MessageHandler *phandler, uint32 id = (uint32)-1); + virtual void ReceiveSends(); + +#ifdef WIN32 + HANDLE GetHandle() { + return thread_; + } +#endif + +private: + static void *PreLoop(void *pv); + void Join(); + + std::list<_SendMessage> sendlist_; + bool started_; + bool has_sends_; + +#ifdef POSIX + pthread_t thread_; +#endif + +#ifdef WIN32 + HANDLE thread_; +#endif + + friend class ThreadManager; +}; + +// AutoThread automatically installs itself at construction +// uninstalls at destruction, if a Thread object is +// _not already_ associated with the current OS thread. + +class AutoThread : public Thread { +public: + AutoThread(SocketServer* ss = 0); + virtual ~AutoThread(); +}; + +} // namespace cricket + +#endif // __THREAD_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/base/winping.h b/kopete/protocols/jabber/jingle/libjingle/talk/base/winping.h new file mode 100644 index 00000000..99ba2fdc --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/base/winping.h @@ -0,0 +1,101 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _WINPING_H_ +#define _WINPING_H_ + +#ifdef WIN32 + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include "talk/base/basictypes.h" + +#include +#define _WINSOCKAPI_ +#include +#undef SetPort + +// This class wraps a Win32 API for doing ICMP pinging. This API, unlike the +// the normal socket APIs (as implemented on Win9x), will return an error if +// an ICMP packet with the dont-fragment bit set is too large. This means this +// class can be used to detect the MTU to a given address. + +typedef struct ip_option_information { + UCHAR Ttl; // Time To Live + UCHAR Tos; // Type Of Service + UCHAR Flags; // IP header flags + UCHAR OptionsSize; // Size in bytes of options data + PUCHAR OptionsData; // Pointer to options data +} IP_OPTION_INFORMATION, * PIP_OPTION_INFORMATION; + +typedef HANDLE (WINAPI *PIcmpCreateFile)(); + +typedef BOOL (WINAPI *PIcmpCloseHandle)(HANDLE icmp_handle); + +typedef DWORD (WINAPI *PIcmpSendEcho)( + HANDLE IcmpHandle, + ULONG DestinationAddress, + LPVOID RequestData, + WORD RequestSize, + PIP_OPTION_INFORMATION RequestOptions, + LPVOID ReplyBuffer, + DWORD ReplySize, + DWORD Timeout); + +class WinPing { +public: + WinPing(); + ~WinPing(); + + // Determines whether the class was initialized correctly. + bool IsValid() { return valid_; } + + // Attempts to send a ping with the given parameters. + enum PingResult { PING_FAIL, PING_TOO_LARGE, PING_TIMEOUT, PING_SUCCESS }; + PingResult Ping( + uint32 ip, uint32 data_size, uint32 timeout_millis, uint8 ttl, + bool allow_fragments); + +private: + HMODULE dll_; + HANDLE hping_; + PIcmpCreateFile create_; + PIcmpCloseHandle close_; + PIcmpSendEcho send_; + char* data_; + uint32 dlen_; + char* reply_; + uint32 rlen_; + bool valid_; +}; + +#endif // WIN32 + +#endif // _WINPING_H_ + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/examples/Makefile.am new file mode 100644 index 00000000..43b0edb5 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=login call diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/Makefile.am new file mode 100644 index 00000000..81cf9345 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/Makefile.am @@ -0,0 +1,16 @@ +bin_PROGRAMS = call +call_CXXFLAGS = $(AM_CXXFLAGS) +call_SOURCES = call_main.cc callclient.cc console.cc presencepushtask.cc presenceouttask.cc +noinst_HEADERS = callclient.h console.h presenceouttask.h presencepushtask.h status.h +call_LDADD = \ + $(srcdir)/../../../talk/examples/login/libcricketexampleslogin.la \ + $(srcdir)/../../../talk/session/phone/libcricketsessionphone.la \ + $(srcdir)/../../../talk/p2p/client/libcricketp2pclient.la \ + $(srcdir)/../../../talk/p2p/base/libcricketp2pbase.la \ + $(srcdir)/../../../talk/xmpp/libcricketxmpp.la \ + $(srcdir)/../../../talk/xmllite/libcricketxmllite.la \ + $(srcdir)/../../../talk/base/libcricketbase.la \ + $(srcdir)/../../../talk/third_party/mediastreamer/libmediastreamer.la \ + $(EXPAT_LIBS) $(ORTP_LIBS) -lpthread $(ILBC_LIBS) $(SPEEX_LIBS) $(GLIB_LIBS) -lasound +AM_CPPFLAGS = -DPOSIX +DEFAULT_INCLUDES = -I$(srcdir)/../../.. \ No newline at end of file diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/call.pro b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/call.pro new file mode 100644 index 00000000..ccf0638b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/call.pro @@ -0,0 +1,19 @@ +TEMPLATE = app +INCLUDEPATH = ../../.. +DEFINES += POSIX + +include(../../../../../conf.pri) + +# Input +SOURCES += \ + call_main.cc \ + callclient.cc \ + console.cc \ + presenceouttask.cc \ + presencepushtask.cc \ + ../login/xmppauth.cc \ + ../login/xmpppump.cc \ + ../login/xmppsocket.cc \ + ../login/xmppthread.cc + +LIBS += ../../../liblibjingle.a diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/call_main.cc b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/call_main.cc new file mode 100644 index 00000000..1a965326 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/call_main.cc @@ -0,0 +1,62 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google 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 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "talk/xmpp/xmppclientsettings.h" +#include "talk/examples/login/xmppthread.h" +#include "talk/examples/login/xmppauth.h" +#include "talk/examples/call/callclient.h" +#include "talk/examples/call/console.h" + +void GetString(const char* desc, char* out) { + printf("%s: ", desc); + fflush(stdout); + scanf("%s", out); +} + +int main(int argc, char **argv) { + // TODO: Make this into a console task + char username[256], auth_cookie[256]; + GetString("Username", username); + GetString("Auth Cookie", auth_cookie); + + printf("Logging in as %s@gmail.com\n", username); + + // We will run the console and the XMPP client on the main thread. The + // CallClient maintains a separate worker thread for voice. + + cricket::PhysicalSocketServer ss; + cricket::Thread main_thread(&ss); + cricket::ThreadManager::SetCurrent(&main_thread); + + InitConsole(&ss); + XmppPump pump; + CallClient client(pump.client()); + + buzz::XmppClientSettings xcs; + xcs.set_user(username); + xcs.set_host("gmail.com"); + xcs.set_use_tls(false); + xcs.set_auth_cookie(auth_cookie); + xcs.set_server(cricket::SocketAddress("talk.google.com", 5222)); + pump.DoLogin(xcs, new XmppSocket(false), new XmppAuth()); + + main_thread.Loop(); + + return 0; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/callclient.cc b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/callclient.cc new file mode 100644 index 00000000..c8c28310 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/callclient.cc @@ -0,0 +1,390 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google 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 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "talk/xmpp/constants.h" +#include "talk/base/thread.h" +#include "talk/base/network.h" +#include "talk/base/socketaddress.h" +#include "talk/p2p/base/sessionmanager.h" +#include "talk/p2p/base/helpers.h" +#include "talk/p2p/client/basicportallocator.h" +#include "talk/session/receiver.h" +#include "talk/session/sessionsendtask.h" +#include "talk/session/phone/phonesessionclient.h" +#include "talk/examples/call/callclient.h" +#include "talk/examples/call/console.h" +#include "talk/examples/call/presencepushtask.h" +#include "talk/examples/call/presenceouttask.h" + +namespace { + +const char* CALL_COMMANDS = +"Available commands:\n" +"\n" +" hangup Ends the call.\n" +" mute Stops sending voice.\n" +" unmute Re-starts sending voice.\n" +""; + +class CallTask: public ConsoleTask, public sigslot::has_slots<> { +public: + CallTask(CallClient* call_client, const buzz::Jid& jid, cricket::Call* call) + : call_client_(call_client), jid_(jid), call_(call) { + } + + virtual ~CallTask() {} + + virtual void Start() { + call_client_->phone_client()->SignalCallDestroy.connect( + this, &CallTask::OnCallDestroy); + if (!call_) { + call_ = call_client_->phone_client()->CreateCall(); + call_->SignalSessionState.connect(this, &CallTask::OnSessionState); + session_ = call_->InitiateSession(jid_); + } + call_client_->phone_client()->SetFocus(call_); + } + + virtual std::string GetPrompt() { return jid_.node(); } + + virtual void ProcessLine(const std::string& line) { + std::vector words; + ParseLine(line, &words); + + if ((words.size() == 1) && (words[0] == "hangup")) { + call_->Terminate(); + SignalDone(this); + } else if ((words.size() == 1) && (words[0] == "mute")) { + call_->Mute(true); + } else if ((words.size() == 1) && (words[0] == "unmute")) { + call_->Mute(false); + } else { + console()->Print(CALL_COMMANDS); + } + } + +private: + CallClient* call_client_; + buzz::Jid jid_; + cricket::Call* call_; + cricket::Session* session_; + + void OnCallDestroy(cricket::Call* call) { + if (call == call_) { + console()->Print("call destroyed"); + SignalDone(this); + } + } + + void OnSessionState(cricket::Call* call, + cricket::Session* session, + cricket::Session::State state) { + if (state == cricket::Session::STATE_SENTINITIATE) { + console()->Print("calling..."); + } else if (state == cricket::Session::STATE_RECEIVEDACCEPT) { + console()->Print("call answered"); + } else if (state == cricket::Session::STATE_RECEIVEDREJECT) { + console()->Print("call not answered"); + SignalDone(this); + } else if (state == cricket::Session::STATE_INPROGRESS) { + console()->Print("call in progress"); + } else if (state == cricket::Session::STATE_RECEIVEDTERMINATE) { + console()->Print("other side hung up"); + SignalDone(this); + } + } +}; + +const char* RECEIVE_COMMANDS = +"Available commands:\n" +"\n" +" accept Accepts the incoming call and switches to it.\n" +" reject Rejects the incoming call and stays with the current call.\n" +""; + +class ReceiveTask: public ConsoleTask { +public: + ReceiveTask(CallClient* call_client, + const buzz::Jid& jid, + cricket::Call* call) + : call_client_(call_client), jid_(jid), call_(call) { + } + + virtual std::string GetPrompt() { return jid_.node(); } + + virtual void ProcessLine(const std::string& line) { + std::vector words; + ParseLine(line, &words); + + if ((words.size() == 1) && (words[0] == "accept")) { + assert(call_->sessions().size() == 1); + call_->AcceptSession(call_->sessions()[0]); + Console()->Push(new CallTask(call_client_, jid_, call_)); + SignalDone(this); + } else if ((words.size() == 1) && (words[0] == "reject")) { + call_->RejectSession(call_->sessions()[0]); + SignalDone(this); + } else { + console()->Print(RECEIVE_COMMANDS); + } + } + +private: + CallClient* call_client_; + buzz::Jid jid_; + cricket::Call* call_; +}; + +const char* CONSOLE_COMMANDS = +"Available commands:\n" +"\n" +" roster Prints the online friends from your roster.\n" +" call Initiates a call to the friend with the given name.\n" +" quit Quits the application.\n" +""; + +class CallConsoleTask: public ConsoleTask { +public: + CallConsoleTask(CallClient* call_client) : call_client_(call_client) {} + virtual ~CallConsoleTask() {} + + virtual std::string GetPrompt() { return "console"; } + + virtual void ProcessLine(const std::string& line) { + std::vector words; + ParseLine(line, &words); + + if ((words.size() == 1) && (words[0] == "quit")) { + SignalDone(this); + } else if ((words.size() == 1) && (words[0] == "roster")) { + call_client_->PrintRoster(); + } else if ((words.size() == 2) && (words[0] == "call")) { + call_client_->MakeCallTo(words[1]); + } else { + console()->Print(CONSOLE_COMMANDS); + } + } + +private: + CallClient* call_client_; +}; + +const char* DescribeStatus(buzz::Status::Show show, const std::string& desc) { + switch (show) { + case buzz::Status::SHOW_XA: return desc.c_str(); + case buzz::Status::SHOW_ONLINE: return "online"; + case buzz::Status::SHOW_AWAY: return "away"; + case buzz::Status::SHOW_DND: return "do not disturb"; + case buzz::Status::SHOW_CHAT: return "ready to chat"; + delault: return "offline"; + } +} + +} // namespace + +CallClient::CallClient(buzz::XmppClient* xmpp_client) + : xmpp_client_(xmpp_client), roster_(new RosterMap) { + xmpp_client_->SignalStateChange.connect(this, &CallClient::OnStateChange); + Console()->Push(new CallConsoleTask(this)); +} + +CallClient::~CallClient() { + delete roster_; +} + +const std::string CallClient::strerror(buzz::XmppEngine::Error err) { + switch (err) { + case buzz::XmppEngine::ERROR_NONE: + return ""; + case buzz::XmppEngine::ERROR_XML: + return "Malformed XML or encoding error"; + case buzz::XmppEngine::ERROR_STREAM: + return "XMPP stream error"; + case buzz::XmppEngine::ERROR_VERSION: + return "XMPP version error"; + case buzz::XmppEngine::ERROR_UNAUTHORIZED: + return "User is not authorized (Confirm your GX cookie at mail.google.com)"; + case buzz::XmppEngine::ERROR_TLS: + return "TLS could not be negotiated"; + case buzz::XmppEngine::ERROR_AUTH: + return "Authentication could not be negotiated"; + case buzz::XmppEngine::ERROR_BIND: + return "Resource or session binding could not be negotiated"; + case buzz::XmppEngine::ERROR_CONNECTION_CLOSED: + return "Connection closed by output handler."; + case buzz::XmppEngine::ERROR_DOCUMENT_CLOSED: + return "Closed by "; + case buzz::XmppEngine::ERROR_SOCKET: + return "Socket error"; + } +} + +void CallClient::OnStateChange(buzz::XmppEngine::State state) { + switch (state) { + case buzz::XmppEngine::STATE_START: + Console()->Print("connecting..."); + break; + + case buzz::XmppEngine::STATE_OPENING: + Console()->Print("logging in..."); + break; + + case buzz::XmppEngine::STATE_OPEN: + Console()->Print("logged in..."); + InitPhone(); + InitPresence(); + break; + + case buzz::XmppEngine::STATE_CLOSED: + buzz::XmppEngine::Error error = xmpp_client_->GetError(); + Console()->Print("logged out..." + strerror(error)); + exit(0); + } +} + +void CallClient::InitPhone() { + std::string client_unique = xmpp_client_->jid().Str(); + cricket::InitRandom(client_unique.c_str(), client_unique.size()); + + worker_thread_ = new cricket::Thread(); + + network_manager_ = new cricket::NetworkManager(); + + cricket::SocketAddress *stun_addr = new cricket::SocketAddress("64.233.167.126", 19302); + port_allocator_ = new cricket::BasicPortAllocator(network_manager_, stun_addr, NULL); + + session_manager_ = new cricket::SessionManager( + port_allocator_, worker_thread_); + session_manager_->SignalRequestSignaling.connect( + this, &CallClient::OnRequestSignaling); + session_manager_->OnSignalingReady(); + + phone_client_ = new cricket::PhoneSessionClient( + xmpp_client_->jid(),session_manager_); + phone_client_->SignalCallCreate.connect(this, &CallClient::OnCallCreate); + phone_client_->SignalSendStanza.connect(this, &CallClient::OnSendStanza); + + receiver_ = new cricket::Receiver(xmpp_client_, phone_client_); + receiver_->Start(); + + worker_thread_->Start(); +} + +void CallClient::OnRequestSignaling() { + session_manager_->OnSignalingReady(); +} + +void CallClient::OnCallCreate(cricket::Call* call) { + call->SignalSessionState.connect(this, &CallClient::OnSessionState); +} + +void CallClient::OnSessionState(cricket::Call* call, + cricket::Session* session, + cricket::Session::State state) { + if (state == cricket::Session::STATE_RECEIVEDINITIATE) { + buzz::Jid jid(session->remote_address()); + Console()->Printf("Incoming call from '%s'", jid.Str().c_str()); + Console()->Push(new ReceiveTask(this, jid, call)); + } +} + +void CallClient::OnSendStanza(cricket::SessionClient *client, const buzz::XmlElement* stanza) { + cricket::SessionSendTask* sender = + new cricket::SessionSendTask(xmpp_client_, phone_client_); + sender->Send(stanza); + sender->Start(); +} + +void CallClient::InitPresence() { + presence_push_ = new buzz::PresencePushTask(xmpp_client_); + presence_push_->SignalStatusUpdate.connect( + this, &CallClient::OnStatusUpdate); + presence_push_->Start(); + + buzz::Status my_status; + my_status.set_jid(xmpp_client_->jid()); + my_status.set_available(true); + my_status.set_invisible(false); + my_status.set_show(buzz::Status::SHOW_ONLINE); + my_status.set_priority(0); + my_status.set_know_capabilities(true); + my_status.set_phone_capability(true); + my_status.set_is_google_client(true); + my_status.set_version("1.0.0.66"); + + buzz::PresenceOutTask* presence_out_ = + new buzz::PresenceOutTask(xmpp_client_); + presence_out_->Send(my_status); + presence_out_->Start(); +} + +void CallClient::OnStatusUpdate(const buzz::Status& status) { + RosterItem item; + item.jid = status.jid(); + item.show = status.show(); + item.status = status.status(); + + std::string key = item.jid.Str(); + + if (status.available() && status.phone_capability()) { + Console()->Printf("Adding to roster: %s", key.c_str()); + (*roster_)[key] = item; + } else { + Console()->Printf("Removing from roster: %s", key.c_str()); + RosterMap::iterator iter = roster_->find(key); + if (iter != roster_->end()) + roster_->erase(iter); + } +} + +void CallClient::PrintRoster() { + Console()->Printf("Roster contains %d callable", roster_->size()); + RosterMap::iterator iter = roster_->begin(); + while (iter != roster_->end()) { + Console()->Printf("%s - %s", + iter->second.jid.BareJid().Str().c_str(), + DescribeStatus(iter->second.show, iter->second.status)); + iter++; + } +} + +void CallClient::MakeCallTo(const std::string& name) { + bool found = false; + buzz::Jid found_jid; + + RosterMap::iterator iter = roster_->begin(); + while (iter != roster_->end()) { + if (iter->second.jid.node() == name) { + found = true; + found_jid = iter->second.jid; + break; + } + ++iter; + } + + if (found) { + Console()->Printf("Found online friend '%s'", found_jid.Str().c_str()); + Console()->Push(new CallTask(this, found_jid, NULL)); + } else { + Console()->Printf("Could not find online friend '%s'", name.c_str()); + } +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/callclient.h b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/callclient.h new file mode 100644 index 00000000..2400b7db --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/callclient.h @@ -0,0 +1,88 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google 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 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 CRICKET_EXAMPLES_CALL_CALLCLIENT_H__ +#define CRICKET_EXAMPLES_CALL_CALLCLIENT_H__ + +#include +#include +#include "talk/p2p/base/session.h" +#include "talk/p2p/client/sessionclient.h" +#include "talk/xmpp/xmppclient.h" +#include "talk/examples/call/status.h" + +namespace buzz { +class PresencePushTask; +class Status; +} + +namespace cricket { +class Thread; +class NetworkManager; +class PortAllocator; +class PhoneSessionClient; +class Receiver; +class Call; +} + +struct RosterItem { + buzz::Jid jid; + buzz::Status::Show show; + std::string status; +}; + +class CallClient: public sigslot::has_slots<> { +public: + CallClient(buzz::XmppClient* xmpp_client); + ~CallClient(); + + cricket::PhoneSessionClient* phone_client() const { return phone_client_; } + + void PrintRoster(); + void MakeCallTo(const std::string& name); + +private: + typedef std::map RosterMap; + + buzz::XmppClient* xmpp_client_; + cricket::Thread* worker_thread_; + cricket::NetworkManager* network_manager_; + cricket::PortAllocator* port_allocator_; + cricket::SessionManager* session_manager_; + cricket::PhoneSessionClient* phone_client_; + cricket::Receiver* receiver_; + buzz::PresencePushTask* presence_push_; + RosterMap* roster_; + + void OnStateChange(buzz::XmppEngine::State state); + + void InitPhone(); + void OnRequestSignaling(); + void OnCallCreate(cricket::Call* call); + const std::string strerror(buzz::XmppEngine::Error err); + void OnSessionState(cricket::Call* call, + cricket::Session* session, + cricket::Session::State state); + void OnSendStanza(cricket::SessionClient *client, const buzz::XmlElement* stanza); + + void InitPresence(); + void OnStatusUpdate(const buzz::Status& status); +}; + +#endif // CRICKET_EXAMPLES_CALL_CALLCLIENT_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/console.cc b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/console.cc new file mode 100644 index 00000000..4150f281 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/console.cc @@ -0,0 +1,196 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google 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 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +extern "C" { +#include +#include +#include +#include +#include +} +#include +#include +#include +#include + +#include "talk/examples/call/console.h" + +namespace { + +void PError(const char* desc) { + perror(desc); + exit(1); +} + +CConsole* gConsole = NULL; + +const uint32 MSG_UPDATE = 1; + +} // namespace + +void InitConsole(cricket::PhysicalSocketServer* ss) { + assert(gConsole == NULL); + assert(ss); + gConsole = new CConsole(ss); +} + +CConsole* Console() { + assert(gConsole); + return gConsole; +} + +CConsole::CConsole(cricket::PhysicalSocketServer* ss) + : prompting_(false), prompt_dirty_(false) { + stdin_ = ss->CreateFile(0); + stdin_->SignalReadEvent.connect(this, &CConsole::OnReadInput); + + tasks_ = new std::vector; +} + +CConsole::~CConsole() { + delete stdin_; + delete tasks_; +} + +void CConsole::Push(ConsoleTask* task) { + task->set_console(this); + task->SignalDone.connect(this, &CConsole::OnTaskDone); + tasks_->push_back(task); + task->Start(); + UpdatePrompt(); +} + +void CConsole::Remove(ConsoleTask* task) { + int index = -1; + for (size_t i = 0; i < tasks_->size(); ++i) { + if ((*tasks_)[i] == task) + index = i; + } + + assert(index >= 0); + tasks_->erase(tasks_->begin() + index); + if (static_cast(tasks_->size()) == index) + UpdatePrompt(); + + delete task; + + if (tasks_->size() == 0) + exit(0); +} + +void CConsole::Print(const char* str) { + if (prompting_) + printf("\r"); + printf("%s\n", str); + prompting_ = false; + UpdatePrompt(); +} + +void CConsole::Print(const std::string& str) { + Print(str.c_str()); +} + +void CConsole::Printf(const char* format, ...) { + va_list ap; + va_start(ap, format); + + char buf[4096]; + int size = vsnprintf(buf, sizeof(buf), format, ap); + assert(size >= 0); + assert(size < static_cast(sizeof(buf))); + buf[size] = '\0'; + Print(buf); + + va_end(ap); +} + +void CConsole::OnTaskDone(ConsoleTask* task) { + Remove(task); +} + +void CConsole::OnReadInput(cricket::AsyncFile* file) { + assert(file == stdin_); + + char buf[4096]; + int size = read(0, buf, sizeof(buf)); + if (size < 0) + PError("read"); + + prompting_ = (buf[size-1] != '\n'); + + int start = 0; + for (int i = 0; i < size; ++i) { + if (buf[i] == '\n') { + std::string line = input_; + line.append(buf + start, i + 1 - start); + input_.clear(); + + assert(tasks_->size() > 0); + tasks_->back()->ProcessLine(line); + + start = i + 1; + } + } + + input_.append(buf + start, size - start); +} + +void CConsole::OnMessage(cricket::Message* pmsg) { + assert(pmsg->message_id == MSG_UPDATE); + assert(tasks_->size() > 0); + if (prompting_) + printf("\n"); + printf("%s: %s", tasks_->back()->GetPrompt().c_str(), input_.c_str()); + fflush(stdout); + prompting_ = true; + prompt_dirty_ = false; +} + +void CConsole::UpdatePrompt() { + if (!prompt_dirty_) { + prompt_dirty_ = true; + cricket::Thread::Current()->Post(this, MSG_UPDATE); + } +} + +void ConsoleTask::ParseLine(const std::string& line, + std::vector* words) { + assert(line.size() > 0); + assert(line[line.size() - 1] == '\n'); + + int start = -1; + int state = 0; + for (int index = 0; index <= static_cast(line.size()); ++index) { + if (state == 0) { + if (!isspace(line[index])) { + start = index; + state = 1; + } + } else { + assert(state == 1); + assert(start >= 0); + if (isspace(line[index])) { + std::string word(line, start, index - start); + words->push_back(word); + start = -1; + state = 0; + } + } + } +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/console.h b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/console.h new file mode 100644 index 00000000..aca229b9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/console.h @@ -0,0 +1,82 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google 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 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 CRICKET_EXAMPLES_CALL_CONSOLE_H__ +#define CRICKET_EXAMPLES_CALL_CONSOLE_H__ + +#include +#include + +#include "talk/base/sigslot.h" +#include "talk/base/thread.h" +#include "talk/base/physicalsocketserver.h" + +class CConsole; + +class ConsoleTask { +public: + ConsoleTask() : console_(NULL) {} + virtual ~ConsoleTask() {} + + CConsole* console() const { return console_; } + void set_console(CConsole* console) { console_ = console; } + + virtual void Start() {} + virtual std::string GetPrompt() = 0; + virtual void ProcessLine(const std::string& line) = 0; // includes newline + + sigslot::signal1 SignalDone; + +protected: + void ParseLine(const std::string& line, std::vector* words); + +private: + CConsole* console_; +}; + +class CConsole: public cricket::MessageHandler, public sigslot::has_slots<> { +public: + CConsole(cricket::PhysicalSocketServer* ss); + ~CConsole(); + + void Push(ConsoleTask* task); + void Remove(ConsoleTask* task); + + // final newline should not be included + void Print(const char* str); + void Print(const std::string& str); + void Printf(const char* format, ...); + +private: + cricket::AsyncFile* stdin_; + std::vector* tasks_; + std::string input_; + bool prompting_; + bool prompt_dirty_; + + void OnTaskDone(ConsoleTask* task); + void OnReadInput(cricket::AsyncFile* file); + void OnMessage(cricket::Message* pmsg); + void UpdatePrompt(); +}; + +void InitConsole(cricket::PhysicalSocketServer* ss); +CConsole* Console(); + +#endif // CRICKET_EXAMPLES_CALL_CONSOLE_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presenceouttask.cc b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presenceouttask.cc new file mode 100644 index 00000000..3ecdb420 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presenceouttask.cc @@ -0,0 +1,148 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google 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 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "talk/xmpp/constants.h" +#include "talk/xmpp/xmppclient.h" +#include "talk/examples/call/presenceouttask.h" + +namespace buzz { + +// string helper functions ----------------------------------------------------- +template static +bool FromString(const std::string& s, + T * t) { + std::istringstream iss(s); + return !(iss>>*t).fail(); +} + +template static +bool ToString(const T &t, + std::string* s) { + std::ostringstream oss; + oss << t; + *s = oss.str(); + return !oss.fail(); +} + +XmppReturnStatus +PresenceOutTask::Send(const Status & s) { + if (GetState() != STATE_INIT) + return XMPP_RETURN_BADSTATE; + + stanza_.reset(TranslateStatus(s)); + return XMPP_RETURN_OK; +} + +XmppReturnStatus +PresenceOutTask::SendDirected(const Jid & j, const Status & s) { + if (GetState() != STATE_INIT) + return XMPP_RETURN_BADSTATE; + + XmlElement * presence = TranslateStatus(s); + presence->AddAttr(QN_TO, j.Str()); + stanza_.reset(presence); + return XMPP_RETURN_OK; +} + +XmppReturnStatus PresenceOutTask::SendProbe(const Jid & jid) { + if (GetState() != STATE_INIT) + return XMPP_RETURN_BADSTATE; + + XmlElement * presence = new XmlElement(QN_PRESENCE); + presence->AddAttr(QN_TO, jid.Str()); + presence->AddAttr(QN_TYPE, "probe"); + + stanza_.reset(presence); + return XMPP_RETURN_OK; +} + +int +PresenceOutTask::ProcessStart() { + if (SendStanza(stanza_.get()) != XMPP_RETURN_OK) + return STATE_ERROR; + return STATE_DONE; +} + +XmlElement * +PresenceOutTask::TranslateStatus(const Status & s) { + XmlElement * result = new XmlElement(QN_PRESENCE); + if (!s.available()) { + result->AddAttr(QN_TYPE, STR_UNAVAILABLE); + } + else { + if (s.invisible()) { + result->AddAttr(QN_TYPE, STR_INVISIBLE); + } + + if (s.show() != Status::SHOW_ONLINE && s.show() != Status::SHOW_OFFLINE) { + result->AddElement(new XmlElement(QN_SHOW)); + switch (s.show()) { + default: + result->AddText(STR_SHOW_AWAY, 1); + break; + case Status::SHOW_XA: + result->AddText(STR_SHOW_XA, 1); + break; + case Status::SHOW_DND: + result->AddText(STR_SHOW_DND, 1); + break; + case Status::SHOW_CHAT: + result->AddText(STR_SHOW_CHAT, 1); + break; + } + } + + result->AddElement(new XmlElement(QN_STATUS)); + result->AddText(s.status(), 1); + + std::string pri; + ToString(s.priority(), &pri); + + result->AddElement(new XmlElement(QN_PRIORITY)); + result->AddText(pri, 1); + + if (s.know_capabilities() && s.is_google_client()) { + result->AddElement(new XmlElement(QN_CAPS_C, true)); + result->AddAttr(QN_NODE, GOOGLE_CLIENT_NODE, 1); + result->AddAttr(QN_VER, s.version(), 1); + result->AddAttr(QN_EXT, s.phone_capability() ? "voice-v1" : "", 1); + } + + // Put the delay mark on the presence according to JEP-0091 + { + result->AddElement(new XmlElement(kQnDelayX, true)); + + // This here is why we *love* the C runtime + time_t current_time_seconds; + time(¤t_time_seconds); + struct tm* current_time = gmtime(¤t_time_seconds); + char output[256]; + strftime(output, ARRAY_SIZE(output), "%Y%m%dT%H:%M:%S", current_time); + result->AddAttr(kQnStamp, output, 1); + } + + } + + return result; +} + + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presenceouttask.h b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presenceouttask.h new file mode 100644 index 00000000..868bda59 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presenceouttask.h @@ -0,0 +1,46 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google 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 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 _PRESENCEOUTTASK_H_ +#define _PRESENCEOUTTASK_H_ + +#include "talk/xmpp/xmppengine.h" +#include "talk/xmpp/xmpptask.h" +#include "talk/examples/call/status.h" + +namespace buzz { + +class PresenceOutTask : public XmppTask { +public: + PresenceOutTask(Task * parent) : XmppTask(parent) {} + virtual ~PresenceOutTask() {} + + XmppReturnStatus Send(const Status & s); + XmppReturnStatus SendDirected(const Jid & j, const Status & s); + XmppReturnStatus SendProbe(const Jid& jid); + + virtual int ProcessStart(); +private: + XmlElement * TranslateStatus(const Status & s); + scoped_ptr stanza_; +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presencepushtask.cc b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presencepushtask.cc new file mode 100644 index 00000000..d0543b99 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presencepushtask.cc @@ -0,0 +1,172 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google 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 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "talk/examples/call/presencepushtask.h" +#include "talk/xmpp/constants.h" +#include + + +namespace buzz { + +// string helper functions ----------------------------------------------------- +template static +bool FromString(const std::string& s, + T * t) { + std::istringstream iss(s); + return !(iss>>*t).fail(); +} + +template static +bool ToString(const T &t, + std::string* s) { + std::ostringstream oss; + oss << t; + *s = oss.str(); + return !oss.fail(); +} + +static bool +IsXmlSpace(int ch) { + return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t'; +} + +static bool +ListContainsToken(const std::string & list, const std::string & token) { + size_t i = list.find(token); + if (i == std::string::npos || token.empty()) + return false; + bool boundary_before = (i == 0 || IsXmlSpace(list[i - 1])); + bool boundary_after = (i == list.length() - token.length() || IsXmlSpace(list[i + token.length()])); + return boundary_before && boundary_after; +} + + +bool +PresencePushTask::HandleStanza(const XmlElement * stanza) { + if (stanza->Name() != QN_PRESENCE) + return false; + if (stanza->HasAttr(QN_TYPE) && stanza->Attr(QN_TYPE) != STR_UNAVAILABLE) + return false; + QueueStanza(stanza); + return true; +} + +static bool IsUtf8FirstByte(int c) { + return (((c)&0x80)==0) || // is single byte + ((unsigned char)((c)-0xc0)<0x3e); // or is lead byte +} + +int +PresencePushTask::ProcessStart() { + const XmlElement * stanza = NextStanza(); + if (stanza == NULL) + return STATE_BLOCKED; + Status s; + + s.set_jid(Jid(stanza->Attr(QN_FROM))); + + if (stanza->Attr(QN_TYPE) == STR_UNAVAILABLE) { + s.set_available(false); + SignalStatusUpdate(s); + } + else { + s.set_available(true); + const XmlElement * status = stanza->FirstNamed(QN_STATUS); + if (status != NULL) { + s.set_status(status->BodyText()); + + // Truncate status messages longer than 300 bytes + if (s.status().length() > 300) { + size_t len = 300; + + // Be careful not to split legal utf-8 chars in half + while (!IsUtf8FirstByte(s.status()[len]) && len > 0) { + len -= 1; + } + std::string truncated(s.status(), 0, len); + s.set_status(truncated); + } + } + + const XmlElement * priority = stanza->FirstNamed(QN_PRIORITY); + if (priority != NULL) { + int pri; + if (FromString(priority->BodyText(), &pri)) { + s.set_priority(pri); + } + } + + const XmlElement * show = stanza->FirstNamed(QN_SHOW); + if (show == NULL || show->FirstChild() == NULL) { + s.set_show(Status::SHOW_ONLINE); + } + else { + if (show->BodyText() == "away") { + s.set_show(Status::SHOW_AWAY); + } + else if (show->BodyText() == "xa") { + s.set_show(Status::SHOW_XA); + } + else if (show->BodyText() == "dnd") { + s.set_show(Status::SHOW_DND); + } + else if (show->BodyText() == "chat") { + s.set_show(Status::SHOW_CHAT); + } + else { + s.set_show(Status::SHOW_ONLINE); + } + } + + const XmlElement * caps = stanza->FirstNamed(QN_CAPS_C); + if (caps != NULL) { + std::string node = caps->Attr(QN_NODE); + std::string ver = caps->Attr(QN_VER); + std::string exts = caps->Attr(QN_EXT); + + s.set_know_capabilities(true); + + if (node == GOOGLE_CLIENT_NODE) { + s.set_is_google_client(true); + s.set_version(ver); + if (ListContainsToken(exts, "voice-v1")) { + s.set_phone_capability(true); + } + } + } + + const XmlElement* delay = stanza->FirstNamed(kQnDelayX); + if (delay != NULL) { + // Ideally we would parse this according to the Psuedo ISO-8601 rules + // that are laid out in JEP-0082: + // http://www.jabber.org/jeps/jep-0082.html + std::string stamp = delay->Attr(kQnStamp); + s.set_sent_time(stamp); + } + + SignalStatusUpdate(s); + } + + return STATE_START; +} + + +} + + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presencepushtask.h b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presencepushtask.h new file mode 100644 index 00000000..45bc9020 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/presencepushtask.h @@ -0,0 +1,44 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google 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 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 _PRESENCEPUSHTASK_H_ +#define _PRESENCEPUSHTASK_H_ + +#include "talk/xmpp/xmppengine.h" +#include "talk/xmpp/xmpptask.h" +#include "talk/base/sigslot.h" +#include "talk/examples/call/status.h" + +namespace buzz { + +class PresencePushTask : public XmppTask { + +public: + PresencePushTask(Task * parent) : XmppTask(parent, XmppEngine::HL_TYPE) {} + virtual int ProcessStart(); + sigslot::signal1SignalStatusUpdate; + +protected: + virtual bool HandleStanza(const XmlElement * stanza); +}; + + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/status.h b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/status.h new file mode 100644 index 00000000..a1e76f62 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/call/status.h @@ -0,0 +1,213 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google 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 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 _STATUS_H_ +#define _STATUS_H_ + +#include "talk/xmpp/jid.h" +#include "talk/xmpp/constants.h" + +#define GOOGLE_CLIENT_NODE "http://www.google.com/xmpp/client/caps" + +namespace buzz { + +class Status { +public: + Status() : + pri_(0), + show_(SHOW_NONE), + available_(false), + invisible_(false), + e_code_(0), + phone_capability_(false), + know_capabilities_(false), + is_google_client_(false), + feedback_probation_(false) {}; + + ~Status() {} + + // These are arranged in "priority order", i.e., if we see + // two statuses at the same priority but with different Shows, + // we will show the one with the highest show in the following + // order. + enum Show { + SHOW_NONE = 0, + SHOW_INVISIBLE = 1, + SHOW_OFFLINE = 2, + SHOW_XA = 3, + SHOW_AWAY = 4, + SHOW_DND = 5, + SHOW_ONLINE = 6, + SHOW_CHAT = 7, + }; + + const Jid & jid() const { return jid_; } + int priority() const { return pri_; } + Show show() const { return show_; } + const std::string & status() const { return status_; } + bool available() const { return available_ ; } + bool invisible() const { return invisible_; } + int error_code() const { return e_code_; } + const std::string & error_string() const { return e_str_; } + bool know_capabilities() const { return know_capabilities_; } + bool phone_capability() const { return phone_capability_; } + bool is_google_client() const { return is_google_client_; } + const std::string & version() const { return version_; } + bool feedback_probation() const { return feedback_probation_; } + const std::string& sent_time() const { return sent_time_; } + + void set_jid(const Jid & jid) { jid_ = jid; } + void set_priority(int pri) { pri_ = pri; } + void set_show(Show show) { show_ = show; } + void set_status(const std::string & status) { status_ = status; } + void set_available(bool a) { available_ = a; } + void set_invisible(bool i) { invisible_ = i; } + void set_error(int e_code, const std::string e_str) + { e_code_ = e_code; e_str_ = e_str; } + void set_know_capabilities(bool f) { know_capabilities_ = f; } + void set_phone_capability(bool f) { phone_capability_ = f; } + void set_is_google_client(bool f) { is_google_client_ = f; } + void set_version(const std::string & v) { version_ = v; } + void set_feedback_probation(bool f) { feedback_probation_ = f; } + void set_sent_time(const std::string& time) { sent_time_ = time; } + + void UpdateWith(const Status & new_value) { + if (!new_value.know_capabilities()) { + bool k = know_capabilities(); + bool i = is_google_client(); + bool p = phone_capability(); + std::string v = version(); + + *this = new_value; + + set_know_capabilities(k); + set_is_google_client(i); + set_phone_capability(p); + set_version(v); + } + else { + *this = new_value; + } + } + + bool HasQuietStatus() const { + if (status_.empty()) + return false; + return !(QuietStatus().empty()); + } + + // Knowledge of other clients' silly automatic status strings - + // Don't show these. + std::string QuietStatus() const { + if (jid_.resource().find("Psi") != std::string::npos) { + if (status_ == "Online" || + status_.find("Auto Status") != std::string::npos) + return STR_EMPTY; + } + if (jid_.resource().find("Gaim") != std::string::npos) { + if (status_ == "Sorry, I ran out for a bit!") + return STR_EMPTY; + } + return TrimStatus(status_); + } + + std::string ExplicitStatus() const { + std::string result = QuietStatus(); + if (result.empty()) { + result = ShowStatus(); + } + return result; + } + + std::string ShowStatus() const { + std::string result; + if (!available()) { + result = "Offline"; + } + else { + switch (show()) { + case SHOW_AWAY: + case SHOW_XA: + result = "Idle"; + break; + case SHOW_DND: + result = "Busy"; + break; + case SHOW_CHAT: + result = "Chatty"; + break; + default: + result = "Available"; + break; + } + } + return result; + } + + static std::string TrimStatus(const std::string & st) { + std::string s(st); + int j = 0; + bool collapsing = true; + for (unsigned int i = 0; i < s.length(); i+= 1) { + if (s[i] <= ' ' && s[i] >= 0) { + if (collapsing) { + continue; + } + else { + s[j] = ' '; + j += 1; + collapsing = true; + } + } + else { + s[j] = s[i]; + j += 1; + collapsing = false; + } + } + if (collapsing && j > 0) { + j -= 1; + } + s.erase(j, s.length()); + return s; + } + +private: + Jid jid_; + int pri_; + Show show_; + std::string status_; + bool available_; + bool invisible_; + int e_code_; + std::string e_str_; + bool feedback_probation_; + + // capabilities (valid only if know_capabilities_ + bool know_capabilities_; + bool phone_capability_; + bool is_google_client_; + std::string version_; + + std::string sent_time_; // from the jabber:x:delay element +}; + +} + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/Makefile.am new file mode 100644 index 00000000..16164fb7 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/Makefile.am @@ -0,0 +1,15 @@ +noinst_LTLIBRARIES= libcricketexampleslogin.la +libcricketexampleslogin_la_SOURCES = xmppsocket.cc \ + xmppauth.cc \ + xmppthread.cc \ + xmpppump.cc +noinst_HEADERS = xmppauth.h xmpppump.h xmppsocket.h xmppthread.h +bin_PROGRAMS = login +login_CXXFLAGS = $(AM_CXXFLAGS) +login_SOURCES = login_main.cc xmppsocket.cc xmppthread.cc xmpppump.cc xmppauth.cc +login_LDADD = $(srcdir)/../../../talk/xmpp/libcricketxmpp.la \ + $(srcdir)/../../../talk/xmllite/libcricketxmllite.la \ + $(srcdir)/../../../talk/base/libcricketbase.la \ + $(EXPAT_LIBS) -lpthread +AM_CPPFLAGS = -DPOSIX +DEFAULT_INCLUDES = -I$(srcdir)/../../.. diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/login_main.cc b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/login_main.cc new file mode 100644 index 00000000..2939c79f --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/login_main.cc @@ -0,0 +1,63 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/base/thread.h" +#include "talk/xmpp/xmppclientsettings.h" +#include "talk/examples/login/xmppthread.h" +#include + +int main(int argc, char **argv) { + printf("Auth Cookie: "); + fflush(stdout); + + char auth_cookie[256]; + scanf("%s", auth_cookie); + + char username[256]; + scanf("%s", username); + + // Start xmpp on a different thread + XmppThread thread; + thread.Start(); + + buzz::XmppClientSettings xcs; + xcs.set_user(username); + xcs.set_host("gmail.com"); + xcs.set_use_tls(false); + xcs.set_auth_cookie(auth_cookie); + xcs.set_server(cricket::SocketAddress("talk.google.com", 5222)); + thread.Login(xcs); + + // Use main thread for console input + std::string line; + while (std::getline(std::cin, line)) { + if (line == "quit") + break; + } + return 0; +} + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppauth.cc b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppauth.cc new file mode 100644 index 00000000..66191f12 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppauth.cc @@ -0,0 +1,93 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include +#include "talk/examples/login/xmppauth.h" +#include "talk/xmpp/saslcookiemechanism.h" +#include "talk/xmpp/saslplainmechanism.h" + +XmppAuth::XmppAuth() : done_(false), error_(false) { +} + +XmppAuth::~XmppAuth() { +} + +void XmppAuth::StartPreXmppAuth(const buzz::Jid & jid, + const cricket::SocketAddress & server, + const buzz::XmppPassword & pass, + const std::string & auth_cookie) { + jid_ = jid; + passwd_ = pass; + auth_cookie_ = auth_cookie; + error_ = auth_cookie.empty(); + done_ = true; + + SignalAuthDone(); +} + +std::string XmppAuth::ChooseBestSaslMechanism( + const std::vector & mechanisms, + bool encrypted) { + std::vector::const_iterator it; + + // a token is the weakest auth - 15s, service-limited, so prefer it. + it = std::find(mechanisms.begin(), mechanisms.end(), "X-GOOGLE-TOKEN"); + if (it != mechanisms.end()) + return "X-GOOGLE-TOKEN"; + + // a cookie is the next weakest - 14 days + it = std::find(mechanisms.begin(), mechanisms.end(), "X-GOOGLE-COOKIE"); + if (it != mechanisms.end()) + return "X-GOOGLE-COOKIE"; + + // never pass @google.com passwords without encryption!! + if (!encrypted && (jid_.domain() == "google.com")) + return ""; + + // as a last resort, use plain authentication + if (jid_.domain() != "google.com") { + it = std::find(mechanisms.begin(), mechanisms.end(), "PLAIN"); + if (it != mechanisms.end()) + return "PLAIN"; + } + + // No good mechanism found + return ""; +} + +buzz::SaslMechanism* XmppAuth::CreateSaslMechanism( + const std::string & mechanism) { + if (mechanism == "X-GOOGLE-TOKEN") { + return new buzz::SaslCookieMechanism(mechanism, jid_.Str(), auth_cookie_); + //} else if (mechanism == "X-GOOGLE-COOKIE") { + // return new buzz::SaslCookieMechanism(mechanism, jid.Str(), sid_); + } else if (mechanism == "PLAIN") { + return new buzz::SaslPlainMechanism(jid_, passwd_); + } else { + return NULL; + } +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppauth.h b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppauth.h new file mode 100644 index 00000000..57743f90 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppauth.h @@ -0,0 +1,71 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _XMPPAUTH_H_ +#define _XMPPAUTH_H_ + +#include + +#include "talk/base/sigslot.h" +#include "talk/xmpp/jid.h" +#include "talk/xmpp/saslhandler.h" +#include "talk/xmpp/prexmppauth.h" +#include "talk/xmpp/xmpppassword.h" + +class XmppAuth: public buzz::PreXmppAuth { +public: + XmppAuth(); + virtual ~XmppAuth(); + + virtual void StartPreXmppAuth(const buzz::Jid & jid, + const cricket::SocketAddress & server, + const buzz::XmppPassword & pass, + const std::string & auth_cookie); + + virtual bool IsAuthDone() { return done_; } + virtual bool IsAuthorized() { return !error_; } + virtual bool HadError() { return error_; } + virtual buzz::CaptchaChallenge GetCaptchaChallenge() { + return buzz::CaptchaChallenge(); + } + virtual std::string GetAuthCookie() { return auth_cookie_; } + + virtual std::string ChooseBestSaslMechanism( + const std::vector & mechanisms, + bool encrypted); + + virtual buzz::SaslMechanism * CreateSaslMechanism( + const std::string & mechanism); + +private: + buzz::Jid jid_; + buzz::XmppPassword passwd_; + std::string auth_cookie_; + bool done_, error_; +}; + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmpppump.cc b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmpppump.cc new file mode 100644 index 00000000..7d966fb3 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmpppump.cc @@ -0,0 +1,73 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/examples/login/xmpppump.h" +#include "talk/examples/login/xmppauth.h" + +XmppPump::XmppPump(XmppPumpNotify * notify) { + state_ = buzz::XmppEngine::STATE_NONE; + notify_ = notify; + client_ = new buzz::XmppClient(this); // NOTE: deleted by TaskRunner +} + +void XmppPump::DoLogin(const buzz::XmppClientSettings & xcs, + buzz::AsyncSocket* socket, + buzz::PreXmppAuth* auth) { + OnStateChange(buzz::XmppEngine::STATE_START); + client_->SignalStateChange.connect(this, &XmppPump::OnStateChange); + client_->Connect(xcs, socket, auth); + client_->Start(); +} + +void XmppPump::DoDisconnect() { + client_->Disconnect(); + OnStateChange(buzz::XmppEngine::STATE_CLOSED); +} + +void XmppPump::OnStateChange(buzz::XmppEngine::State state) { + if (state_ == state) + return; + state_ = state; + if (notify_ != NULL) + notify_->OnStateChange(state); +} + +void XmppPump::WakeTasks() { + cricket::Thread::Current()->Post(this); +} + +unsigned long long XmppPump::CurrentTime() { + return cricket::Time(); +} + +void XmppPump::OnMessage(cricket::Message *pmsg) { + RunTasks(); +} + +buzz::XmppReturnStatus XmppPump::SendStanza(const buzz::XmlElement *stanza) { + return client_->SendStanza(stanza); +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmpppump.h b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmpppump.h new file mode 100644 index 00000000..fd6f88bb --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmpppump.h @@ -0,0 +1,73 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _XMPPPUMP_H_ +#define _XMPPPUMP_H_ + +#include "talk/base/messagequeue.h" +#include "talk/base/taskrunner.h" +#include "talk/base/thread.h" +#include "talk/base/jtime.h" +#include "talk/xmpp/xmppclient.h" +#include "talk/xmpp/xmppengine.h" +#include "talk/xmpp/xmpptask.h" + +// Simple xmpp pump + +class XmppPumpNotify { +public: + virtual void OnStateChange(buzz::XmppEngine::State state) = 0; +}; + +class XmppPump : public cricket::MessageHandler, public buzz::TaskRunner { +public: + XmppPump(XmppPumpNotify * notify = NULL); + + buzz::XmppClient *client() { return client_; } + + void DoLogin(const buzz::XmppClientSettings & xcs, + buzz::AsyncSocket* socket, + buzz::PreXmppAuth* auth); + void DoDisconnect(); + + void OnStateChange(buzz::XmppEngine::State state); + + void WakeTasks(); + + unsigned long long CurrentTime(); + + void OnMessage(cricket::Message *pmsg); + + buzz::XmppReturnStatus SendStanza(const buzz::XmlElement *stanza); + +private: + buzz::XmppClient *client_; + buzz::XmppEngine::State state_; + XmppPumpNotify *notify_; +}; + +#endif // _XMPPPUMP_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppsocket.cc b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppsocket.cc new file mode 100644 index 00000000..33aabf3e --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppsocket.cc @@ -0,0 +1,144 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include +#include "talk/base/basicdefs.h" +#include "talk/base/logging.h" +#include "talk/base/thread.h" +#ifdef FEATURE_ENABLE_SSL +#include "talk/base/schanneladapter.h" +#endif +#include "xmppsocket.h" + +XmppSocket::XmppSocket(bool tls) : tls_(tls) { + cricket::Thread* pth = cricket::Thread::Current(); + cricket::AsyncSocket* socket = + pth->socketserver()->CreateAsyncSocket(SOCK_STREAM); +#ifdef FEATURE_ENABLE_SSL + if (tls_) + socket = new cricket::SChannelAdapter(socket); +#endif + cricket_socket_ = socket; + cricket_socket_->SignalReadEvent.connect(this, &XmppSocket::OnReadEvent); + cricket_socket_->SignalWriteEvent.connect(this, &XmppSocket::OnWriteEvent); + cricket_socket_->SignalConnectEvent.connect(this, + &XmppSocket::OnConnectEvent); + state_ = buzz::AsyncSocket::STATE_CLOSED; +} + +XmppSocket::~XmppSocket() { + Close(); + delete cricket_socket_; +} + +void XmppSocket::OnReadEvent(cricket::AsyncSocket * socket) { + SignalRead(); +} + +void XmppSocket::OnWriteEvent(cricket::AsyncSocket * socket) { + // Write bytes if there are any + while (buffer_.Length() != 0) { + int written = cricket_socket_->Send(buffer_.Data(), buffer_.Length()); + if (written > 0) { + buffer_.Shift(written); + continue; + } + if (!cricket_socket_->IsBlocking()) + LOG(LS_ERROR) << "Send error: " << cricket_socket_->GetError(); + return; + } +} + +void XmppSocket::OnConnectEvent(cricket::AsyncSocket * socket) { +#if defined(FEATURE_ENABLE_SSL) + if (state_ == buzz::AsyncSocket::STATE_TLS_CONNECTING) { + state_ = buzz::AsyncSocket::STATE_TLS_OPEN; + SignalSSLConnected(); + OnWriteEvent(cricket_socket_); + return; + } +#endif // !defined(FEATURE_ENABLE_SSL) + state_ = buzz::AsyncSocket::STATE_OPEN; + SignalConnected(); +} + +buzz::AsyncSocket::State XmppSocket::state() { + return state_; +} + +buzz::AsyncSocket::Error XmppSocket::error() { + return buzz::AsyncSocket::ERROR_NONE; +} + +bool XmppSocket::Connect(const cricket::SocketAddress& addr) { + if (cricket_socket_->Connect(addr) < 0) { + return cricket_socket_->IsBlocking(); + } + return true; +} + +bool XmppSocket::Read(char * data, size_t len, size_t* len_read) { + int read = cricket_socket_->Recv(data, len); + if (read > 0) { + *len_read = (size_t)read; + return true; + } + return false; +} + +bool XmppSocket::Write(const char * data, size_t len) { + buffer_.WriteBytes(data, len); + OnWriteEvent(cricket_socket_); + return true; +} + +bool XmppSocket::Close() { + if (state_ != buzz::AsyncSocket::STATE_OPEN) + return false; + if (cricket_socket_->Close() == 0) { + state_ = buzz::AsyncSocket::STATE_CLOSED; + SignalClosed(); + return true; + } + return false; +} + +bool XmppSocket::StartTls(const std::string & domainname) { +#if defined(FEATURE_ENABLE_SSL) + if (!tls_) + return false; + cricket::SChannelAdapter * ssl = + static_cast(cricket_socket_); + ssl->set_ignore_bad_cert(true); + if (ssl->StartSSL(domainname.c_str(), false) != 0) + return false; + state_ = buzz::AsyncSocket::STATE_TLS_CONNECTING; + return true; +#else // !defined(FEATURE_ENABLE_SSL) + return false; +#endif // !defined(FEATURE_ENABLE_SSL) +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppsocket.h b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppsocket.h new file mode 100644 index 00000000..f6c53d7d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppsocket.h @@ -0,0 +1,63 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _XMPPSOCKET_H_ +#define _XMPPSOCKET_H_ + +#include "talk/base/asyncsocket.h" +#include "talk/base/bytebuffer.h" +#include "talk/base/sigslot.h" +#include "talk/xmpp/asyncsocket.h" + +extern cricket::AsyncSocket* cricket_socket_; + +class XmppSocket : public buzz::AsyncSocket, public sigslot::has_slots<> { +public: + XmppSocket(bool tls); + ~XmppSocket(); + + virtual buzz::AsyncSocket::State state(); + virtual buzz::AsyncSocket::Error error(); + + virtual bool Connect(const cricket::SocketAddress& addr); + virtual bool Read(char * data, size_t len, size_t* len_read); + virtual bool Write(const char * data, size_t len); + virtual bool Close(); + virtual bool StartTls(const std::string & domainname); + +private: + void OnReadEvent(cricket::AsyncSocket * socket); + void OnWriteEvent(cricket::AsyncSocket * socket); + void OnConnectEvent(cricket::AsyncSocket * socket); + + cricket::AsyncSocket * cricket_socket_; + buzz::AsyncSocket::State state_; + cricket::ByteBuffer buffer_; + bool tls_; +}; + +#endif // _XMPPSOCKET_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppthread.cc b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppthread.cc new file mode 100644 index 00000000..7a1b2a13 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppthread.cc @@ -0,0 +1,80 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/xmpp/xmppclientsettings.h" +#include "talk/examples/login/xmppthread.h" +#include "talk/examples/login/xmppauth.h" + +namespace { + +const uint32 MSG_LOGIN = 1; +const uint32 MSG_DISCONNECT = 2; + +struct LoginData: public cricket::MessageData { + LoginData(const buzz::XmppClientSettings& s) : xcs(s) {} + virtual ~LoginData() {} + + buzz::XmppClientSettings xcs; +}; + +} // namespace + +XmppThread::XmppThread() { + pump_ = new XmppPump(this); +} + +XmppThread::~XmppThread() { + delete pump_; +} + +void XmppThread::Loop(int cms) { + Thread::Loop(cms); +} + +void XmppThread::Login(const buzz::XmppClientSettings& xcs) { + Post(this, MSG_LOGIN, new LoginData(xcs)); +} + +void XmppThread::Disconnect() { + Post(this, MSG_DISCONNECT); +} + +void XmppThread::OnStateChange(buzz::XmppEngine::State state) { +} + +void XmppThread::OnMessage(cricket::Message* pmsg) { + if (pmsg->message_id == MSG_LOGIN) { + assert(pmsg->pdata); + LoginData* data = reinterpret_cast(pmsg->pdata); + pump_->DoLogin(data->xcs, new XmppSocket(false), new XmppAuth()); + delete data; + } else if (pmsg->message_id == MSG_DISCONNECT) { + pump_->DoDisconnect(); + } else { + assert(false); + } +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppthread.h b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppthread.h new file mode 100644 index 00000000..533a2376 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/examples/login/xmppthread.h @@ -0,0 +1,57 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _XMPPTHREAD_H_ +#define _XMPPTHREAD_H_ + +#include "talk/xmpp/xmppclientsettings.h" +#include "talk/base/thread.h" +#include "talk/examples/login/xmpppump.h" +#include "talk/examples/login/xmppsocket.h" +#include + +class XmppThread: + public cricket::Thread, XmppPumpNotify, cricket::MessageHandler { +public: + XmppThread(); + ~XmppThread(); + + buzz::XmppClient* client() { return pump_->client(); } + + void Loop(int cms); + + void Login(const buzz::XmppClientSettings & xcs); + void Disconnect(); + +private: + XmppPump* pump_; + + void OnStateChange(buzz::XmppEngine::State state); + void OnMessage(cricket::Message* pmsg); +}; + +#endif // _XMPPTHREAD_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/Makefile.am new file mode 100644 index 00000000..c935a6bd --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=base client diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/Makefile.am new file mode 100644 index 00000000..6cc30f15 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/Makefile.am @@ -0,0 +1,47 @@ +## Does not compile with final +KDE_OPTIONS = nofinal + +libcricketp2pbase_la_SOURCES = stun.cc \ + port.cc \ + udpport.cc \ + tcpport.cc \ + helpers.cc \ + sessionmanager.cc \ + session.cc \ + p2psocket.cc \ + relayport.cc \ + stunrequest.cc \ + stunport.cc \ + socketmanager.cc + +noinst_HEADERS = candidate.h \ + portallocator.h \ + relayport.h \ + session.h \ + sessionmessage.h \ + stunport.h \ + tcpport.h \ + helpers.h \ + port.h \ + sessionid.h \ + socketmanager.h \ + stunrequest.h \ + udpport.h \ + p2psocket.h \ + sessiondescription.h \ + sessionmanager.h \ + stun.h \ + relayserver.h \ + stunserver.h + +AM_CPPFLAGS = -DPOSIX $(all_includes) -I$(srcdir)/../../.. + +bin_PROGRAMS = relayserver stunserver +relayserver_SOURCES = relayserver.cc relayserver_main.cc +relayserver_LDADD = ../../base/libcricketbase.la libcricketp2pbase.la -lpthread +stunserver_SOURCES = stunserver.cc stunserver_main.cc +stunserver_LDADD = ../../base/libcricketbase.la libcricketp2pbase.la -lpthread + +noinst_LTLIBRARIES = libcricketp2pbase.la + +DEFAULT_INCLUDES = -I$(srcdir)/../../.. diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/candidate.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/candidate.h new file mode 100644 index 00000000..c2f974b8 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/candidate.h @@ -0,0 +1,118 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _CANDIDATE_H_ +#define _CANDIDATE_H_ + +#include +#include +#include "talk/base/socketaddress.h" + +namespace cricket { + +// Candidate for ICE based connection discovery. + +class Candidate { +public: + + const std::string & name() const { return name_; } + void set_name(const std::string & name) { name_ = name; } + + const std::string & protocol() const { return protocol_; } + void set_protocol(const std::string & protocol) { protocol_ = protocol; } + + const SocketAddress & address() const { return address_; } + void set_address(const SocketAddress & address) { address_ = address; } + + const float preference() const { return preference_; } + void set_preference(const float preference) { preference_ = preference; } + const std::string preference_str() const { + std::ostringstream ost; + ost << preference_; + return ost.str(); + } + void set_preference_str(const std::string & preference) { + std::istringstream ist(preference); + ist >> preference_; + } + + const std::string & username() const { return username_; } + void set_username(const std::string & username) { username_ = username; } + + const std::string & password() const { return password_; } + void set_password(const std::string & password) { password_ = password; } + + const std::string & type() const { return type_; } + void set_type(const std::string & type) { type_ = type; } + + const std::string & network_name() const { return network_name_; } + void set_network_name(const std::string & network_name) { + network_name_ = network_name; + } + + // Candidates in a new generation replace those in the old generation. + uint32 generation() const { return generation_; } + void set_generation(uint32 generation) { generation_ = generation; } + const std::string generation_str() const { + std::ostringstream ost; + ost << generation_; + return ost.str(); + } + void set_generation_str(const std::string& str) { + std::istringstream ist(str); + ist >> generation_; + } + + // Determines whether this candidate is equivalent to the given one. + bool IsEquivalent(const Candidate& c) const { + // We ignore the network name, since that is just debug information, and + // the preference, since that should be the same if the rest is (and it's + // a float so equality checking is always worrisome). + return (name_ == c.name_) && + (protocol_ == c.protocol_) && + (address_ == c.address_) && + (username_ == c.username_) && + (password_ == c.password_) && + (type_ == c.type_) && + (generation_ == c.generation_); + } + +private: + std::string name_; + std::string protocol_; + SocketAddress address_; + float preference_; + std::string username_; + std::string password_; + std::string type_; + std::string network_name_; + uint32 generation_; +}; + +} // namespace cricket + +#endif // _CANDIDATE_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/helpers.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/helpers.cc new file mode 100644 index 00000000..83e02a27 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/helpers.cc @@ -0,0 +1,129 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/p2p/base/helpers.h" +#include "talk/base/jtime.h" +#include +#include + +// TODO: Change this implementation to use OpenSSL's RAND_bytes. That will +// give cryptographically random values on all platforms. + +#ifdef WIN32 +#include +#include +#endif + +namespace cricket { + +static long g_seed = 1L; + +int GetRandom() { + return ((g_seed = g_seed * 214013L + 2531011L) >> 16) & 0x7fff; +} + +void SetRandomSeed(unsigned long seed) +{ + g_seed = (long)seed; +} + +static bool s_initrandom; + +void InitRandom(const char *client_unique, size_t len) { + s_initrandom = true; + + // Hash this string - unique per client + + uint32 hash = 0; + if (client_unique != NULL) { + for (int i = 0; i < (int)len; i++) + hash = ((hash << 2) + hash) + client_unique[i]; + } + + // Now initialize the seed against a high resolution + // counter + +#ifdef WIN32 + LARGE_INTEGER big; + QueryPerformanceCounter(&big); + SetRandomSeed(big.LowPart ^ hash); +#else + SetRandomSeed(Time() ^ hash); +#endif +} + +const char BASE64[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', '+', '/' +}; + +// Generates a random string of the given length. We generate base64 values so +// that they will be printable, though that's not necessary. + +std::string CreateRandomString(int len) { + // Random number generator should of been initialized! + assert(s_initrandom); + if (!s_initrandom) + InitRandom(0, 0); + + std::string str; + for (int i = 0; i < len; i++) +#if defined(_MSC_VER) && _MSC_VER < 1300 + str.insert(str.end(), BASE64[GetRandom() & 63]); +#else + str.push_back(BASE64[GetRandom() & 63]); +#endif + return str; +} + +uint32 CreateRandomId() { + uint8 b1 = (uint8)(GetRandom() & 255); + uint8 b2 = (uint8)(GetRandom() & 255); + uint8 b3 = (uint8)(GetRandom() & 255); + uint8 b4 = (uint8)(GetRandom() & 255); + return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24); +} + +bool IsBase64Char(char ch) { + return (('A' <= ch) && (ch <= 'Z')) || + (('a' <= ch) && (ch <= 'z')) || + (('0' <= ch) && (ch <= '9')) || + (ch == '+') || (ch == '/'); +} + +bool IsBase64Encoded(const std::string& str) { + for (size_t i = 0; i < str.size(); ++i) { + if (!IsBase64Char(str.at(i))) + return false; + } + return true; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/helpers.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/helpers.h new file mode 100644 index 00000000..1c8dfa7f --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/helpers.h @@ -0,0 +1,51 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __HELPERS_H__ +#define __HELPERS_H__ + +#include "talk/base/basictypes.h" +#include + +namespace cricket { + +// srand initializer +void InitRandom(const char *client_unique, size_t len); + +// Generates a (cryptographically) random string of the given length. +std::string CreateRandomString(int length); + +// Generates a random id +uint32 CreateRandomId(); + +// Determines whether the given string consists entirely of valid base64 +// encoded characters. +bool IsBase64Encoded(const std::string& str); + +} // namespace cricket + +#endif // __HELPERS_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/p2psocket.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/p2psocket.cc new file mode 100644 index 00000000..eb53efeb --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/p2psocket.cc @@ -0,0 +1,910 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +// Description of the P2PSocket class in P2PSocket.h +// +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include +#include +#include "talk/base/logging.h" +#include "talk/p2p/base/p2psocket.h" +#include +namespace { + +// messages for queuing up work for ourselves +const uint32 MSG_SORT = 1; +const uint32 MSG_PING = 2; +const uint32 MSG_ALLOCATE = 3; + +// When the socket is unwritable, we will use 10 Kbps (ignoring IP+UDP headers) +// for pinging. When the socket is writable, we will use only 1 Kbps because +// we don't want to degrade the quality on a modem. These numbers should work +// well on a 28.8K modem, which is the slowest connection on which the voice +// quality is reasonable at all. +static const uint32 PING_PACKET_SIZE = 60 * 8; +static const uint32 WRITABLE_DELAY = 1000 * PING_PACKET_SIZE / 1000; // 480ms +static const uint32 UNWRITABLE_DELAY = 1000 * PING_PACKET_SIZE / 10000;// 50ms + +// If there is a current writable connection, then we will also try hard to +// make sure it is pinged at this rate. +static const uint32 MAX_CURRENT_WRITABLE_DELAY = 900; // 2*WRITABLE_DELAY - bit + +// The minimum improvement in MOS that justifies a switch. +static const double kMinImprovement = 10; + +// Amount of time that we wait when *losing* writability before we try doing +// another allocation. +static const int kAllocateDelay = 1 * 1000; // 1 second + +// We will try creating a new allocator from scratch after a delay of this +// length without becoming writable (or timing out). +static const int kAllocatePeriod = 20 * 1000; // 20 seconds + +cricket::Port::CandidateOrigin GetOrigin(cricket::Port* port, + cricket::Port* origin_port) { + if (!origin_port) + return cricket::Port::ORIGIN_MESSAGE; + else if (port == origin_port) + return cricket::Port::ORIGIN_THIS_PORT; + else + return cricket::Port::ORIGIN_OTHER_PORT; +} + +// Compares two connections based only on static information about them. +int CompareConnectionCandidates(cricket::Connection* a, + cricket::Connection* b) { + // Combine local and remote preferences + assert(a->local_candidate().preference() == a->port()->preference()); + assert(b->local_candidate().preference() == b->port()->preference()); + double a_pref = a->local_candidate().preference() + * a->remote_candidate().preference(); + double b_pref = b->local_candidate().preference() + * b->remote_candidate().preference(); + + // Now check combined preferences. Lower values get sorted last. + if (a_pref > b_pref) + return 1; + if (a_pref < b_pref) + return -1; + + return 0; +} + +// Compare two connections based on their writability and static preferences. +int CompareConnections(cricket::Connection *a, cricket::Connection *b) { + // Sort based on write-state. Better states have lower values. + if (a->write_state() < b->write_state()) + return 1; + if (a->write_state() > b->write_state()) + return -1; + + // Compare the candidate information. + return CompareConnectionCandidates(a, b); +} + +// Wraps the comparison connection into a less than operator that puts higher +// priority writable connections first. +class ConnectionCompare { +public: + bool operator()(const cricket::Connection *ca, + const cricket::Connection *cb) { + cricket::Connection* a = const_cast(ca); + cricket::Connection* b = const_cast(cb); + + // Compare first on writability and static preferences. + int cmp = CompareConnections(a, b); + if (cmp > 0) + return true; + if (cmp < 0) + return false; + + // Otherwise, sort based on latency estimate. + return a->rtt() < b->rtt(); + + // Should we bother checking for the last connection that last received + // data? It would help rendezvous on the connection that is also receiving + // packets. + // + // TODO: Yes we should definitely do this. The TCP protocol gains + // efficiency by being used bidirectionally, as opposed to two separate + // unidirectional streams. This test should probably occur before + // comparison of local prefs (assuming combined prefs are the same). We + // need to be careful though, not to bounce back and forth with both sides + // trying to rendevous with the other. + } +}; + +// Determines whether we should switch between two connections, based first on +// static preferences and then (if those are equal) on latency estimates. +bool ShouldSwitch(cricket::Connection* a_conn, cricket::Connection* b_conn) { + if (a_conn == b_conn) + return false; + + if ((a_conn == NULL) || (b_conn == NULL)) // don't think the latter should happen + return true; + + int prefs_cmp = CompareConnections(a_conn, b_conn); + if (prefs_cmp < 0) + return true; + if (prefs_cmp > 0) + return false; + + return b_conn->rtt() <= a_conn->rtt() + kMinImprovement; +} + +} // unnamed namespace + +namespace cricket { + +P2PSocket::P2PSocket(const std::string &name, PortAllocator *allocator) +: worker_thread_(Thread::Current()), name_(name), allocator_(allocator), + error_(0), state_(STATE_CONNECTING), waiting_for_signaling_(false), + best_connection_(NULL), pinging_started_(false), sort_dirty_(false), + was_writable_(false), was_timed_out_(true) { +} + +P2PSocket::~P2PSocket() { + assert(worker_thread_ == Thread::Current()); + + thread()->Clear(this); + + for (uint32 i = 0; i < allocator_sessions_.size(); ++i) + delete allocator_sessions_[i]; +} + +// Add the allocator session to our list so that we know which sessions +// are still active. +void P2PSocket::AddAllocatorSession(PortAllocatorSession* session) { + session->set_generation(static_cast(allocator_sessions_.size())); + allocator_sessions_.push_back(session); + + // We now only want to apply new candidates that we receive to the ports + // created by this new session because these are replacing those of the + // previous sessions. + ports_.clear(); + + session->SignalPortReady.connect(this, &P2PSocket::OnPortReady); + session->SignalCandidatesReady.connect(this, &P2PSocket::OnCandidatesReady); + session->GetInitialPorts(); + if (pinging_started_) + session->StartGetAllPorts(); +} + +// Go into the state of processing candidates, and running in general +void P2PSocket::StartProcessingCandidates() { + assert(worker_thread_ == Thread::Current()); + + // Kick off an allocator session + OnAllocate(); + + // Start pinging as the ports come in. + thread()->Post(this, MSG_PING); +} + +// Reset the socket, clear up any previous allocations and start over +void P2PSocket::Reset() { + assert(worker_thread_ == Thread::Current()); + + // Get rid of all the old allocators. This should clean up everything. + for (uint32 i = 0; i < allocator_sessions_.size(); ++i) + delete allocator_sessions_[i]; + + allocator_sessions_.clear(); + ports_.clear(); + connections_.clear(); + best_connection_ = NULL; + + // Forget about all of the candidates we got before. + remote_candidates_.clear(); + + // Revert to the connecting state. + set_state(STATE_CONNECTING); + + // Reinitialize the rest of our state. + waiting_for_signaling_ = false; + pinging_started_ = false; + sort_dirty_ = false; + was_writable_ = false; + was_timed_out_ = true; + + // Start a new allocator. + OnAllocate(); + + // Start pinging as the ports come in. + thread()->Clear(this); + thread()->Post(this, MSG_PING); +} + +// A new port is available, attempt to make connections for it +void P2PSocket::OnPortReady(PortAllocatorSession *session, Port* port) { + assert(worker_thread_ == Thread::Current()); + + // Set in-effect options on the new port + for (OptionMap::const_iterator it = options_.begin(); it != options_.end(); ++it) { + int val = port->SetOption(it->first, it->second); + if (val < 0) { + LOG(WARNING) << "SetOption(" << it->first << ", " << it->second << ") failed: " << port->GetError(); + } + } + + // Remember the ports and candidates, and signal that candidates are ready. + // The session will handle this, and send an initiate/accept/modify message + // if one is pending. + + ports_.push_back(port); + port->SignalUnknownAddress.connect(this, &P2PSocket::OnUnknownAddress); + port->SignalDestroyed.connect(this, &P2PSocket::OnPortDestroyed); + + // Attempt to create a connection from this new port to all of the remote + // candidates that we were given so far. + + std::vector::iterator iter; + for (iter = remote_candidates_.begin(); iter != remote_candidates_.end(); + ++iter) + CreateConnection(port, *iter, iter->origin_port(), false); + + SortConnections(); +} + +// A new candidate is available, let listeners know +void P2PSocket::OnCandidatesReady(PortAllocatorSession *session, + const std::vector& candidates) { + SignalCandidatesReady(this, candidates); +} + +// Handle stun packets +void P2PSocket::OnUnknownAddress(Port *port, + const SocketAddress &address, + StunMessage *stun_msg, + const std::string &remote_username) { + assert(worker_thread_ == Thread::Current()); + + // Port has received a valid stun packet from an address that no Connection + // is currently available for. See if the remote user name is in the remote + // candidate list. If it isn't return error to the stun request. + + const Candidate *candidate = NULL; + std::vector::iterator it; + for (it = remote_candidates_.begin(); it != remote_candidates_.end(); ++it) { + if ((*it).username() == remote_username) { + candidate = &(*it); + break; + } + } + if (candidate == NULL) { + // Don't know about this username, the request is bogus + // This sometimes happens if a binding response comes in before the ACCEPT + // message. It is totally valid; the retry state machine will try again. + + port->SendBindingErrorResponse(stun_msg, address, + STUN_ERROR_STALE_CREDENTIALS, STUN_ERROR_REASON_STALE_CREDENTIALS); + delete stun_msg; + return; + } + + // Check for connectivity to this address. Create connections + // to this address across all local ports. First, add this as a new remote + // address + + Candidate new_remote_candidate = *candidate; + new_remote_candidate.set_address(address); + //new_remote_candidate.set_protocol(port->protocol()); + + // This remote username exists. Now create connections using this candidate, + // and resort + + if (CreateConnections(new_remote_candidate, port, true)) { + // Send the pinger a successful stun response. + port->SendBindingResponse(stun_msg, address); + + // Update the list of connections since we just added another. We do this + // after sending the response since it could (in principle) delete the + // connection in question. + SortConnections(); + } else { + // Hopefully this won't occur, because changing a destination address + // shouldn't cause a new connection to fail + assert(false); + port->SendBindingErrorResponse(stun_msg, address, STUN_ERROR_SERVER_ERROR, + STUN_ERROR_REASON_SERVER_ERROR); + } + + delete stun_msg; +} + +// We received a candidate from the other side, make connections so we +// can try to use these remote candidates with our local candidates. +void P2PSocket::AddRemoteCandidates( + const std::vector &remote_candidates) { + assert(worker_thread_ == Thread::Current()); + + // The remote candidates have come in. Remember them and start to establish + // connections + + std::vector::const_iterator it; + for (it = remote_candidates.begin(); it != remote_candidates.end(); ++it) + CreateConnections(*it, NULL, false); + + // Resort the connections + + SortConnections(); +} + +// Creates connections from all of the ports that we care about to the given +// remote candidate. The return value is true iff we created a connection from +// the origin port. +bool P2PSocket::CreateConnections(const Candidate &remote_candidate, + Port* origin_port, + bool readable) { + assert(worker_thread_ == Thread::Current()); + + // Add a new connection for this candidate to every port that allows such a + // connection (i.e., if they have compatible protocols) and that does not + // already have a connection to an equivalent candidate. We must be careful + // to make sure that the origin port is included, even if it was pruned, + // since that may be the only port that can create this connection. + + bool created = false; + + std::vector::reverse_iterator it; + for (it = ports_.rbegin(); it != ports_.rend(); ++it) { + if (CreateConnection(*it, remote_candidate, origin_port, readable)) { + if (*it == origin_port) + created = true; + } + } + + if ((origin_port != NULL) && + find(ports_.begin(), ports_.end(), origin_port) == ports_.end()) { + if (CreateConnection(origin_port, remote_candidate, origin_port, readable)) + created = true; + } + + // Remember this remote candidate so that we can add it to future ports. + RememberRemoteCandidate(remote_candidate, origin_port); + + return created; +} + +// Setup a connection object for the local and remote candidate combination. +// And then listen to connection object for changes. +bool P2PSocket::CreateConnection(Port* port, + const Candidate& remote_candidate, + Port* origin_port, + bool readable) { + // Look for an existing connection with this remote address. If one is not + // found, then we can create a new connection for this address. + Connection* connection = port->GetConnection(remote_candidate.address()); + if (connection != NULL) { + // It is not legal to try to change any of the parameters of an existing + // connection; however, the other side can send a duplicate candidate. + if (!remote_candidate.IsEquivalent(connection->remote_candidate())) { + LOG(INFO) << "Attempt to change a remote candidate"; + return false; + } + } else { + Port::CandidateOrigin origin = GetOrigin(port, origin_port); + connection = port->CreateConnection(remote_candidate, origin); + if (!connection) + return false; + + connections_.push_back(connection); + connection->SignalReadPacket.connect(this, &P2PSocket::OnReadPacket); + connection->SignalStateChange.connect(this, &P2PSocket::OnConnectionStateChange); + connection->SignalDestroyed.connect(this, &P2PSocket::OnConnectionDestroyed); + } + + // If we are readable, it is because we are creating this in response to a + // ping from the other side. This will cause the state to become readable. + if (readable) + connection->ReceivedPing(); + + return true; +} + +// Maintain our remote candidate list, adding this new remote one. +void P2PSocket::RememberRemoteCandidate(const Candidate& remote_candidate, + Port* origin_port) { + // Remove any candidates whose generation is older than this one. The + // presence of a new generation indicates that the old ones are not useful. + uint32 i = 0; + while (i < remote_candidates_.size()) { + if (remote_candidates_[i].generation() < remote_candidate.generation()) { + remote_candidates_.erase(remote_candidates_.begin() + i); + LOG(INFO) << "Pruning candidate from old generation: " + << remote_candidates_[i].address().ToString(); + + } else { + i += 1; + } + } + + // Make sure this candidate is not a duplicate. + for (uint32 i = 0; i < remote_candidates_.size(); ++i) { + if (remote_candidates_[i].IsEquivalent(remote_candidate)) { + LOG(INFO) << "Duplicate candidate: " + << remote_candidate.address().ToString(); + return; + } + } + + // Try this candidate for all future ports. + remote_candidates_.push_back(RemoteCandidate(remote_candidate, origin_port)); + + // We have some candidates from the other side, we are now serious about + // this connection. Let's do the StartGetAllPorts thing. + if (!pinging_started_) { + pinging_started_ = true; + for (size_t i = 0; i < allocator_sessions_.size(); ++i) { + if (!allocator_sessions_[i]->IsGettingAllPorts()) + allocator_sessions_[i]->StartGetAllPorts(); + } + } +} + +// Send data to the other side, using our best connection +int P2PSocket::Send(const char *data, size_t len) { + // This can get called on any thread that is convenient to write from! + if (best_connection_ == NULL) { + error_ = EWOULDBLOCK; + return SOCKET_ERROR; + } + int sent = best_connection_->Send(data, len); + if (sent <= 0) { + assert(sent < 0); + error_ = best_connection_->GetError(); + } + return sent; +} + +// Monitor connection states +void P2PSocket::UpdateConnectionStates() { + uint32 now = Time(); + + // We need to copy the list of connections since some may delete themselves + // when we call UpdateState. + for (uint32 i = 0; i < connections_.size(); ++i) + connections_[i]->UpdateState(now); +} + +// Prepare for best candidate sorting +void P2PSocket::RequestSort() { + if (!sort_dirty_) { + worker_thread_->Post(this, MSG_SORT); + sort_dirty_ = true; + } +} + +// Sort the available connections to find the best one. We also monitor +// the number of available connections and the current state so that we +// can possibly kick off more allocators (for more connections). +void P2PSocket::SortConnections() { + assert(worker_thread_ == Thread::Current()); + + // Make sure the connection states are up-to-date since this affects how they + // will be sorted. + UpdateConnectionStates(); + + // Any changes after this point will require a re-sort. + sort_dirty_ = false; + + // Get a list of the networks that we are using. + std::set networks; + for (uint32 i = 0; i < connections_.size(); ++i) + networks.insert(connections_[i]->port()->network()); + + // Find the best alternative connection by sorting. It is important to note + // that amongst equal preference, writable connections, this will choose the + // one whose estimated latency is lowest. So it is the only one that we + // need to consider switching to. + + ConnectionCompare cmp; + std::stable_sort(connections_.begin(), connections_.end(), cmp); + Connection* top_connection = NULL; + if (connections_.size() > 0) + top_connection = connections_[0]; + + // If necessary, switch to the new choice. + if (ShouldSwitch(best_connection_, top_connection)) + SwitchBestConnectionTo(top_connection); + + // We can prune any connection for which there is a writable connection on + // the same network with better or equal prefences. We leave those with + // better preference just in case they become writable later (at which point, + // we would prune out the current best connection). We leave connections on + // other networks because they may not be using the same resources and they + // may represent very distinct paths over which we can switch. + std::set::iterator network; + for (network = networks.begin(); network != networks.end(); ++network) { + Connection* primier = GetBestConnectionOnNetwork(*network); + if (!primier || (primier->write_state() != Connection::STATE_WRITABLE)) + continue; + + for (uint32 i = 0; i < connections_.size(); ++i) { + if ((connections_[i] != primier) && + (connections_[i]->port()->network() == *network) && + (CompareConnectionCandidates(primier, connections_[i]) >= 0)) { + connections_[i]->Prune(); + } + } + } + + // Count the number of connections in the various states. + + int writable = 0; + int write_connect = 0; + int write_timeout = 0; + + for (uint32 i = 0; i < connections_.size(); ++i) { + switch (connections_[i]->write_state()) { + case Connection::STATE_WRITABLE: + ++writable; + break; + case Connection::STATE_WRITE_CONNECT: + ++write_connect; + break; + case Connection::STATE_WRITE_TIMEOUT: + ++write_timeout; + break; + default: + assert(false); + } + } + + if (writable > 0) { + HandleWritable(); + } else if (write_connect > 0) { + HandleNotWritable(); + } else { + HandleAllTimedOut(); + } + + // Notify of connection state change + SignalConnectionMonitor(this); +} + +// Track the best connection, and let listeners know +void P2PSocket::SwitchBestConnectionTo(Connection* conn) { + best_connection_ = conn; + if (best_connection_) + SignalConnectionChanged(this, + best_connection_->remote_candidate().address()); +} + +// We checked the status of our connections and we had at least one that +// was writable, go into the writable state. +void P2PSocket::HandleWritable() { + // + // One or more connections writable! + // + if (state_ != STATE_WRITABLE) { + for (uint32 i = 0; i < allocator_sessions_.size(); ++i) { + if (allocator_sessions_[i]->IsGettingAllPorts()) { + allocator_sessions_[i]->StopGetAllPorts(); + } + } + + // Stop further allocations. + thread()->Clear(this, MSG_ALLOCATE); + } + + // We're writable, obviously we aren't timed out + was_writable_ = true; + was_timed_out_ = false; + set_state(STATE_WRITABLE); +} + +// We checked the status of our connections and we didn't have any that +// were writable, go into the connecting state (kick off a new allocator +// session). +void P2PSocket::HandleNotWritable() { + // + // No connections are writable but not timed out! + // + if (was_writable_) { + // If we were writable, let's kick off an allocator session immediately + was_writable_ = false; + OnAllocate(); + } + + // We were connecting, obviously not ALL timed out. + was_timed_out_ = false; + set_state(STATE_CONNECTING); +} + +// We checked the status of our connections and not only weren't they writable +// but they were also timed out, we really need a new allocator. +void P2PSocket::HandleAllTimedOut() { + // + // No connections... all are timed out! + // + if (!was_timed_out_) { + // We weren't timed out before, so kick off an allocator now (we'll still + // be in the fully timed out state until the allocator actually gives back + // new ports) + OnAllocate(); + } + + // NOTE: we start was_timed_out_ in the true state so that we don't get + // another allocator created WHILE we are in the process of building up + // our first allocator. + was_timed_out_ = true; + was_writable_ = false; + set_state(STATE_CONNECTING); +} + +// If we have a best connection, return it, otherwise return top one in the +// list (later we will mark it best). +Connection* P2PSocket::GetBestConnectionOnNetwork(Network* network) { + // If the best connection is on this network, then it wins. + if (best_connection_ && (best_connection_->port()->network() == network)) + return best_connection_; + + // Otherwise, we return the top-most in sorted order. + for (uint32 i = 0; i < connections_.size(); ++i) { + if (connections_[i]->port()->network() == network) + return connections_[i]; + } + + return NULL; +} + +// Handle any queued up requests +void P2PSocket::OnMessage(Message *pmsg) { + if (pmsg->message_id == MSG_SORT) + OnSort(); + else if (pmsg->message_id == MSG_PING) + OnPing(); + else if (pmsg->message_id == MSG_ALLOCATE) + OnAllocate(); + else + assert(false); +} + +// Handle queued up sort request +void P2PSocket::OnSort() { + // Resort the connections based on the new statistics. + SortConnections(); +} + +// Handle queued up ping request +void P2PSocket::OnPing() { + // Make sure the states of the connections are up-to-date (since this affects + // which ones are pingable). + UpdateConnectionStates(); + + // Find the oldest pingable connection and have it do a ping. + Connection* conn = FindNextPingableConnection(); + if (conn) + conn->Ping(Time()); + + // Post ourselves a message to perform the next ping. + uint32 delay = (state_ == STATE_WRITABLE) ? WRITABLE_DELAY : UNWRITABLE_DELAY; + thread()->PostDelayed(delay, this, MSG_PING); +} + +// Is the connection in a state for us to even consider pinging the other side? +bool P2PSocket::IsPingable(Connection* conn) { + // An unconnected connection cannot be written to at all, so pinging is out + // of the question. + if (!conn->connected()) + return false; + + if (state_ == STATE_WRITABLE) { + // If we are writable, then we only want to ping connections that could be + // better than this one, i.e., the ones that were not pruned. + return (conn->write_state() != Connection::STATE_WRITE_TIMEOUT); + } else { + // If we are not writable, then we need to try everything that might work. + // This includes both connections that do not have write timeout as well as + // ones that do not have read timeout. A connection could be readable but + // be in write-timeout if we pruned it before. Since the other side is + // still pinging it, it very well might still work. + return (conn->write_state() != Connection::STATE_WRITE_TIMEOUT) || + (conn->read_state() != Connection::STATE_READ_TIMEOUT); + } +} + +// Returns the next pingable connection to ping. This will be the oldest +// pingable connection unless we have a writable connection that is past the +// maximum acceptable ping delay. +Connection* P2PSocket::FindNextPingableConnection() { + uint32 now = Time(); + if (best_connection_ && + (best_connection_->write_state() == Connection::STATE_WRITABLE) && + (best_connection_->last_ping_sent() + + MAX_CURRENT_WRITABLE_DELAY <= now)) { + return best_connection_; + } + + Connection* oldest_conn = NULL; + uint32 oldest_time = 0xFFFFFFFF; + for (uint32 i = 0; i < connections_.size(); ++i) { + if (IsPingable(connections_[i])) { + if (connections_[i]->last_ping_sent() < oldest_time) { + oldest_time = connections_[i]->last_ping_sent(); + oldest_conn = connections_[i]; + } + } + } + return oldest_conn; +} + +// return the number of "pingable" connections +uint32 P2PSocket::NumPingableConnections() { + uint32 count = 0; + for (uint32 i = 0; i < connections_.size(); ++i) { + if (IsPingable(connections_[i])) + count += 1; + } + return count; +} + +// When a connection's state changes, we need to figure out who to use as +// the best connection again. It could have become usable, or become unusable. +void P2PSocket::OnConnectionStateChange(Connection *connection) { + assert(worker_thread_ == Thread::Current()); + + // We have to unroll the stack before doing this because we may be changing + // the state of connections while sorting. + RequestSort(); +} + +// When a connection is removed, edit it out, and then update our best +// connection. +void P2PSocket::OnConnectionDestroyed(Connection *connection) { + assert(worker_thread_ == Thread::Current()); + + // Remove this connection from the list. + std::vector::iterator iter = + find(connections_.begin(), connections_.end(), connection); + assert(iter != connections_.end()); + connections_.erase(iter); + + LOG(INFO) << "Removed connection from p2p socket: " + << static_cast(connections_.size()) << " remaining"; + + // If this is currently the best connection, then we need to pick a new one. + // The call to SortConnections will pick a new one. It looks at the current + // best connection in order to avoid switching between fairly similar ones. + // Since this connection is no longer an option, we can just set best to NULL + // and re-choose a best assuming that there was no best connection. + if (best_connection_ == connection) { + SwitchBestConnectionTo(NULL); + RequestSort(); + } +} + +// When a port is destroyed remove it from our list of ports to use for +// connection attempts. +void P2PSocket::OnPortDestroyed(Port* port) { + assert(worker_thread_ == Thread::Current()); + + // Remove this port from the list (if we didn't drop it already). + std::vector::iterator iter = find(ports_.begin(), ports_.end(), port); + if (iter != ports_.end()) + ports_.erase(iter); + + LOG(INFO) << "Removed port from p2p socket: " + << static_cast(ports_.size()) << " remaining"; +} + +// We data is available, let listeners know +void P2PSocket::OnReadPacket(Connection *connection, + const char *data, size_t len) { + assert(worker_thread_ == Thread::Current()); + + // Let the client know of an incoming packet + + SignalReadPacket(this, data, len); +} + +// return socket name +const std::string &P2PSocket::name() const { + return name_; +} + +// return socket error value +int P2PSocket::GetError() { + return error_; +} + +// return a reference to the list of connections +const std::vector& P2PSocket::connections() { + return connections_; +} + +// Set options on ourselves is simply setting options on all of our available +// port objects. +int P2PSocket::SetOption(Socket::Option opt, int value) { + OptionMap::iterator it = options_.find(opt); + if (it == options_.end()) { + options_.insert(std::make_pair(opt, value)); + } else if (it->second == value) { + return 0; + } else { + it->second = value; + } + + for (uint32 i = 0; i < ports_.size(); ++i) { + int val = ports_[i]->SetOption(opt, value); + if (val < 0) { + // Because this also occurs deferred, probably no point in reporting an error + LOG(WARNING) << "SetOption(" << opt << ", " << value << ") failed: " << ports_[i]->GetError(); + } + } + return 0; +} + +// returns the current state +P2PSocket::State P2PSocket::state() { + return state_; +} + +// Set the current state, and let listeners know when it changes +void P2PSocket::set_state(P2PSocket::State state) { + assert(worker_thread_ == Thread::Current()); + if (state != state_) { + state_ = state; + SignalState(this, state); + } +} + +// Time for a new allocator, lets make sure we have a signalling channel +// to communicate candidates through first. +void P2PSocket::OnAllocate() { + // Allocation timer went off + waiting_for_signaling_ = true; + SignalRequestSignaling(); +} + +// When the signalling channel is ready, we can really kick off the allocator +void P2PSocket::OnSignalingReady() { + if (waiting_for_signaling_) { + waiting_for_signaling_ = false; + AddAllocatorSession(allocator_->CreateSession(name_)); + thread()->PostDelayed(kAllocatePeriod, this, MSG_ALLOCATE); + } +} + +// return the current best connection writable state. +bool P2PSocket::writable() { + assert(worker_thread_ == Thread::Current()); + + if (best_connection_ == NULL) + return false; + return best_connection_->write_state() == Connection::STATE_WRITABLE; +} + +// return the worker thread +Thread *P2PSocket::thread() { + return worker_thread_; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/p2psocket.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/p2psocket.h new file mode 100644 index 00000000..32eb1f6d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/p2psocket.h @@ -0,0 +1,164 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +// P2PSocket wraps up the state management of the connection between two +// P2P clients. Clients have candidate ports for connecting, and connections +// which are combinations of candidates from each end (Alice and Bob each +// have candidates, one candidate from Alice and one candidate from Bob are +// used to make a connection, repeat to make many connections). +// +// When all of the available connections become invalid (non-writable), we +// kick off a process of determining more candidates and more connections. +// +#ifndef _CRICKET_P2P_BASE_P2PSOCKET_H_ +#define _CRICKET_P2P_BASE_P2PSOCKET_H_ + +#include +#include +#include "talk/base/sigslot.h" +#include "talk/p2p/base/candidate.h" +#include "talk/p2p/base/port.h" +#include "talk/p2p/base/portallocator.h" + +namespace cricket { + +// Adds the port on which the candidate originated. +class RemoteCandidate: public Candidate { + public: + RemoteCandidate(const Candidate& c, Port* origin_port) + : Candidate(c), origin_port_(origin_port) {} + + Port* origin_port() { return origin_port_; } + + private: + Port* origin_port_; +}; + +// P2PSocket manages the candidates and connection process to keep two P2P +// clients connected to each other. +class P2PSocket : public MessageHandler, public sigslot::has_slots<> { + public: + enum State { + STATE_CONNECTING = 0, // establishing writability + STATE_WRITABLE, // connected - ready for writing + }; + + P2PSocket(const std::string &name, PortAllocator *allocator); + virtual ~P2PSocket(); + + // Typically SocketManager calls these + + const std::string &name() const; + void StartProcessingCandidates(); + void AddRemoteCandidates(const std::vector &remote_candidates); + void OnSignalingReady(); + void Reset(); + + // Typically the Session Client calls these + + int Send(const char *data, size_t len); + int SetOption(Socket::Option opt, int value); + int GetError(); + + State state(); + bool writable(); + const std::vector& connections(); + Connection* best_connection() { return best_connection_; } + Thread *thread(); + + sigslot::signal2 SignalState; + sigslot::signal0<> SignalRequestSignaling; + sigslot::signal3 SignalReadPacket; + sigslot::signal2 SignalConnectionChanged; + sigslot::signal2&> + SignalCandidatesReady; + sigslot::signal1 SignalConnectionMonitor; + + // Handler for internal messages. + virtual void OnMessage(Message *pmsg); + + private: + void set_state(State state); + void UpdateConnectionStates(); + void RequestSort(); + void SortConnections(); + void SwitchBestConnectionTo(Connection* conn); + void HandleWritable(); + void HandleNotWritable(); + void HandleAllTimedOut(); + Connection* GetBestConnectionOnNetwork(Network* network); + bool CreateConnections(const Candidate &remote_candidate, Port* origin_port, + bool readable); + bool CreateConnection(Port* port, const Candidate& remote_candidate, + Port* origin_port, bool readable); + void RememberRemoteCandidate(const Candidate& remote_candidate, + Port* origin_port); + void OnUnknownAddress(Port *port, const SocketAddress &addr, + StunMessage *stun_msg, + const std::string &remote_username); + void OnPortReady(PortAllocatorSession *session, Port* port); + void OnCandidatesReady(PortAllocatorSession *session, + const std::vector& candidates); + void OnConnectionStateChange(Connection *connection); + void OnConnectionDestroyed(Connection *connection); + void OnPortDestroyed(Port* port); + void OnAllocate(); + void OnReadPacket(Connection *connection, const char *data, size_t len); + void OnSort(); + void OnPing(); + bool IsPingable(Connection* conn); + Connection* FindNextPingableConnection(); + uint32 NumPingableConnections(); + PortAllocatorSession* allocator_session() { + return allocator_sessions_.back(); + } + void AddAllocatorSession(PortAllocatorSession* session); + + Thread *worker_thread_; + State state_; + bool waiting_for_signaling_; + int error_; + std::string name_; + PortAllocator *allocator_; + std::vector allocator_sessions_; + std::vector ports_; + std::vector connections_; + Connection *best_connection_; + std::vector remote_candidates_; + bool pinging_started_; // indicates whether StartGetAllCandidates has been called + bool sort_dirty_; // indicates whether another sort is needed right now + bool was_writable_; + bool was_timed_out_; + typedef std::map OptionMap; + OptionMap options_; + + DISALLOW_EVIL_CONSTRUCTORS(P2PSocket); +}; + +} // namespace cricket + +#endif // _CRICKET_P2P_BASE_P2PSOCKET_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/port.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/port.cc new file mode 100644 index 00000000..14549b5b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/port.cc @@ -0,0 +1,869 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/base/logging.h" +#include "talk/base/asyncudpsocket.h" +#include "talk/base/asynctcpsocket.h" +#include "talk/base/socketadapters.h" +#include "talk/p2p/base/port.h" +#include "talk/p2p/base/helpers.h" +#include "talk/base/scoped_ptr.h" +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::memcmp; +} +#endif + +namespace { + +// The length of time we wait before timing out readability on a connection. +const uint32 CONNECTION_READ_TIMEOUT = 30 * 1000; // 30 seconds + +// The length of time we wait before timing out writability on a connection. +const uint32 CONNECTION_WRITE_TIMEOUT = 15 * 1000; // 15 seconds + +// The length of time we wait before we become unwritable. +const uint32 CONNECTION_WRITE_CONNECT_TIMEOUT = 5 * 1000; // 5 seconds + +// The number of pings that must fail to respond before we become unwritable. +const uint32 CONNECTION_WRITE_CONNECT_FAILURES = 5; + +// This is the length of time that we wait for a ping response to come back. +const int CONNECTION_RESPONSE_TIMEOUT = 5 * 1000; // 5 seconds + +// Determines whether we have seen at least the given maximum number of +// pings fail to have a response. +inline bool TooManyFailures( + const std::vector& pings_since_last_response, + uint32 maximum_failures, + uint32 rtt_estimate, + uint32 now) { + + // If we haven't sent that many pings, then we can't have failed that many. + if (pings_since_last_response.size() < maximum_failures) + return false; + + // Check if the window in which we would expect a response to the ping has + // already elapsed. + return pings_since_last_response[maximum_failures - 1] + rtt_estimate < now; +} + +// Determines whether we have gone too long without seeing any response. +inline bool TooLongWithoutResponse( + const std::vector& pings_since_last_response, + uint32 maximum_time, + uint32 now) { + + if (pings_since_last_response.size() == 0) + return false; + + return pings_since_last_response[0] + maximum_time < now; +} + +// We will restrict RTT estimates (when used for determining state) to be +// within a reasonable range. +const uint32 MINIMUM_RTT = 100; // 0.1 seconds +const uint32 MAXIMUM_RTT = 3000; // 3 seconds + +// When we don't have any RTT data, we have to pick something reasonable. We +// use a large value just in case the connection is really slow. +const uint32 DEFAULT_RTT = MAXIMUM_RTT; + +// Computes our estimate of the RTT given the current estimate and the number +// of data points on which it is based. +inline uint32 ConservativeRTTEstimate(uint32 rtt, uint32 rtt_data_points) { + if (rtt_data_points == 0) + return DEFAULT_RTT; + else + return cricket::_max(MINIMUM_RTT, cricket::_min(MAXIMUM_RTT, 2 * rtt)); +} + +// Weighting of the old rtt value to new data. +const int RTT_RATIO = 3; // 3 : 1 + +// The delay before we begin checking if this port is useless. +const int kPortTimeoutDelay = 30 * 1000; // 30 seconds + +const uint32 MSG_CHECKTIMEOUT = 1; +const uint32 MSG_DELETE = 1; + +} + +namespace cricket { + +static const char * const PROTO_NAMES[PROTO_LAST+1] = { "udp", "tcp", "ssltcp" }; + +const char * ProtoToString(ProtocolType proto) { + return PROTO_NAMES[proto]; +} + +bool StringToProto(const char * value, ProtocolType& proto) { + for (size_t i=0; i<=PROTO_LAST; ++i) { + if (strcmp(PROTO_NAMES[i], value) == 0) { + proto = static_cast(i); + return true; + } + } + return false; +} + +ProxyInfo Port::proxy_; + +Port::Port(Thread* thread, const std::string& type, SocketFactory* factory, + Network* network) + : thread_(thread), factory_(factory), type_(type), network_(network), + preference_(-1), lifetime_(LT_PRESTART) { + + if (factory_ == NULL) + factory_ = thread_->socketserver(); + + set_username_fragment(CreateRandomString(16)); + set_password(CreateRandomString(16)); +} + +Port::~Port() { + // Delete all of the remaining connections. We copy the list up front + // because each deletion will cause it to be modified. + + std::vector list; + + AddressMap::iterator iter = connections_.begin(); + while (iter != connections_.end()) { + list.push_back(iter->second); + ++iter; + } + + for (uint32 i = 0; i < list.size(); i++) + delete list[i]; +} + +Connection* Port::GetConnection(const SocketAddress& remote_addr) { + AddressMap::const_iterator iter = connections_.find(remote_addr); + if (iter != connections_.end()) + return iter->second; + else + return NULL; +} + +void Port::set_username_fragment(const std::string& username_fragment) { + username_frag_ = username_fragment; +} + +void Port::set_password(const std::string& password) { + password_ = password; +} + +void Port::add_address(const SocketAddress& address, const std::string& protocol, bool final) { + Candidate c; + c.set_name(name_); + c.set_type(type_); + c.set_protocol(protocol); + c.set_address(address); + c.set_preference(preference_); + c.set_username(username_frag_); + c.set_password(password_); + c.set_network_name(network_->name()); + c.set_generation(generation_); + candidates_.push_back(c); + + if (final) + SignalAddressReady(this); +} + +void Port::AddConnection(Connection* conn) { + connections_[conn->remote_candidate().address()] = conn; + conn->SignalDestroyed.connect(this, &Port::OnConnectionDestroyed); + SignalConnectionCreated(this, conn); +} + +void Port::OnReadPacket( + const char* data, size_t size, const SocketAddress& addr) { + + // If this is an authenticated STUN request, then signal unknown address and + // send back a proper binding response. + StunMessage* msg; + std::string remote_username; + if (!GetStunMessage(data, size, addr, msg, remote_username)) { + LOG(LERROR) << "Received non-STUN packet from unknown address: " + << addr.ToString(); + } else if (!msg) { + // STUN message handled already + } else if (msg->type() == STUN_BINDING_REQUEST) { + SignalUnknownAddress(this, addr, msg, remote_username); + } else { + LOG(LERROR) << "Received unexpected STUN message type (" << msg->type() + << ") from unknown address: " << addr.ToString(); + delete msg; + } +} + +void Port::SendBindingRequest(Connection* conn) { + + // Construct the request message. + + StunMessage request; + request.SetType(STUN_BINDING_REQUEST); + request.SetTransactionID(CreateRandomString(16)); + + StunByteStringAttribute* username_attr = + StunAttribute::CreateByteString(STUN_ATTR_USERNAME); + std::string username = conn->remote_candidate().username(); + username.append(username_frag_); + username_attr->CopyBytes(username.c_str(), (uint16)username.size()); + request.AddAttribute(username_attr); + + // Send the request message. + // NOTE: If we wanted to, this is where we would add the HMAC. + ByteBuffer buf; + request.Write(&buf); + SendTo(buf.Data(), buf.Length(), conn->remote_candidate().address(), false); +} + +bool Port::GetStunMessage(const char* data, size_t size, + const SocketAddress& addr, StunMessage *& msg, + std::string& remote_username) { + // NOTE: This could clearly be optimized to avoid allocating any memory. + // However, at the data rates we'll be looking at on the client side, + // this probably isn't worth worrying about. + + msg = 0; + + // Parse the request message. If the packet is not a complete and correct + // STUN message, then ignore it. + buzz::scoped_ptr stun_msg(new StunMessage()); + ByteBuffer buf(data, size); + if (!stun_msg->Read(&buf) || (buf.Length() > 0)) { + return false; + } + + // The packet must include a username that either begins or ends with our + // fragment. It should begin with our fragment if it is a request and it + // should end with our fragment if it is a response. + const StunByteStringAttribute* username_attr = + stun_msg->GetByteString(STUN_ATTR_USERNAME); + + int remote_frag_len = (username_attr ? username_attr->length() : 0); + remote_frag_len -= static_cast(username_frag_.size()); + + if (stun_msg->type() == STUN_BINDING_REQUEST) { + if ((remote_frag_len < 0) + || (std::memcmp(username_attr->bytes(), + username_frag_.c_str(), username_frag_.size()) != 0)) { + LOG(LERROR) << "Received STUN request with bad username"; + SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_BAD_REQUEST, + STUN_ERROR_REASON_BAD_REQUEST); + return true; + } + + remote_username.assign(username_attr->bytes() + username_frag_.size(), + username_attr->bytes() + username_attr->length()); + } else if ((stun_msg->type() == STUN_BINDING_RESPONSE) + || (stun_msg->type() == STUN_BINDING_ERROR_RESPONSE)) { + if ((remote_frag_len < 0) + || (std::memcmp(username_attr->bytes() + remote_frag_len, + username_frag_.c_str(), username_frag_.size()) != 0)) { + LOG(LERROR) << "Received STUN response with bad username"; + // Do not send error response to a response + return true; + } + + remote_username.assign(username_attr->bytes(), + username_attr->bytes() + remote_frag_len); + + if (stun_msg->type() == STUN_BINDING_ERROR_RESPONSE) { + if (const StunErrorCodeAttribute* error_code = stun_msg->GetErrorCode()) { + LOG(LERROR) << "Received STUN binding error:" + << " class=" << error_code->error_class() + << " number=" << error_code->number() + << " reason='" << error_code->reason() << "'"; + // Return message to allow error-specific processing + } else { + LOG(LERROR) << "Received STUN error response with no error code"; + // Drop corrupt message + return true; + } + } + } else { + LOG(LERROR) << "Received STUN packet with invalid type: " + << stun_msg->type(); + return true; + } + + // Return the STUN message found. + msg = stun_msg.release(); + return true; +} + +void Port::SendBindingResponse( + StunMessage* request, const SocketAddress& addr) { + + assert(request->type() == STUN_BINDING_REQUEST); + + // Retrieve the username from the request. + const StunByteStringAttribute* username_attr = + request->GetByteString(STUN_ATTR_USERNAME); + assert(username_attr); + + // Fill in the response message. + + StunMessage response; + response.SetType(STUN_BINDING_RESPONSE); + response.SetTransactionID(request->transaction_id()); + + StunByteStringAttribute* username2_attr = + StunAttribute::CreateByteString(STUN_ATTR_USERNAME); + username2_attr->CopyBytes(username_attr->bytes(), username_attr->length()); + response.AddAttribute(username2_attr); + + StunAddressAttribute* addr_attr = + StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS); + addr_attr->SetFamily(1); + addr_attr->SetPort(addr.port()); + addr_attr->SetIP(addr.ip()); + response.AddAttribute(addr_attr); + + // Send the response message. + // NOTE: If we wanted to, this is where we would add the HMAC. + ByteBuffer buf; + response.Write(&buf); + SendTo(buf.Data(), buf.Length(), addr, false); + + // The fact that we received a successful request means that this connection + // (if one exists) should now be readable. + Connection* conn = GetConnection(addr); + assert(conn); + if (conn) + conn->ReceivedPing(); +} + +void Port::SendBindingErrorResponse( + StunMessage* request, const SocketAddress& addr, int error_code, + const std::string& reason) { + + assert(request->type() == STUN_BINDING_REQUEST); + + // Retrieve the username from the request. If it didn't have one, we + // shouldn't be responding at all. + const StunByteStringAttribute* username_attr = + request->GetByteString(STUN_ATTR_USERNAME); + assert(username_attr); + + // Fill in the response message. + + StunMessage response; + response.SetType(STUN_BINDING_ERROR_RESPONSE); + response.SetTransactionID(request->transaction_id()); + + StunByteStringAttribute* username2_attr = + StunAttribute::CreateByteString(STUN_ATTR_USERNAME); + username2_attr->CopyBytes(username_attr->bytes(), username_attr->length()); + response.AddAttribute(username2_attr); + + StunErrorCodeAttribute* error_attr = StunAttribute::CreateErrorCode(); + error_attr->SetErrorCode(error_code); + error_attr->SetReason(reason); + response.AddAttribute(error_attr); + + // Send the response message. + // NOTE: If we wanted to, this is where we would add the HMAC. + ByteBuffer buf; + response.Write(&buf); + SendTo(buf.Data(), buf.Length(), addr, false); +} + +AsyncPacketSocket * Port::CreatePacketSocket(ProtocolType proto) { + if (proto == PROTO_UDP) { + return new AsyncUDPSocket(factory_->CreateAsyncSocket(SOCK_DGRAM)); + } else if ((proto == PROTO_TCP) || (proto == PROTO_SSLTCP)) { + AsyncSocket * socket = factory_->CreateAsyncSocket(SOCK_STREAM); + switch (proxy().type) { + case PROXY_NONE: + break; + case PROXY_SOCKS5: + socket = new AsyncSocksProxySocket(socket, proxy().address, proxy().username, proxy().password); + break; + case PROXY_HTTPS: + default: + socket = new AsyncHttpsProxySocket(socket, proxy().address, proxy().username, proxy().password); + break; + } + if (proto == PROTO_SSLTCP) { + socket = new AsyncSSLSocket(socket); + } + return new AsyncTCPSocket(socket); + } else { + LOG(INFO) << "Unknown protocol: " << proto; + return 0; + } +} + +void Port::OnMessage(Message *pmsg) { + assert(pmsg->message_id == MSG_CHECKTIMEOUT); + assert(lifetime_ == LT_PRETIMEOUT); + lifetime_ = LT_POSTTIMEOUT; + CheckTimeout(); +} + +void Port::Start() { + // The port sticks around for a minimum lifetime, after which + // we destroy it when it drops to zero connections. + if (lifetime_ == LT_PRESTART) { + lifetime_ = LT_PRETIMEOUT; + thread_->PostDelayed(kPortTimeoutDelay, this, MSG_CHECKTIMEOUT); + } else { + LOG(WARNING) << "Port restart attempted"; + } +} + +void Port::OnConnectionDestroyed(Connection* conn) { + AddressMap::iterator iter = connections_.find(conn->remote_candidate().address()); + assert(iter != connections_.end()); + connections_.erase(iter); + + CheckTimeout(); +} + +void Port::CheckTimeout() { + // If this port has no connections, then there's no reason to keep it around. + // When the connections time out (both read and write), they will delete + // themselves, so if we have any connections, they are either readable or + // writable (or still connecting). + if ((lifetime_ == LT_POSTTIMEOUT) && connections_.empty()) { + LOG(INFO) << "Destroying port: " << name_ << "-" << type_; + SignalDestroyed(this); + delete this; + } +} + +// A ConnectionRequest is a simple STUN ping used to determine writability. +class ConnectionRequest : public StunRequest { +public: + ConnectionRequest(Connection* connection) : connection_(connection) { + } + + virtual ~ConnectionRequest() { + } + + virtual void Prepare(StunMessage* request) { + request->SetType(STUN_BINDING_REQUEST); + StunByteStringAttribute* username_attr = + StunAttribute::CreateByteString(STUN_ATTR_USERNAME); + std::string username = connection_->remote_candidate().username(); + username.append(connection_->port()->username_fragment()); + username_attr->CopyBytes(username.c_str(), (uint16)username.size()); + request->AddAttribute(username_attr); + } + + virtual void OnResponse(StunMessage* response) { + connection_->OnConnectionRequestResponse(response, Elapsed()); + } + + virtual void OnErrorResponse(StunMessage* response) { + connection_->OnConnectionRequestErrorResponse(response, Elapsed()); + } + + virtual void OnTimeout() { + } + + virtual int GetNextDelay() { + // Each request is sent only once. After a single delay , the request will + // time out. + timeout_ = true; + return CONNECTION_RESPONSE_TIMEOUT; + } + +private: + Connection* connection_; +}; + +// +// Connection +// + +Connection::Connection(Port* port, size_t index, const Candidate& remote_candidate) + : requests_(port->thread()), port_(port), local_candidate_index_(index), + remote_candidate_(remote_candidate), read_state_(STATE_READ_TIMEOUT), + write_state_(STATE_WRITE_CONNECT), connected_(true), pruned_(false), + rtt_(0), rtt_data_points_(0), last_ping_sent_(0), last_ping_received_(0), + recv_total_bytes_(0), recv_bytes_second_(0), + last_recv_bytes_second_time_((uint32)-1), last_recv_bytes_second_calc_(0), + sent_total_bytes_(0), sent_bytes_second_(0), + last_sent_bytes_second_time_((uint32)-1), last_sent_bytes_second_calc_(0) { + + // Wire up to send stun packets + requests_.SignalSendPacket.connect(this, &Connection::OnSendStunPacket); +} + +Connection::~Connection() { +} + +const Candidate& Connection::local_candidate() const { + if (local_candidate_index_ < port_->candidates().size()) + return port_->candidates()[local_candidate_index_]; + assert(false); + static Candidate foo; + return foo; +} + +void Connection::set_read_state(ReadState value) { + ReadState old_value = read_state_; + read_state_ = value; + if (value != old_value) { + SignalStateChange(this); + CheckTimeout(); + } +} + +void Connection::set_write_state(WriteState value) { + WriteState old_value = write_state_; + write_state_ = value; + if (value != old_value) { + SignalStateChange(this); + CheckTimeout(); + } +} + +void Connection::set_connected(bool value) { + bool old_value = connected_; + connected_ = value; + + // When connectedness is turned off, this connection is done. + if (old_value && !value) + set_write_state(STATE_WRITE_TIMEOUT); +} + +void Connection::OnSendStunPacket(const void* data, size_t size) { + port_->SendTo(data, size, remote_candidate_.address(), false); +} + +void Connection::OnReadPacket(const char* data, size_t size) { + StunMessage* msg; + std::string remote_username; + const SocketAddress& addr(remote_candidate_.address()); + if (!port_->GetStunMessage(data, size, addr, msg, remote_username)) { + // The packet did not parse as a valid STUN message + + // If this connection is readable, then pass along the packet. + if (read_state_ == STATE_READABLE) { + // readable means data from this address is acceptable + // Send it on! + + recv_total_bytes_ += size; + SignalReadPacket(this, data, size); + + // If timed out sending writability checks, start up again + if (!pruned_ && (write_state_ == STATE_WRITE_TIMEOUT)) + set_write_state(STATE_WRITE_CONNECT); + } else { + // Not readable means the remote address hasn't send a valid + // binding request yet. + + LOG(WARNING) << "Received non-STUN packet from an unreadable connection."; + } + } else if (!msg) { + // The packet was STUN, but was already handled + } else if (remote_username != remote_candidate_.username()) { + // Not destined this connection + LOG(LERROR) << "Received STUN packet on wrong address."; + if (msg->type() == STUN_BINDING_REQUEST) { + port_->SendBindingErrorResponse(msg, addr, STUN_ERROR_BAD_REQUEST, + STUN_ERROR_REASON_BAD_REQUEST); + } + delete msg; + } else { + // The packet is STUN, with the current username + // If this is a STUN request, then update the readable bit and respond. + // If this is a STUN response, then update the writable bit. + + switch (msg->type()) { + case STUN_BINDING_REQUEST: + // Incoming, validated stun request from remote peer. + // This call will also set the connection readable. + + port_->SendBindingResponse(msg, addr); + + // If timed out sending writability checks, start up again + if (!pruned_ && (write_state_ == STATE_WRITE_TIMEOUT)) + set_write_state(STATE_WRITE_CONNECT); + break; + + case STUN_BINDING_RESPONSE: + case STUN_BINDING_ERROR_RESPONSE: + // Response from remote peer. Does it match request sent? + // This doesn't just check, it makes callbacks if transaction + // id's match + requests_.CheckResponse(msg); + break; + + default: + assert(false); + break; + } + + // Done with the message; delete + + delete msg; + } +} + +void Connection::Prune() { + pruned_ = true; + requests_.Clear(); + set_write_state(STATE_WRITE_TIMEOUT); +} + +void Connection::Destroy() { + set_read_state(STATE_READ_TIMEOUT); + set_write_state(STATE_WRITE_TIMEOUT); +} + +void Connection::UpdateState(uint32 now) { + // Check the readable state. + // + // Since we don't know how many pings the other side has attempted, the best + // test we can do is a simple window. + + if ((read_state_ == STATE_READABLE) && + (last_ping_received_ + CONNECTION_READ_TIMEOUT <= now)) { + set_read_state(STATE_READ_TIMEOUT); + } + + // Check the writable state. (The order of these checks is important.) + // + // Before becoming unwritable, we allow for a fixed number of pings to fail + // (i.e., receive no response). We also have to give the response time to + // get back, so we include a conservative estimate of this. + // + // Before timing out writability, we give a fixed amount of time. This is to + // allow for changes in network conditions. + + uint32 rtt = ConservativeRTTEstimate(rtt_, rtt_data_points_); + + if ((write_state_ == STATE_WRITABLE) && + TooManyFailures(pings_since_last_response_, + CONNECTION_WRITE_CONNECT_FAILURES, + rtt, + now) && + TooLongWithoutResponse(pings_since_last_response_, + CONNECTION_WRITE_CONNECT_TIMEOUT, + now)) { + set_write_state(STATE_WRITE_CONNECT); + } + + if ((write_state_ == STATE_WRITE_CONNECT) && + TooLongWithoutResponse(pings_since_last_response_, + CONNECTION_WRITE_TIMEOUT, + now)) { + set_write_state(STATE_WRITE_TIMEOUT); + } +} + +void Connection::Ping(uint32 now) { + assert(connected_); + last_ping_sent_ = now; + pings_since_last_response_.push_back(now); + requests_.Send(new ConnectionRequest(this)); +} + +void Connection::ReceivedPing() { + last_ping_received_ = Time(); + set_read_state(STATE_READABLE); +} + +void Connection::OnConnectionRequestResponse(StunMessage *response, uint32 rtt) { + // We have a potentially valid reply from the remote address. + // The packet must include a username that ends with our fragment, + // since it is a response. + + // Check exact message type + bool valid = true; + if (response->type() != STUN_BINDING_RESPONSE) + valid = false; + + // Must have username attribute + const StunByteStringAttribute* username_attr = + response->GetByteString(STUN_ATTR_USERNAME); + if (valid) { + if (!username_attr) { + LOG(LERROR) << "Received likely STUN packet with no username"; + valid = false; + } + } + + // Length must be at least the size of our fragment (actually, should + // be bigger since our fragment is at the end!) + if (valid) { + if (username_attr->length() <= port_->username_fragment().size()) { + LOG(LERROR) << "Received likely STUN packet with short username"; + valid = false; + } + } + + // Compare our fragment with the end of the username - must be exact match + if (valid) { + std::string username_fragment = port_->username_fragment(); + int offset = (int)(username_attr->length() - username_fragment.size()); + if (std::memcmp(username_attr->bytes() + offset, + username_fragment.c_str(), username_fragment.size()) != 0) { + LOG(LERROR) << "Received STUN response with bad username"; + valid = false; + } + } + + if (valid) { + // Valid response. If we're not already, become writable. We may be + // bringing a pruned connection back to life, but if we don't really want + // it, we can always prune it again. + set_write_state(STATE_WRITABLE); + + pings_since_last_response_.clear(); + rtt_ = (RTT_RATIO * rtt_ + rtt) / (RTT_RATIO + 1); + rtt_data_points_ += 1; + } +} + +void Connection::OnConnectionRequestErrorResponse(StunMessage *response, uint32 rtt) { + const StunErrorCodeAttribute* error = response->GetErrorCode(); + uint32 error_code = error ? error->error_code() : STUN_ERROR_GLOBAL_FAILURE; + + if ((error_code == STUN_ERROR_UNKNOWN_ATTRIBUTE) + || (error_code == STUN_ERROR_SERVER_ERROR) + || (error_code == STUN_ERROR_UNAUTHORIZED)) { + // Recoverable error, retry + } else if (error_code == STUN_ERROR_STALE_CREDENTIALS) { + // Race failure, retry + } else { + // This is not a valid connection. + set_connected(false); + } +} + +void Connection::CheckTimeout() { + // If both read and write have timed out, then this connection can contribute + // no more to p2p socket unless at some later date readability were to come + // back. However, we gave readability a long time to timeout, so at this + // point, it seems fair to get rid of this connectoin. + if ((read_state_ == STATE_READ_TIMEOUT) && + (write_state_ == STATE_WRITE_TIMEOUT)) { + port_->thread()->Post(this, MSG_DELETE); + } +} + +void Connection::OnMessage(Message *pmsg) { + assert(pmsg->message_id == MSG_DELETE); + + LOG(INFO) << "Destroying connection: from " + << local_candidate().address().ToString() + << " to " << remote_candidate_.address().ToString(); + + SignalDestroyed(this); + delete this; +} + +size_t Connection::recv_bytes_second() { + // Snapshot bytes / second calculator + + uint32 current_time = Time(); + if (last_recv_bytes_second_time_ != (uint32)-1) { + int delta = TimeDiff(current_time, last_recv_bytes_second_time_); + if (delta >= 1000) { + int fraction_time = delta % 1000; + int seconds_time = delta - fraction_time; + int fraction_bytes = (int)(recv_total_bytes_ - last_recv_bytes_second_calc_) * fraction_time / delta; + recv_bytes_second_ = (recv_total_bytes_ - last_recv_bytes_second_calc_ - fraction_bytes) * seconds_time / delta; + last_recv_bytes_second_time_ = current_time - fraction_time; + last_recv_bytes_second_calc_ = recv_total_bytes_ - fraction_bytes; + } + } + if (last_recv_bytes_second_time_ == (uint32)-1) { + last_recv_bytes_second_time_ = current_time; + last_recv_bytes_second_calc_ = recv_total_bytes_; + } + + return recv_bytes_second_; +} + +size_t Connection::recv_total_bytes() { + return recv_total_bytes_; +} + +size_t Connection::sent_bytes_second() { + // Snapshot bytes / second calculator + + uint32 current_time = Time(); + if (last_sent_bytes_second_time_ != (uint32)-1) { + int delta = TimeDiff(current_time, last_sent_bytes_second_time_); + if (delta >= 1000) { + int fraction_time = delta % 1000; + int seconds_time = delta - fraction_time; + int fraction_bytes = (int)(sent_total_bytes_ - last_sent_bytes_second_calc_) * fraction_time / delta; + sent_bytes_second_ = (sent_total_bytes_ - last_sent_bytes_second_calc_ - fraction_bytes) * seconds_time / delta; + last_sent_bytes_second_time_ = current_time - fraction_time; + last_sent_bytes_second_calc_ = sent_total_bytes_ - fraction_bytes; + } + } + if (last_sent_bytes_second_time_ == (uint32)-1) { + last_sent_bytes_second_time_ = current_time; + last_sent_bytes_second_calc_ = sent_total_bytes_; + } + + return sent_bytes_second_; +} + +size_t Connection::sent_total_bytes() { + return sent_total_bytes_; +} + +ProxyConnection::ProxyConnection(Port* port, size_t index, const Candidate& candidate) + : Connection(port, index, candidate), error_(0) { +} + +int ProxyConnection::Send(const void* data, size_t size) { + if (write_state() != STATE_WRITABLE) { + error_ = EWOULDBLOCK; + return SOCKET_ERROR; + } + int sent = port_->SendTo(data, size, remote_candidate_.address(), true); + if (sent <= 0) { + assert(sent < 0); + error_ = port_->GetError(); + } else { + sent_total_bytes_ += sent; + } + return sent; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/port.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/port.h new file mode 100644 index 00000000..c22fad28 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/port.h @@ -0,0 +1,367 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __PORT_H__ +#define __PORT_H__ + +#include "talk/base/network.h" +#include "talk/base/socketaddress.h" +#include "talk/base/proxyinfo.h" +#include "talk/base/sigslot.h" +#include "talk/base/thread.h" +#include "talk/p2p/base/candidate.h" +#include "talk/p2p/base/stun.h" +#include "talk/p2p/base/stunrequest.h" + +#include +#include +#include + +namespace cricket { + +class Connection; +class AsyncPacketSocket; + +enum ProtocolType { PROTO_UDP, PROTO_TCP, PROTO_SSLTCP, PROTO_LAST = PROTO_SSLTCP }; +const char * ProtoToString(ProtocolType proto); +bool StringToProto(const char * value, ProtocolType& proto); + +struct ProtocolAddress { + SocketAddress address; + ProtocolType proto; + + ProtocolAddress(const SocketAddress& a, ProtocolType p) : address(a), proto(p) { } +}; + +// Represents a local communication mechanism that can be used to create +// connections to similar mechanisms of the other client. Subclasses of this +// one add support for specific mechanisms like local UDP ports. +class Port: public MessageHandler, public sigslot::has_slots<> { +public: + Port(Thread* thread, const std::string &type, SocketFactory* factory, + Network* network); + virtual ~Port(); + + // The thread on which this port performs its I/O. + Thread* thread() { return thread_; } + + // The factory used to create the sockets of this port. + SocketFactory* socket_factory() const { return factory_; } + void set_socket_factory(SocketFactory* factory) { factory_ = factory; } + + // Each port is identified by a name (for debugging purposes). + const std::string& name() const { return name_; } + void set_name(const std::string& name) { name_ = name; } + + // In order to establish a connection to this Port (so that real data can be + // sent through), the other side must send us a STUN binding request that is + // authenticated with this username and password. + const std::string& username_fragment() const { return username_frag_; } + const std::string& password() const { return password_; } + + // A value in [0,1] that indicates the preference for this port versus other + // ports on this client. (Larger indicates more preference.) + float preference() const { return preference_; } + void set_preference(float preference) { preference_ = preference; } + + // Identifies the port type. + //const std::string& protocol() const { return proto_; } + const std::string& type() const { return type_; } + + // Identifies network that this port was allocated on. + Network* network() { return network_; } + + // Identifies the generation that this port was created in. + uint32 generation() { return generation_; } + void set_generation(uint32 generation) { generation_ = generation; } + + // PrepareAddress will attempt to get an address for this port that other + // clients can send to. It may take some time before the address is read. + // Once it is ready, we will send SignalAddressReady. + virtual void PrepareAddress() = 0; + sigslot::signal1 SignalAddressReady; + //const SocketAddress& address() const { return address_; } + + // Provides all of the above information in one handy object. + const std::vector& candidates() const { return candidates_; } + + // Returns a map containing all of the connections of this port, keyed by the + // remote address. + typedef std::map AddressMap; + const AddressMap& connections() { return connections_; } + + // Returns the connection to the given address or NULL if none exists. + Connection* GetConnection(const SocketAddress& remote_addr); + + // Creates a new connection to the given address. + enum CandidateOrigin { ORIGIN_THIS_PORT, ORIGIN_OTHER_PORT, ORIGIN_MESSAGE }; + virtual Connection* CreateConnection(const Candidate& remote_candidate, CandidateOrigin origin) = 0; + + // Called each time a connection is created. + sigslot::signal2 SignalConnectionCreated; + + // Sends the given packet to the given address, provided that the address is + // that of a connection or an address that has sent to us already. + virtual int SendTo( + const void* data, size_t size, const SocketAddress& addr, bool payload) = 0; + + // Indicates that we received a successful STUN binding request from an + // address that doesn't correspond to any current connection. To turn this + // into a real connection, call CreateConnection. + sigslot::signal4 SignalUnknownAddress; + + // Sends a response message (normal or error) to the given request. One of + // these methods should be called as a response to SignalUnknownAddress. + // NOTE: You MUST call CreateConnection BEFORE SendBindingResponse. + void SendBindingResponse(StunMessage* request, const SocketAddress& addr); + void SendBindingErrorResponse( + StunMessage* request, const SocketAddress& addr, int error_code, + const std::string& reason); + + // Indicates that errors occurred when performing I/O. + sigslot::signal2 SignalReadError; + sigslot::signal2 SignalWriteError; + + // Functions on the underlying socket(s). + virtual int SetOption(Socket::Option opt, int value) = 0; + virtual int GetError() = 0; + + static void set_proxy(const ProxyInfo& proxy) { proxy_ = proxy; } + static const ProxyInfo& proxy() { return proxy_; } + + AsyncPacketSocket * CreatePacketSocket(ProtocolType proto); + + virtual void OnMessage(Message *pmsg); + + // Indicates to the port that its official use has now begun. This will + // start the timer that checks to see if the port is being used. + void Start(); + + // Signaled when this port decides to delete itself because it no longer has + // any usefulness. + sigslot::signal1 SignalDestroyed; + +protected: + Thread* thread_; + SocketFactory* factory_; + std::string type_; + Network* network_; + uint32 generation_; + std::string name_; + std::string username_frag_; + std::string password_; + float preference_; + std::vector candidates_; + AddressMap connections_; + enum Lifetime { LT_PRESTART, LT_PRETIMEOUT, LT_POSTTIMEOUT } lifetime_; + + // Fills in the username fragment and password. These will be initially set + // in the constructor to random values. Subclasses can override, though. + void set_username_fragment(const std::string& username_fragment); + void set_password(const std::string& password); + + // Fills in the local address of the port. + void add_address(const SocketAddress& address, const std::string& protocol, bool final = true); + + // Adds the given connection to the list. (Deleting removes them.) + void AddConnection(Connection* conn); + + // Called when a packet is received from an unknown address that is not + // currently a connection. If this is an authenticated STUN binding request, + // then we will signal the client. + void OnReadPacket(const char* data, size_t size, const SocketAddress& addr); + + // Constructs a STUN binding request for the given connection and sends it. + void SendBindingRequest(Connection* conn); + + // If the given data comprises a complete and correct STUN message then the + // return value is true, otherwise false. If the message username corresponds + // with this port's username fragment, msg will contain the parsed STUN + // message. Otherwise, the function may send a STUN response internally. + // remote_username contains the remote fragment of the STUN username. + bool GetStunMessage(const char* data, size_t size, const SocketAddress& addr, + StunMessage *& msg, std::string& remote_username); + + friend class Connection; + +private: + // Called when one of our connections deletes itself. + void OnConnectionDestroyed(Connection* conn); + + // Checks if this port is useless, and hence, should be destroyed. + void CheckTimeout(); + + static ProxyInfo proxy_; +}; + +// Represents a communication link between a port on the local client and a +// port on the remote client. +class Connection : public MessageHandler, public sigslot::has_slots<> { +public: + virtual ~Connection(); + + // The local port where this connection sends and receives packets. + Port* port() { return port_; } + const Port* port() const { return port_; } + + // Returns the description of the local port + virtual const Candidate& local_candidate() const; + + // Returns the description of the remote port to which we communicate. + const Candidate& remote_candidate() const { return remote_candidate_; } + + enum ReadState { + STATE_READABLE = 0, // we have received pings recently + STATE_READ_TIMEOUT = 1 // we haven't received pings in a while + }; + + ReadState read_state() const { return read_state_; } + + enum WriteState { + STATE_WRITABLE = 0, // we have received ping responses recently + STATE_WRITE_CONNECT = 1, // we have had a few ping failures + STATE_WRITE_TIMEOUT = 2 // we have had a large number of ping failures + }; + + WriteState write_state() const { return write_state_; } + + // Determines whether the connection has finished connecting. This can only + // be false for TCP connections. + bool connected() const { return connected_; } + + // Estimate of the round-trip time over this connection. + uint32 rtt() const { return rtt_; } + + size_t sent_total_bytes(); + size_t sent_bytes_second(); + size_t recv_total_bytes(); + size_t recv_bytes_second(); + sigslot::signal1 SignalStateChange; + + // Sent when the connection has decided that it is no longer of value. It + // will delete itself immediately after this call. + sigslot::signal1 SignalDestroyed; + + // The connection can send and receive packets asynchronously. This matches + // the interface of AsyncPacketSocket, which may use UDP or TCP under the covers. + virtual int Send(const void* data, size_t size) = 0; + + // Error if Send() returns < 0 + virtual int GetError() = 0; + + sigslot::signal3 SignalReadPacket; + + // Called when a packet is received on this connection. + void OnReadPacket(const char* data, size_t size); + + // Called when a connection is determined to be no longer useful to us. We + // still keep it around in case the other side wants to use it. But we can + // safely stop pinging on it and we can allow it to time out if the other + // side stops using it as well. + bool pruned() { return pruned_; } + void Prune(); + + // Makes the connection go away. + void Destroy(); + + // Checks that the state of this connection is up-to-date. The argument is + // the current time, which is compared against various timeouts. + void UpdateState(uint32 now); + + // Called when this connection should try checking writability again. + uint32 last_ping_sent() { return last_ping_sent_; } + void Ping(uint32 now); + + // Called whenever a valid ping is received on this connection. This is + // public because the connection intercepts the first ping for us. + void ReceivedPing(); + +protected: + Port* port_; + size_t local_candidate_index_; + Candidate remote_candidate_; + ReadState read_state_; + WriteState write_state_; + bool connected_; + bool pruned_; + StunRequestManager requests_; + uint32 rtt_; + uint32 rtt_data_points_; + uint32 last_ping_sent_; // last time we sent a ping to the other side + uint32 last_ping_received_; // last time we received a ping from the other side + std::vector pings_since_last_response_; + + size_t recv_total_bytes_; + size_t recv_bytes_second_; + uint32 last_recv_bytes_second_time_; + size_t last_recv_bytes_second_calc_; + + size_t sent_total_bytes_; + size_t sent_bytes_second_; + uint32 last_sent_bytes_second_time_; + size_t last_sent_bytes_second_calc_; + + // Callbacks from ConnectionRequest + void OnConnectionRequestResponse(StunMessage *response, uint32 rtt); + void OnConnectionRequestErrorResponse(StunMessage *response, uint32 rtt); + + // Called back when StunRequestManager has a stun packet to send + void OnSendStunPacket(const void* data, size_t size); + + // Constructs a new connection to the given remote port. + Connection(Port* port, size_t index, const Candidate& candidate); + + // Changes the state and signals if necessary. + void set_read_state(ReadState value); + void set_write_state(WriteState value); + void set_connected(bool value); + + // Checks if this connection is useless, and hence, should be destroyed. + void CheckTimeout(); + + void OnMessage(Message *pmsg); + + friend class Port; + friend class ConnectionRequest; +}; + +// ProxyConnection defers all the interesting work to the port + +class ProxyConnection : public Connection { +public: + ProxyConnection(Port* port, size_t index, const Candidate& candidate); + + virtual int Send(const void* data, size_t size); + virtual int GetError() { return error_; } + +private: + int error_; +}; + +} // namespace cricket + +#endif // __PORT_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/portallocator.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/portallocator.h new file mode 100644 index 00000000..3246f29f --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/portallocator.h @@ -0,0 +1,91 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _PORTALLOCATOR_H_ +#define _PORTALLOCATOR_H_ + +#include "talk/base/sigslot.h" +#include "talk/p2p/base/port.h" +#include +#undef SetPort + +namespace cricket { + +// PortAllocator is responsible for allocating Port types for a given +// P2PSocket. It also handles port freeing. +// +// Clients can override this class to control port allocation, including +// what kinds of ports are allocated. + +class PortAllocatorSession : public sigslot::has_slots<> { +public: + // Prepares an initial set of ports to try. + virtual void GetInitialPorts() = 0; + + // Starts and stops the flow of additional ports to try. + virtual void StartGetAllPorts() = 0; + virtual void StopGetAllPorts() = 0; + virtual bool IsGettingAllPorts() = 0; + + sigslot::signal2 SignalPortReady; + sigslot::signal2&> SignalCandidatesReady; + + uint32 generation() { return generation_; } + void set_generation(uint32 generation) { generation_ = generation; } + +private: + uint32 generation_; +}; + +const uint32 PORTALLOCATOR_DISABLE_UDP = 0x01; +const uint32 PORTALLOCATOR_DISABLE_STUN = 0x02; +const uint32 PORTALLOCATOR_DISABLE_RELAY = 0x04; +const uint32 PORTALLOCATOR_DISABLE_TCP = 0x08; +const uint32 PORTALLOCATOR_ENABLE_SHAKER = 0x10; + +const uint32 kDefaultPortAllocatorFlags = 0; + +class PortAllocator { +public: + PortAllocator() : flags_(kDefaultPortAllocatorFlags) {} + + virtual PortAllocatorSession *CreateSession(const std::string &name) = 0; + + uint32 flags() const { return flags_; } + void set_flags(uint32 flags) { flags_ = flags; } + + const ProxyInfo& proxy() const { return proxy_; } + void set_proxy(const ProxyInfo& proxy) { proxy_ = proxy; } + +protected: + uint32 flags_; + ProxyInfo proxy_; +}; + +} // namespace cricket + +#endif // _PORTALLOCATOR_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayport.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayport.cc new file mode 100644 index 00000000..4ba12be3 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayport.cc @@ -0,0 +1,640 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/base/logging.h" +#include "talk/base/asynctcpsocket.h" +#include "talk/p2p/base/relayport.h" +#include "talk/p2p/base/helpers.h" +#include +#include +#ifdef OSX +#include +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::strerror; +} +#endif + +#ifdef POSIX +extern "C" { +#include +} +#endif // POSIX + +namespace cricket { + +const int KEEPALIVE_DELAY = 10 * 60 * 1000; +const int RETRY_DELAY = 50; // 50ms, from ICE spec +const uint32 RETRY_TIMEOUT = 50 * 1000; // ICE says 50 secs + +const uint32 MSG_DISPOSE_SOCKET = 100; // needs to be more than ID used by Port +typedef TypedMessageData DisposeSocketData; + +class AsyncTCPSocket; + +// Manages a single connection to the relayserver. We aim to use each +// connection for only a specific destination address so that we can avoid +// wrapping every packet in a STUN send / data indication. +class RelayEntry : public sigslot::has_slots<> { +public: + RelayEntry(RelayPort* port, const SocketAddress& ext_addr, const SocketAddress& local_addr); + ~RelayEntry(); + + RelayPort* port() { return port_; } + + const SocketAddress& address() { return ext_addr_; } + void set_address(const SocketAddress& addr) { ext_addr_ = addr; } + + AsyncPacketSocket* socket() { return socket_; } + + bool connected() { return connected_; } + void set_connected(bool connected) { connected_ = connected; } + + bool locked() { return locked_; } + + // Returns the last error on the socket of this entry. + int GetError() { return socket_->GetError(); } + + // Sends the STUN requests to the server to initiate this connection. + void Connect(); + + // Called when this entry becomes connected. The address given is the one + // exposed to the outside world on the relay server. + void OnConnect(const SocketAddress& mapped_addr); + + // Sends a packet to the given destination address using the socket of this + // entry. This will wrap the packet in STUN if necessary. + int SendTo(const void* data, size_t size, const SocketAddress& addr); + + // Schedules a keep-alive allocate request. + void ScheduleKeepAlive(); + + void SetServerIndex(size_t sindex) { server_index_ = sindex; } + size_t ServerIndex() const { return server_index_; } + + // Try a different server address + void HandleConnectFailure(); + +private: + RelayPort* port_; + SocketAddress ext_addr_, local_addr_; + size_t server_index_; + AsyncPacketSocket* socket_; + bool connected_; + bool locked_; + StunRequestManager requests_; + + // Called when a TCP connection is established or fails + void OnSocketConnect(AsyncTCPSocket* socket); + void OnSocketClose(AsyncTCPSocket* socket, int error); + + // Called when a packet is received on this socket. + void OnReadPacket( + const char* data, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket); + + // Called on behalf of a StunRequest to write data to the socket. This is + // already STUN intended for the server, so no wrapping is necessary. + void OnSendPacket(const void* data, size_t size); + + // Sends the given data on the socket to the server with no wrapping. This + // returns the number of bytes written or -1 if an error occurred. + int SendPacket(const void* data, size_t size); +}; + +// Handles an allocate request for a particular RelayEntry. +class AllocateRequest : public StunRequest { +public: + AllocateRequest(RelayEntry* entry); + virtual ~AllocateRequest() {} + + virtual void Prepare(StunMessage* request); + + virtual int GetNextDelay(); + + virtual void OnResponse(StunMessage* response); + virtual void OnErrorResponse(StunMessage* response); + virtual void OnTimeout(); + +private: + RelayEntry* entry_; + uint32 start_time_; +}; + +const std::string RELAY_PORT_TYPE("relay"); + +RelayPort::RelayPort( + Thread* thread, SocketFactory* factory, Network* network, + const SocketAddress& local_addr, const std::string& username, + const std::string& password, const std::string& magic_cookie) + : Port(thread, RELAY_PORT_TYPE, factory, network), local_addr_(local_addr), + ready_(false), magic_cookie_(magic_cookie), error_(0) { + + entries_.push_back(new RelayEntry(this, SocketAddress(), local_addr_)); + + set_username_fragment(username); + set_password(password); + + if (magic_cookie_.size() == 0) + magic_cookie_.append(STUN_MAGIC_COOKIE_VALUE, 4); +} + +RelayPort::~RelayPort() { + for (unsigned i = 0; i < entries_.size(); ++i) + delete entries_[i]; + thread_->Clear(this); +} + +void RelayPort::AddServerAddress(const ProtocolAddress& addr) { + // Since HTTP proxies usually only allow 443, let's up the priority on PROTO_SSLTCP + if ((addr.proto == PROTO_SSLTCP) + && ((proxy().type == PROXY_HTTPS) || (proxy().type == PROXY_UNKNOWN))) { + server_addr_.push_front(addr); + } else { + server_addr_.push_back(addr); + } +} + +void RelayPort::AddExternalAddress(const ProtocolAddress& addr) { + std::string proto_name = ProtoToString(addr.proto); + for (std::vector::const_iterator it = candidates().begin(); it != candidates().end(); ++it) { + if ((it->address() == addr.address) && (it->protocol() == proto_name)) { + LOG(INFO) << "Redundant relay address: " << proto_name << " @ " << addr.address.ToString(); + return; + } + } + add_address(addr.address, proto_name, false); +} + +void RelayPort::SetReady() { + if (!ready_) { + ready_ = true; + SignalAddressReady(this); + } +} + +const ProtocolAddress * RelayPort::ServerAddress(size_t index) const { + if ((index >= 0) && (index < server_addr_.size())) + return &server_addr_[index]; + return 0; +} + +bool RelayPort::HasMagicCookie(const char* data, size_t size) { + if (size < 24 + magic_cookie_.size()) { + return false; + } else { + return 0 == std::memcmp(data + 24, + magic_cookie_.c_str(), + magic_cookie_.size()); + } +} + +void RelayPort::PrepareAddress() { + // We initiate a connect on the first entry. If this completes, it will fill + // in the server address as the address of this port. + assert(entries_.size() == 1); + entries_[0]->Connect(); + ready_ = false; +} + +Connection* RelayPort::CreateConnection(const Candidate& address, CandidateOrigin origin) { + // We only create connections to non-udp sockets if they are incoming on this port + if ((address.protocol() != "udp") && (origin != ORIGIN_THIS_PORT)) + return 0; + + // We don't support loopback on relays + if (address.type() == type()) + return 0; + + size_t index = 0; + for (size_t i = 0; i < candidates().size(); ++i) { + const Candidate& local = candidates()[i]; + if (local.protocol() == address.protocol()) { + index = i; + break; + } + } + + Connection * conn = new ProxyConnection(this, index, address); + AddConnection(conn); + return conn; +} + +int RelayPort::SendTo(const void* data, + size_t size, + const SocketAddress& addr, bool payload) { + + // Try to find an entry for this specific address. Note that the first entry + // created was not given an address initially, so it can be set to the first + // address that comes along. + + RelayEntry* entry = 0; + + for (unsigned i = 0; i < entries_.size(); ++i) { + if (entries_[i]->address().IsAny() && payload) { + entry = entries_[i]; + entry->set_address(addr); + break; + } else if (entries_[i]->address() == addr) { + entry = entries_[i]; + break; + } + } + + // If we did not find one, then we make a new one. This will not be useable + // until it becomes connected, however. + if (!entry && payload) { + entry = new RelayEntry(this, addr, local_addr_); + if (!entries_.empty()) { + // Use the same port to connect to relay server + entry->SetServerIndex(entries_[0]->ServerIndex()); + } + entry->Connect(); + entries_.push_back(entry); + } + + // If the entry is connected, then we can send on it (though wrapping may + // still be necessary). Otherwise, we can't yet use this connection, so we + // default to the first one. + if (!entry || !entry->connected()) { + assert(!entries_.empty()); + entry = entries_[0]; + if (!entry->connected()) { + error_ = EWOULDBLOCK; + return SOCKET_ERROR; + } + } + + // Send the actual contents to the server using the usual mechanism. + int sent = entry->SendTo(data, size, addr); + if (sent <= 0) { + assert(sent < 0); + error_ = entry->GetError(); + return SOCKET_ERROR; + } + + // The caller of the function is expecting the number of user data bytes, + // rather than the size of the packet. + return (int)size; +} + +void RelayPort::OnMessage(Message *pmsg) { + switch (pmsg->message_id) { + case MSG_DISPOSE_SOCKET: { + DisposeSocketData * data = static_cast(pmsg->pdata); + delete data->data(); + delete data; + break; } + default: + Port::OnMessage(pmsg); + } +} + +int RelayPort::SetOption(Socket::Option opt, int value) { + int result = 0; + for (unsigned i = 0; i < entries_.size(); ++i) { + if (entries_[i]->socket()->SetOption(opt, value) < 0) { + result = -1; + error_ = entries_[i]->socket()->GetError(); + } + } + options_.push_back(OptionValue(opt, value)); + return result; +} + +int RelayPort::GetError() { + return error_; +} + +void RelayPort::OnReadPacket( + const char* data, size_t size, const SocketAddress& remote_addr) { + if (Connection* conn = GetConnection(remote_addr)) { + conn->OnReadPacket(data, size); + } else { + Port::OnReadPacket(data, size, remote_addr); + } +} + +void RelayPort::DisposeSocket(AsyncPacketSocket * socket) { + thread_->Post(this, MSG_DISPOSE_SOCKET, new DisposeSocketData(socket)); +} + +RelayEntry::RelayEntry(RelayPort* port, const SocketAddress& ext_addr, + const SocketAddress& local_addr) + : port_(port), ext_addr_(ext_addr), local_addr_(local_addr), server_index_(0), + socket_(0), connected_(false), locked_(false), requests_(port->thread()) { + + requests_.SignalSendPacket.connect(this, &RelayEntry::OnSendPacket); +} + +RelayEntry::~RelayEntry() { + delete socket_; +} + +void RelayEntry::Connect() { + assert(socket_ == 0); + const ProtocolAddress * ra = port()->ServerAddress(server_index_); + if (!ra) { + LOG(INFO) << "Out of relay server connections"; + return; + } + + LOG(INFO) << "Connecting to relay via " << ProtoToString(ra->proto) << " @ " << ra->address.ToString(); + + socket_ = port_->CreatePacketSocket(ra->proto); + assert(socket_ != 0); + + socket_->SignalReadPacket.connect(this, &RelayEntry::OnReadPacket); + if (socket_->Bind(local_addr_) < 0) + LOG(INFO) << "bind: " << std::strerror(socket_->GetError()); + + for (unsigned i = 0; i < port_->options().size(); ++i) + socket_->SetOption(port_->options()[i].first, port_->options()[i].second); + + if ((ra->proto == PROTO_TCP) || (ra->proto == PROTO_SSLTCP)) { + AsyncTCPSocket * tcp = static_cast(socket_); + tcp->SignalClose.connect(this, &RelayEntry::OnSocketClose); + tcp->SignalConnect.connect(this, &RelayEntry::OnSocketConnect); + tcp->Connect(ra->address); + } else { + requests_.Send(new AllocateRequest(this)); + } +} + +void RelayEntry::OnConnect(const SocketAddress& mapped_addr) { + ProtocolType proto = PROTO_UDP; + LOG(INFO) << "Relay allocate succeeded: " << ProtoToString(proto) << " @ " << mapped_addr.ToString(); + connected_ = true; + + port_->AddExternalAddress(ProtocolAddress(mapped_addr, proto)); + port_->SetReady(); +} + +int RelayEntry::SendTo(const void* data, + size_t size, + const SocketAddress& addr) { + + // If this connection is locked to the address given, then we can send the + // packet with no wrapper. + if (locked_ && (ext_addr_ == addr)) + return SendPacket(data, size); + + // Otherwise, we must wrap the given data in a STUN SEND request so that we + // can communicate the destination address to the server. + // + // Note that we do not use a StunRequest here. This is because there is + // likely no reason to resend this packet. If it is late, we just drop it. + // The next send to this address will try again. + + StunMessage request; + request.SetType(STUN_SEND_REQUEST); + request.SetTransactionID(CreateRandomString(16)); + + StunByteStringAttribute* magic_cookie_attr = + StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE); + magic_cookie_attr->CopyBytes(port_->magic_cookie().c_str(), + (uint16)port_->magic_cookie().size()); + request.AddAttribute(magic_cookie_attr); + + StunByteStringAttribute* username_attr = + StunAttribute::CreateByteString(STUN_ATTR_USERNAME); + username_attr->CopyBytes(port_->username_fragment().c_str(), + (uint16)port_->username_fragment().size()); + request.AddAttribute(username_attr); + + StunAddressAttribute* addr_attr = + StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS); + addr_attr->SetFamily(1); + addr_attr->SetIP(addr.ip()); + addr_attr->SetPort(addr.port()); + request.AddAttribute(addr_attr); + + // Attempt to lock + if (ext_addr_ == addr) { + StunUInt32Attribute* options_attr = + StunAttribute::CreateUInt32(STUN_ATTR_OPTIONS); + options_attr->SetValue(0x1); + request.AddAttribute(options_attr); + } + + StunByteStringAttribute* data_attr = + StunAttribute::CreateByteString(STUN_ATTR_DATA); + data_attr->CopyBytes(data, (uint16)size); + request.AddAttribute(data_attr); + + // TODO: compute the HMAC. + + ByteBuffer buf; + request.Write(&buf); + + return SendPacket(buf.Data(), buf.Length()); +} + +void RelayEntry::ScheduleKeepAlive() { + requests_.SendDelayed(new AllocateRequest(this), KEEPALIVE_DELAY); +} + +void RelayEntry::HandleConnectFailure() { + //if (GetMillisecondCount() - start_time_ > RETRY_TIMEOUT) + // return; + //ScheduleKeepAlive(); + + connected_ = false; + port()->DisposeSocket(socket_); + socket_ = 0; + server_index_ += 1; + Connect(); +} + +void RelayEntry::OnSocketConnect(AsyncTCPSocket* socket) { + assert(socket == socket_); + LOG(INFO) << "relay tcp connected to " << socket->GetRemoteAddress().ToString(); + requests_.Send(new AllocateRequest(this)); +} + +void RelayEntry::OnSocketClose(AsyncTCPSocket* socket, int error) { + assert(socket == socket_); + PLOG(LERROR, error) << "relay tcp connect failed"; + HandleConnectFailure(); +} + +void RelayEntry::OnReadPacket(const char* data, + size_t size, + const SocketAddress& remote_addr, + AsyncPacketSocket* socket) { + assert(socket == socket_); + //assert(remote_addr == port_->server_addr()); TODO: are we worried about this? + + // If the magic cookie is not present, then this is an unwrapped packet sent + // by the server, The actual remote address is the one we recorded. + if (!port_->HasMagicCookie(data, size)) { + if (locked_) { + port_->OnReadPacket(data, size, ext_addr_); + } else { + LOG(WARNING) << "Dropping packet: entry not locked"; + } + return; + } + + ByteBuffer buf(data, size); + StunMessage msg; + if (!msg.Read(&buf)) { + LOG(INFO) << "Incoming packet was not STUN"; + return; + } + + // The incoming packet should be a STUN ALLOCATE response, SEND response, or + // DATA indication. + if (requests_.CheckResponse(&msg)) { + return; + } else if (msg.type() == STUN_SEND_RESPONSE) { + if (const StunUInt32Attribute* options_attr = msg.GetUInt32(STUN_ATTR_OPTIONS)) { + if (options_attr->value() & 0x1) { + locked_ = true; + } + } + return; + } else if (msg.type() != STUN_DATA_INDICATION) { + LOG(INFO) << "Received BAD stun type from server: " << msg.type() + ; + return; + } + + // This must be a data indication. + + const StunAddressAttribute* addr_attr = + msg.GetAddress(STUN_ATTR_SOURCE_ADDRESS2); + if (!addr_attr) { + LOG(INFO) << "Data indication has no source address"; + return; + } else if (addr_attr->family() != 1) { + LOG(INFO) << "Source address has bad family"; + return; + } + + SocketAddress remote_addr2(addr_attr->ip(), addr_attr->port()); + + const StunByteStringAttribute* data_attr = msg.GetByteString(STUN_ATTR_DATA); + if (!data_attr) { + LOG(INFO) << "Data indication has no data"; + return; + } + + // Process the actual data and remote address in the normal manner. + port_->OnReadPacket(data_attr->bytes(), data_attr->length(), remote_addr2); +} + +void RelayEntry::OnSendPacket(const void* data, size_t size) { + SendPacket(data, size); +} + +int RelayEntry::SendPacket(const void* data, size_t size) { + const ProtocolAddress * ra = port_->ServerAddress(server_index_); + if (!ra) { + socket_->SetError(ENOTCONN); + return SOCKET_ERROR; + } + int sent = socket_->SendTo(data, size, ra->address); + if (sent <= 0) { + LOG(LS_VERBOSE) << "sendto: " << std::strerror(socket_->GetError()); + assert(sent < 0); + } + return sent; +} + +AllocateRequest::AllocateRequest(RelayEntry* entry) : entry_(entry) { + start_time_ = GetMillisecondCount(); +} + +void AllocateRequest::Prepare(StunMessage* request) { + request->SetType(STUN_ALLOCATE_REQUEST); + + StunByteStringAttribute* magic_cookie_attr = + StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE); + magic_cookie_attr->CopyBytes( + entry_->port()->magic_cookie().c_str(), + (uint16)entry_->port()->magic_cookie().size()); + request->AddAttribute(magic_cookie_attr); + + StunByteStringAttribute* username_attr = + StunAttribute::CreateByteString(STUN_ATTR_USERNAME); + username_attr->CopyBytes( + entry_->port()->username_fragment().c_str(), + (uint16)entry_->port()->username_fragment().size()); + request->AddAttribute(username_attr); +} + +int AllocateRequest::GetNextDelay() { + int delay = 100 * _max(1 << count_, 2); + count_ += 1; + if (count_ == 5) + timeout_ = true; + return delay; +} + +void AllocateRequest::OnResponse(StunMessage* response) { + const StunAddressAttribute* addr_attr = + response->GetAddress(STUN_ATTR_MAPPED_ADDRESS); + if (!addr_attr) { + LOG(INFO) << "Allocate response missing mapped address."; + } else if (addr_attr->family() != 1) { + LOG(INFO) << "Mapped address has bad family"; + } else { + SocketAddress addr(addr_attr->ip(), addr_attr->port()); + entry_->OnConnect(addr); + } + + // We will do a keep-alive regardless of whether this request suceeds. + // This should have almost no impact on network usage. + entry_->ScheduleKeepAlive(); +} + +void AllocateRequest::OnErrorResponse(StunMessage* response) { + const StunErrorCodeAttribute* attr = response->GetErrorCode(); + if (!attr) { + LOG(INFO) << "Bad allocate response error code"; + } else { + LOG(INFO) << "Allocate error response:" + << " code=" << static_cast(attr->error_code()) + << " reason='" << attr->reason() << "'"; + } + + if (GetMillisecondCount() - start_time_ <= RETRY_TIMEOUT) + entry_->ScheduleKeepAlive(); +} + +void AllocateRequest::OnTimeout() { + LOG(INFO) << "Allocate request timed out"; + entry_->HandleConnectFailure(); +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayport.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayport.h new file mode 100644 index 00000000..7cfdc015 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayport.h @@ -0,0 +1,93 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __RELAYPORT_H__ +#define __RELAYPORT_H__ + +#include "talk/p2p/base/port.h" +#include "talk/p2p/base/stunrequest.h" +#include + +namespace cricket { + +extern const std::string RELAY_PORT_TYPE; +class RelayEntry; + +// Communicates using an allocated port on the relay server. +class RelayPort: public Port { +public: + RelayPort( + Thread* thread, SocketFactory* factory, Network*, + const SocketAddress& local_addr, + const std::string& username, const std::string& password, + const std::string& magic_cookie); + virtual ~RelayPort(); + + void AddServerAddress(const ProtocolAddress& addr); + void AddExternalAddress(const ProtocolAddress& addr); + + typedef std::pair OptionValue; + const std::vector& options() const { return options_; } + + const std::string& magic_cookie() const { return magic_cookie_; } + bool HasMagicCookie(const char* data, size_t size); + + virtual void PrepareAddress(); + virtual Connection* CreateConnection(const Candidate& address, CandidateOrigin origin); + + virtual int SetOption(Socket::Option opt, int value); + virtual int GetError(); + + const ProtocolAddress * ServerAddress(size_t index) const; + + void DisposeSocket(AsyncPacketSocket * socket); + +protected: + void SetReady(); + + virtual int SendTo(const void* data, size_t size, const SocketAddress& addr, bool payload); + virtual void OnMessage(Message *pmsg); + + // Dispatches the given packet to the port or connection as appropriate. + void OnReadPacket( + const char* data, size_t size, const SocketAddress& remote_addr); + +private: + friend class RelayEntry; + + SocketAddress local_addr_; + std::deque server_addr_; + bool ready_; + std::vector entries_; + std::vector options_; + std::string magic_cookie_; + int error_; +}; + +} // namespace cricket + +#endif // __RELAYPORT_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver.cc new file mode 100644 index 00000000..bb52a1d5 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver.cc @@ -0,0 +1,657 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/p2p/base/relayserver.h" +#include "talk/p2p/base/helpers.h" +#include +#include +#include +#include + +#ifdef POSIX +extern "C" { +#include +} +#endif // POSIX + +namespace cricket { + +// By default, we require a ping every 90 seconds. +const int MAX_LIFETIME = 15 * 60 * 1000; + +// The number of bytes in each of the usernames we use. +const uint32 USERNAME_LENGTH = 16; + +// Calls SendTo on the given socket and logs any bad results. +void Send(AsyncPacketSocket* socket, const char* bytes, size_t size, + const SocketAddress& addr) { + int result = socket->SendTo(bytes, size, addr); + if (result < int(size)) { + std::cerr << "SendTo wrote only " << result << " of " << int(size) + << " bytes" << std::endl; + } else if (result < 0) { + std::cerr << "SendTo: " << std::strerror(errno) << std::endl; + } +} + +// Sends the given STUN message on the given socket. +void SendStun(const StunMessage& msg, + AsyncPacketSocket* socket, + const SocketAddress& addr) { + ByteBuffer buf; + msg.Write(&buf); + Send(socket, buf.Data(), buf.Length(), addr); +} + +// Constructs a STUN error response and sends it on the given socket. +void SendStunError(const StunMessage& msg, AsyncPacketSocket* socket, + const SocketAddress& remote_addr, int error_code, + const char* error_desc, const std::string& magic_cookie) { + + StunMessage err_msg; + err_msg.SetType(GetStunErrorResponseType(msg.type())); + err_msg.SetTransactionID(msg.transaction_id()); + + StunByteStringAttribute* magic_cookie_attr = + StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE); + if (magic_cookie.size() == 0) + magic_cookie_attr->CopyBytes(cricket::STUN_MAGIC_COOKIE_VALUE, 4); + else + magic_cookie_attr->CopyBytes(magic_cookie.c_str(), magic_cookie.size()); + err_msg.AddAttribute(magic_cookie_attr); + + StunErrorCodeAttribute* err_code = StunAttribute::CreateErrorCode(); + err_code->SetErrorClass(error_code / 100); + err_code->SetNumber(error_code % 100); + err_code->SetReason(error_desc); + err_msg.AddAttribute(err_code); + + SendStun(err_msg, socket, remote_addr); +} + +RelayServer::RelayServer(Thread* thread) : thread_(thread) { +} + +RelayServer::~RelayServer() { + for (unsigned i = 0; i < internal_sockets_.size(); i++) + delete internal_sockets_[i]; + for (unsigned i = 0; i < external_sockets_.size(); i++) + delete external_sockets_[i]; +} + +void RelayServer::AddInternalSocket(AsyncPacketSocket* socket) { + assert(internal_sockets_.end() == + std::find(internal_sockets_.begin(), internal_sockets_.end(), socket)); + internal_sockets_.push_back(socket); + socket->SignalReadPacket.connect(this, &RelayServer::OnInternalPacket); +} + +void RelayServer::RemoveInternalSocket(AsyncPacketSocket* socket) { + SocketList::iterator iter = + std::find(internal_sockets_.begin(), internal_sockets_.end(), socket); + assert(iter != internal_sockets_.end()); + internal_sockets_.erase(iter); + socket->SignalReadPacket.disconnect(this); +} + +void RelayServer::AddExternalSocket(AsyncPacketSocket* socket) { + assert(external_sockets_.end() == + std::find(external_sockets_.begin(), external_sockets_.end(), socket)); + external_sockets_.push_back(socket); + socket->SignalReadPacket.connect(this, &RelayServer::OnExternalPacket); +} + +void RelayServer::RemoveExternalSocket(AsyncPacketSocket* socket) { + SocketList::iterator iter = + std::find(external_sockets_.begin(), external_sockets_.end(), socket); + assert(iter != external_sockets_.end()); + external_sockets_.erase(iter); + socket->SignalReadPacket.disconnect(this); +} + +void RelayServer::OnInternalPacket( + const char* bytes, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket) { + + // Get the address of the connection we just received on. + SocketAddressPair ap(remote_addr, socket->GetLocalAddress()); + assert(!ap.destination().IsAny()); + + // If this did not come from an existing connection, it should be a STUN + // allocate request. + ConnectionMap::iterator piter = connections_.find(ap); + if (piter == connections_.end()) { + HandleStunAllocate(bytes, size, ap, socket); + return; + } + + RelayServerConnection* int_conn = piter->second; + + // Handle STUN requests to the server itself. + if (int_conn->binding()->HasMagicCookie(bytes, size)) { + HandleStun(int_conn, bytes, size); + return; + } + + // Otherwise, this is a non-wrapped packet that we are to forward. Make sure + // that this connection has been locked. (Otherwise, we would not know what + // address to forward to.) + if (!int_conn->locked()) { + std::cerr << "Dropping packet: connection not locked" << std::endl; + return; + } + + // Forward this to the destination address into the connection. + RelayServerConnection* ext_conn = int_conn->binding()->GetExternalConnection( + int_conn->default_destination()); + if (ext_conn) { + // TODO: Check the HMAC. + ext_conn->Send(bytes, size); + } else { + // This happens very often and is not an error. + //std::cerr << "Dropping packet: no external connection" << std::endl; + } +} + +void RelayServer::OnExternalPacket( + const char* bytes, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket) { + + // Get the address of the connection we just received on. + SocketAddressPair ap(remote_addr, socket->GetLocalAddress()); + assert(!ap.destination().IsAny()); + + // If this connection already exists, then forward the traffic. + ConnectionMap::iterator piter = connections_.find(ap); + if (piter != connections_.end()) { + // TODO: Check the HMAC. + RelayServerConnection* ext_conn = piter->second; + RelayServerConnection* int_conn = + ext_conn->binding()->GetInternalConnection( + ext_conn->addr_pair().source()); + assert(int_conn); + int_conn->Send(bytes, size, ext_conn->addr_pair().source()); + return; + } + + // The first packet should always be a STUN / TURN packet. If it isn't, then + // we should just ignore this packet. + StunMessage msg; + ByteBuffer buf = ByteBuffer(bytes, size); + if (!msg.Read(&buf)) { + std::cerr << "Dropping packet: first packet not STUN" << std::endl; + return; + } + + // The initial packet should have a username (which identifies the binding). + const StunByteStringAttribute* username_attr = + msg.GetByteString(STUN_ATTR_USERNAME); + if (!username_attr) { + std::cerr << "Dropping packet: no username" << std::endl; + return; + } + + uint32 length = _min(uint32(username_attr->length()), USERNAME_LENGTH); + std::string username(username_attr->bytes(), length); + // TODO: Check the HMAC. + + // The binding should already be present. + BindingMap::iterator biter = bindings_.find(username); + if (biter == bindings_.end()) { + // TODO: Turn this back on. This is the sign of a client bug. + //std::cerr << "Dropping packet: no binding with username" << std::endl; + return; + } + + // Add this authenticted connection to the binding. + RelayServerConnection* ext_conn = + new RelayServerConnection(biter->second, ap, socket); + ext_conn->binding()->AddExternalConnection(ext_conn); + AddConnection(ext_conn); + + // We always know where external packets should be forwarded, so we can lock + // them from the beginning. + ext_conn->Lock(); + + // Send this message on the appropriate internal connection. + RelayServerConnection* int_conn = ext_conn->binding()->GetInternalConnection( + ext_conn->addr_pair().source()); + assert(int_conn); + int_conn->Send(bytes, size, ext_conn->addr_pair().source()); +} + +bool RelayServer::HandleStun( + const char* bytes, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket, std::string* username, StunMessage* msg) { + + // Parse this into a stun message. + ByteBuffer buf = ByteBuffer(bytes, size); + if (!msg->Read(&buf)) { + SendStunError(*msg, socket, remote_addr, 400, "Bad Request", ""); + return false; + } + + // The initial packet should have a username (which identifies the binding). + const StunByteStringAttribute* username_attr = + msg->GetByteString(STUN_ATTR_USERNAME); + if (!username_attr) { + SendStunError(*msg, socket, remote_addr, 432, "Missing Username", ""); + return false; + } + + // Record the username if requested. + if (username) + username->append(username_attr->bytes(), username_attr->length()); + + // TODO: Check for unknown attributes (<= 0x7fff) + + return true; +} + +void RelayServer::HandleStunAllocate( + const char* bytes, size_t size, const SocketAddressPair& ap, + AsyncPacketSocket* socket) { + + // Make sure this is a valid STUN request. + StunMessage request; + std::string username; + if (!HandleStun(bytes, size, ap.source(), socket, &username, &request)) + return; + + // Make sure this is a an allocate request. + if (request.type() != STUN_ALLOCATE_REQUEST) { + SendStunError(request, + socket, + ap.source(), + 600, + "Operation Not Supported", + ""); + return; + } + + // TODO: Check the HMAC. + + // Find or create the binding for this username. + + RelayServerBinding* binding; + + BindingMap::iterator biter = bindings_.find(username); + if (biter != bindings_.end()) { + + binding = biter->second; + + } else { + + // NOTE: In the future, bindings will be created by the bot only. This + // else-branch will then disappear. + + // Compute the appropriate lifetime for this binding. + uint32 lifetime = MAX_LIFETIME; + const StunUInt32Attribute* lifetime_attr = + request.GetUInt32(STUN_ATTR_LIFETIME); + if (lifetime_attr) + lifetime = _min(lifetime, lifetime_attr->value() * 1000); + + binding = new RelayServerBinding(this, username, "0", lifetime); + binding->SignalTimeout.connect(this, &RelayServer::OnTimeout); + bindings_[username] = binding; + + std::cout << "Added new binding: " << bindings_.size() << " total" << std::endl; + } + + // Add this connection to the binding. It starts out unlocked. + RelayServerConnection* int_conn = + new RelayServerConnection(binding, ap, socket); + binding->AddInternalConnection(int_conn); + AddConnection(int_conn); + + // Now that we have a connection, this other method takes over. + HandleStunAllocate(int_conn, request); +} + +void RelayServer::HandleStun( + RelayServerConnection* int_conn, const char* bytes, size_t size) { + + // Make sure this is a valid STUN request. + StunMessage request; + std::string username; + if (!HandleStun(bytes, size, int_conn->addr_pair().source(), + int_conn->socket(), &username, &request)) + return; + + // Make sure the username is the one were were expecting. + if (username != int_conn->binding()->username()) { + int_conn->SendStunError(request, 430, "Stale Credentials"); + return; + } + + // TODO: Check the HMAC. + + // Send this request to the appropriate handler. + if (request.type() == STUN_SEND_REQUEST) + HandleStunSend(int_conn, request); + else if (request.type() == STUN_ALLOCATE_REQUEST) + HandleStunAllocate(int_conn, request); + else + int_conn->SendStunError(request, 600, "Operation Not Supported"); +} + +void RelayServer::HandleStunAllocate( + RelayServerConnection* int_conn, const StunMessage& request) { + + // Create a response message that includes an address with which external + // clients can communicate. + + StunMessage response; + response.SetType(STUN_ALLOCATE_RESPONSE); + response.SetTransactionID(request.transaction_id()); + + StunByteStringAttribute* magic_cookie_attr = + StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE); + magic_cookie_attr->CopyBytes(int_conn->binding()->magic_cookie().c_str(), + int_conn->binding()->magic_cookie().size()); + response.AddAttribute(magic_cookie_attr); + + size_t index = rand() % external_sockets_.size(); + SocketAddress ext_addr = external_sockets_[index]->GetLocalAddress(); + + StunAddressAttribute* addr_attr = + StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS); + addr_attr->SetFamily(1); + addr_attr->SetIP(ext_addr.ip()); + addr_attr->SetPort(ext_addr.port()); + response.AddAttribute(addr_attr); + + StunUInt32Attribute* res_lifetime_attr = + StunAttribute::CreateUInt32(STUN_ATTR_LIFETIME); + res_lifetime_attr->SetValue(int_conn->binding()->lifetime() / 1000); + response.AddAttribute(res_lifetime_attr); + + // TODO: Support transport-prefs (preallocate RTCP port). + // TODO: Support bandwidth restrictions. + // TODO: Add message integrity check. + + // Send a response to the caller. + int_conn->SendStun(response); +} + +void RelayServer::HandleStunSend( + RelayServerConnection* int_conn, const StunMessage& request) { + + const StunAddressAttribute* addr_attr = + request.GetAddress(STUN_ATTR_DESTINATION_ADDRESS); + if (!addr_attr) { + int_conn->SendStunError(request, 400, "Bad Request"); + return; + } + + const StunByteStringAttribute* data_attr = + request.GetByteString(STUN_ATTR_DATA); + if (!data_attr) { + int_conn->SendStunError(request, 400, "Bad Request"); + return; + } + + SocketAddress ext_addr(addr_attr->ip(), addr_attr->port()); + RelayServerConnection* ext_conn = + int_conn->binding()->GetExternalConnection(ext_addr); + if (!ext_conn) { + // This happens very often and is not an error. + //std::cerr << "Dropping packet: no external connection" << std::endl; + return; + } + + ext_conn->Send(data_attr->bytes(), data_attr->length()); + + const StunUInt32Attribute* options_attr = + request.GetUInt32(STUN_ATTR_OPTIONS); + if (options_attr && (options_attr->value() & 0x01 != 0)) { + int_conn->set_default_destination(ext_addr); + int_conn->Lock(); + + StunMessage response; + response.SetType(STUN_SEND_RESPONSE); + response.SetTransactionID(request.transaction_id()); + + StunByteStringAttribute* magic_cookie_attr = + StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE); + magic_cookie_attr->CopyBytes(int_conn->binding()->magic_cookie().c_str(), + int_conn->binding()->magic_cookie().size()); + response.AddAttribute(magic_cookie_attr); + + StunUInt32Attribute* options2_attr = + StunAttribute::CreateUInt32(cricket::STUN_ATTR_OPTIONS); + options2_attr->SetValue(0x01); + response.AddAttribute(options2_attr); + + int_conn->SendStun(response); + } +} + +void RelayServer::AddConnection(RelayServerConnection* conn) { + assert(connections_.find(conn->addr_pair()) == connections_.end()); + connections_[conn->addr_pair()] = conn; +} + +void RelayServer::RemoveConnection(RelayServerConnection* conn) { + ConnectionMap::iterator iter = connections_.find(conn->addr_pair()); + assert(iter != connections_.end()); + connections_.erase(iter); +} + +void RelayServer::RemoveBinding(RelayServerBinding* binding) { + BindingMap::iterator iter = bindings_.find(binding->username()); + assert(iter != bindings_.end()); + bindings_.erase(iter); + + std::cout << "Removed a binding: " << bindings_.size() << " remaining" << std::endl; +} + +void RelayServer::OnTimeout(RelayServerBinding* binding) { + // This call will result in all of the necessary clean-up. + delete binding; +} + +RelayServerConnection::RelayServerConnection( + RelayServerBinding* binding, const SocketAddressPair& addrs, + AsyncPacketSocket* socket) + : binding_(binding), addr_pair_(addrs), socket_(socket), locked_(false) { + + // The creation of a new connection constitutes a use of the binding. + binding_->NoteUsed(); +} + +RelayServerConnection::~RelayServerConnection() { + // Remove this connection from the server's map (if it exists there). + binding_->server()->RemoveConnection(this); +} + +void RelayServerConnection::Send(const char* data, size_t size) { + // Note that the binding has been used again. + binding_->NoteUsed(); + + cricket::Send(socket_, data, size, addr_pair_.source()); +} + +void RelayServerConnection::Send( + const char* data, size_t size, const SocketAddress& from_addr) { + // If the from address is known to the client, we don't need to send it. + if (locked() && (from_addr == default_dest_)) { + Send(data, size); + return; + } + + // Wrap the given data in a data-indication packet. + + StunMessage msg; + msg.SetType(STUN_DATA_INDICATION); + msg.SetTransactionID("0000000000000000"); + + StunByteStringAttribute* magic_cookie_attr = + StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE); + magic_cookie_attr->CopyBytes(binding_->magic_cookie().c_str(), + binding_->magic_cookie().size()); + msg.AddAttribute(magic_cookie_attr); + + StunAddressAttribute* addr_attr = + StunAttribute::CreateAddress(STUN_ATTR_SOURCE_ADDRESS2); + addr_attr->SetFamily(1); + addr_attr->SetIP(from_addr.ip()); + addr_attr->SetPort(from_addr.port()); + msg.AddAttribute(addr_attr); + + StunByteStringAttribute* data_attr = + StunAttribute::CreateByteString(STUN_ATTR_DATA); + assert(size <= 65536); + data_attr->CopyBytes(data, uint16(size)); + msg.AddAttribute(data_attr); + + SendStun(msg); +} + +void RelayServerConnection::SendStun(const StunMessage& msg) { + // Note that the binding has been used again. + binding_->NoteUsed(); + + cricket::SendStun(msg, socket_, addr_pair_.source()); +} + +void RelayServerConnection::SendStunError( + const StunMessage& request, int error_code, const char* error_desc) { + // An error does not indicate use. If no legitimate use off the binding + // occurs, we want it to be cleaned up even if errors are still occuring. + + cricket::SendStunError( + request, socket_, addr_pair_.source(), error_code, error_desc, + binding_->magic_cookie()); +} + +void RelayServerConnection::Lock() { + locked_ = true; +} + +void RelayServerConnection::Unlock() { + locked_ = false; +} + +// IDs used for posted messages: +const uint32 MSG_LIFETIME_TIMER = 1; + +RelayServerBinding::RelayServerBinding( + RelayServer* server, const std::string& username, + const std::string& password, uint32 lifetime) + : server_(server), username_(username), password_(password), + lifetime_(lifetime) { + + // For now, every connection uses the standard magic cookie value. + magic_cookie_.append( + reinterpret_cast(STUN_MAGIC_COOKIE_VALUE), 4); + + // Initialize the last-used time to now. + NoteUsed(); + + // Set the first timeout check. + server_->thread()->PostDelayed(lifetime_, this, MSG_LIFETIME_TIMER); +} + +RelayServerBinding::~RelayServerBinding() { + // Clear the outstanding timeout check. + server_->thread()->Clear(this); + + // Clean up all of the connections. + for (size_t i = 0; i < internal_connections_.size(); ++i) + delete internal_connections_[i]; + for (size_t i = 0; i < external_connections_.size(); ++i) + delete external_connections_[i]; + + // Remove this binding from the server's map. + server_->RemoveBinding(this); +} + +void RelayServerBinding::AddInternalConnection(RelayServerConnection* conn) { + internal_connections_.push_back(conn); +} + +void RelayServerBinding::AddExternalConnection(RelayServerConnection* conn) { + external_connections_.push_back(conn); +} + +void RelayServerBinding::NoteUsed() { + last_used_ = GetMillisecondCount(); +} + +bool RelayServerBinding::HasMagicCookie(const char* bytes, size_t size) const { + if (size < 24 + magic_cookie_.size()) { + return false; + } else { + return 0 == std::memcmp( + bytes + 24, magic_cookie_.c_str(), magic_cookie_.size()); + } +} + +RelayServerConnection* RelayServerBinding::GetInternalConnection( + const SocketAddress& ext_addr) { + + // Look for an internal connection that is locked to this address. + for (size_t i = 0; i < internal_connections_.size(); ++i) { + if (internal_connections_[i]->locked() && + (ext_addr == internal_connections_[i]->default_destination())) + return internal_connections_[i]; + } + + // If one was not found, we send to the first connection. + assert(internal_connections_.size() > 0); + return internal_connections_[0]; +} + +RelayServerConnection* RelayServerBinding::GetExternalConnection( + const SocketAddress& ext_addr) { + for (size_t i = 0; i < external_connections_.size(); ++i) { + if (ext_addr == external_connections_[i]->addr_pair().source()) + return external_connections_[i]; + } + return 0; +} + +void RelayServerBinding::OnMessage(Message *pmsg) { + if (pmsg->message_id == MSG_LIFETIME_TIMER) { + assert(!pmsg->pdata); + + // If the lifetime timeout has been exceeded, then send a signal. + // Otherwise, just keep waiting. + if (GetMillisecondCount() >= last_used_ + lifetime_) { + SignalTimeout(this); + } else { + server_->thread()->PostDelayed(lifetime_, this, MSG_LIFETIME_TIMER); + } + + } else { + assert(false); + } +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver.h new file mode 100644 index 00000000..01dc3678 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver.h @@ -0,0 +1,210 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __RELAYSERVER_H__ +#define __RELAYSERVER_H__ + +#include "talk/base/asyncudpsocket.h" +#include "talk/base/socketaddresspair.h" +#include "talk/base/thread.h" +#include "talk/base/jtime.h" +#include "talk/p2p/base/stun.h" + +#include +#include +#include + +namespace cricket { + +class RelayServerBinding; +class RelayServerConnection; + +// Relays traffic between connections to the server that are "bound" together. +// All connections created with the same username/password are bound together. +class RelayServer : public sigslot::has_slots<> { +public: + // Creates a server, which will use this thread to post messages to itself. + RelayServer(Thread* thread); + ~RelayServer(); + + Thread* thread() { return thread_; } + + // Updates the set of sockets that the server uses to talk to "internal" + // clients. These are clients that do the "port allocations". + void AddInternalSocket(AsyncPacketSocket* socket); + void RemoveInternalSocket(AsyncPacketSocket* socket); + + // Updates the set of sockets that the server uses to talk to "external" + // clients. These are the clients that do not do allocations. They do not + // know that these addresses represent a relay server. + void AddExternalSocket(AsyncPacketSocket* socket); + void RemoveExternalSocket(AsyncPacketSocket* socket); + +private: + typedef std::vector SocketList; + typedef std::map BindingMap; + typedef std::map ConnectionMap; + + Thread* thread_; + SocketList internal_sockets_; + SocketList external_sockets_; + BindingMap bindings_; + ConnectionMap connections_; + + // Called when a packet is received by the server on one of its sockets. + void OnInternalPacket( + const char* bytes, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket); + void OnExternalPacket( + const char* bytes, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket); + + // Processes the relevant STUN request types from the client. + bool HandleStun(const char* bytes, size_t size, + const SocketAddress& remote_addr, AsyncPacketSocket* socket, + std::string* username, StunMessage* msg); + void HandleStunAllocate(const char* bytes, size_t size, + const SocketAddressPair& ap, + AsyncPacketSocket* socket); + void HandleStun(RelayServerConnection* int_conn, const char* bytes, + size_t size); + void HandleStunAllocate(RelayServerConnection* int_conn, + const StunMessage& msg); + void HandleStunSend(RelayServerConnection* int_conn, const StunMessage& msg); + + // Adds/Removes the a connection or binding. + void AddConnection(RelayServerConnection* conn); + void RemoveConnection(RelayServerConnection* conn); + void RemoveBinding(RelayServerBinding* binding); + + // Called when the timer for checking lifetime times out. + void OnTimeout(RelayServerBinding* binding); + + friend class RelayServerConnection; + friend class RelayServerBinding; +}; + +// Maintains information about a connection to the server. Each connection is +// part of one and only one binding. +class RelayServerConnection { +public: + RelayServerConnection(RelayServerBinding* binding, + const SocketAddressPair& addrs, + AsyncPacketSocket* socket); + ~RelayServerConnection(); + + RelayServerBinding* binding() { return binding_; } + AsyncPacketSocket* socket() { return socket_; } + + // Returns a pair where the source is the remote address and the destination + // is the local address. + const SocketAddressPair& addr_pair() { return addr_pair_; } + + // Sends a packet to the connected client. If an address is provided, then + // we make sure the internal client receives it, wrapping if necessary. + void Send(const char* data, size_t size); + void Send(const char* data, size_t size, const SocketAddress& ext_addr); + + // Sends a STUN message to the connected client with no wrapping. + void SendStun(const StunMessage& msg); + void SendStunError(const StunMessage& request, int code, const char* desc); + + // A locked connection is one for which we know the intended destination of + // any raw packet received. + bool locked() const { return locked_; } + void Lock(); + void Unlock(); + + // Records the address that raw packets should be forwarded to (for internal + // packets only; for external, we already know where they go). + const SocketAddress& default_destination() const { return default_dest_; } + void set_default_destination(const SocketAddress& addr) { + default_dest_ = addr; + } + +private: + RelayServerBinding* binding_; + SocketAddressPair addr_pair_; + AsyncPacketSocket* socket_; + bool locked_; + SocketAddress default_dest_; +}; + +// Records a set of internal and external connections that we relay between, +// or in other words, that are "bound" together. +class RelayServerBinding : public MessageHandler { +public: + RelayServerBinding( + RelayServer* server, const std::string& username, + const std::string& password, uint32 lifetime); + virtual ~RelayServerBinding(); + + RelayServer* server() { return server_; } + uint32 lifetime() { return lifetime_; } + const std::string& username() { return username_; } + const std::string& password() { return password_; } + const std::string& magic_cookie() { return magic_cookie_; } + + // Adds/Removes a connection into the binding. + void AddInternalConnection(RelayServerConnection* conn); + void AddExternalConnection(RelayServerConnection* conn); + + // We keep track of the use of each binding. If we detect that it was not + // used for longer than the lifetime, then we send a signal. + void NoteUsed(); + sigslot::signal1 SignalTimeout; + + // Determines whether the given packet has the magic cookie present (in the + // right place). + bool HasMagicCookie(const char* bytes, size_t size) const; + + // Determines the connection to use to send packets to or from the given + // external address. + RelayServerConnection* GetInternalConnection(const SocketAddress& ext_addr); + RelayServerConnection* GetExternalConnection(const SocketAddress& ext_addr); + + // MessageHandler: + void OnMessage(Message *pmsg); + +private: + RelayServer* server_; + + std::string username_; + std::string password_; + std::string magic_cookie_; + + std::vector internal_connections_; + std::vector external_connections_; + + uint32 lifetime_; + uint32 last_used_; + // TODO: bandwidth +}; + +} // namespace cricket + +#endif // __RELAYSERVER_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver.pro b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver.pro new file mode 100644 index 00000000..41bc6b63 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver.pro @@ -0,0 +1,14 @@ +TEMPLATE = app +INCLUDEPATH = ../../.. +DEFINES += POSIX + +include(../../../../../conf.pri) + +# Input +SOURCES += \ + relayserver.cc \ + relayserver_main.cc \ + ../../base/host.cc \ + ../../base/socketaddresspair.cc + +LIBS += ../../../liblibjingle.a diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver_main.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver_main.cc new file mode 100644 index 00000000..5f624f37 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/relayserver_main.cc @@ -0,0 +1,75 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/base/host.h" +#include "talk/base/thread.h" +#include "talk/p2p/base/relayserver.h" +#include +#include + +#ifdef POSIX +extern "C" { +#include +} +#endif // POSIX + +using namespace cricket; + +int main(int argc, char **argv) { + if (argc != 1) { + std::cerr << "usage: relayserver" << std::endl; + return 1; + } + + assert(LocalHost().networks().size() >= 2); + SocketAddress int_addr(LocalHost().networks()[1]->ip(), 5000); + SocketAddress ext_addr(LocalHost().networks()[1]->ip(), 5001); + + Thread *pthMain = Thread::Current(); + + AsyncUDPSocket* int_socket = CreateAsyncUDPSocket(pthMain->socketserver()); + if (int_socket->Bind(int_addr) < 0) { + std::cerr << "bind: " << std::strerror(errno) << std::endl; + return 1; + } + + AsyncUDPSocket* ext_socket = CreateAsyncUDPSocket(pthMain->socketserver()); + if (ext_socket->Bind(ext_addr) < 0) { + std::cerr << "bind: " << std::strerror(errno) << std::endl; + return 1; + } + + RelayServer server(pthMain); + server.AddInternalSocket(int_socket); + server.AddExternalSocket(ext_socket); + + std::cout << "Listening internally at " << int_addr.ToString() << std::endl; + std::cout << "Listening externally at " << ext_addr.ToString() << std::endl; + + pthMain->Loop(); + return 0; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/session.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/session.cc new file mode 100644 index 00000000..73873338 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/session.cc @@ -0,0 +1,421 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/base/common.h" +#include "talk/base/logging.h" +#include "talk/p2p/base/helpers.h" +#include "talk/p2p/base/session.h" + +namespace cricket { + +const uint32 MSG_TIMEOUT = 1; +const uint32 MSG_ERROR = 2; +const uint32 MSG_STATE = 3; + +Session::Session(SessionManager *session_manager, const std::string &name, + const SessionID& id) { + session_manager_ = session_manager; + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + name_ = name; + id_ = id; + error_ = ERROR_NONE; + state_ = STATE_INIT; + initiator_ = false; + description_ = NULL; + remote_description_ = NULL; + socket_manager_ = new SocketManager(session_manager_); + socket_manager_->SignalCandidatesReady.connect(this, &Session::OnCandidatesReady); + socket_manager_->SignalNetworkError.connect(this, &Session::OnNetworkError); + socket_manager_->SignalState.connect(this, &Session::OnSocketState); + socket_manager_->SignalRequestSignaling.connect(this, &Session::OnRequestSignaling); +} + +Session::~Session() { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + delete description_; + delete remote_description_; + delete socket_manager_; + session_manager_->signaling_thread()->Clear(this); +} + +P2PSocket *Session::CreateSocket(const std::string &name) { + return socket_manager_->CreateSocket(name); +} + +void Session::DestroySocket(P2PSocket *socket) { + socket_manager_->DestroySocket(socket); +} + +void Session::OnCandidatesReady(const std::vector& candidates) { + SendSessionMessage(SessionMessage::TYPE_CANDIDATES, NULL, &candidates, NULL); +} + +void Session::OnNetworkError() { + // Socket manager is experiencing a network error trying to allocate + // network resources (usually port allocation) + + set_error(ERROR_NETWORK); +} + +void Session::OnSocketState() { + // If the call is not in progress, then we don't care about writability. + // We have separate timers for making sure we transition back to the in- + // progress state in time. + if (state_ != STATE_INPROGRESS) + return; + + // Put the timer into the write state. This is called when the state changes, + // so we will restart the timer each time we lose writability. + if (socket_manager_->writable()) { + session_manager_->signaling_thread()->Clear(this, MSG_TIMEOUT); + } else { + session_manager_->signaling_thread()->PostDelayed( + session_manager_->session_timeout() * 1000, this, MSG_TIMEOUT); + } +} + +void Session::OnRequestSignaling() { + SignalRequestSignaling(); +} + +void Session::OnSignalingReady() { + socket_manager_->OnSignalingReady(); +} + +void Session::SendSessionMessage(SessionMessage::Type type, + const SessionDescription* description, + const std::vector* candidates, + SessionMessage::Cookie* redirect_cookie) { + SessionMessage m; + m.set_type(type); + m.set_to(remote_address_); + m.set_name(name_); + m.set_description(description); + m.set_session_id(id_); + if (candidates) + m.set_candidates(*candidates); + m.set_redirect_target(redirect_target_); + m.set_redirect_cookie(redirect_cookie); + SignalOutgoingMessage(this, m); +} + +bool Session::Initiate(const std::string &to, const SessionDescription *description) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + // Only from STATE_INIT + if (state_ != STATE_INIT) + return false; + + // Setup for signaling. Initiate is asynchronous. It occurs once the address + // candidates are ready. + initiator_ = true; + remote_address_ = to; + description_ = description; + SendSessionMessage(SessionMessage::TYPE_INITIATE, description, NULL, NULL); + set_state(Session::STATE_SENTINITIATE); + + // Let the socket manager know we now want the candidates + socket_manager_->StartProcessingCandidates(); + + // Start the session timeout + session_manager_->signaling_thread()->Clear(this, MSG_TIMEOUT); + session_manager_->signaling_thread()->PostDelayed(session_manager_->session_timeout() * 1000, this, MSG_TIMEOUT); + return true; +} + +bool Session::Accept(const SessionDescription *description) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + // Only if just received initiate + if (state_ != STATE_RECEIVEDINITIATE) + return false; + + // Setup for signaling. Accept is asynchronous. It occurs once the address + // candidates are ready. + initiator_ = false; + description_ = description; + SendSessionMessage(SessionMessage::TYPE_ACCEPT, description, NULL, NULL); + set_state(Session::STATE_SENTACCEPT); + + return true; +} + +bool Session::Modify(const SessionDescription *description) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + // Only if session already STATE_INPROGRESS + if (state_ != STATE_INPROGRESS) + return false; + + // Modify is asynchronous. It occurs once the address candidates are ready. + // Either side can send a modify. It is only valid in an already accepted + // session. + description_ = description; + SendSessionMessage(SessionMessage::TYPE_MODIFY, description, NULL, NULL); + set_state(Session::STATE_SENTMODIFY); + + // Start the session timeout + session_manager_->signaling_thread()->Clear(this, MSG_TIMEOUT); + session_manager_->signaling_thread()->PostDelayed(session_manager_->session_timeout() * 1000, this, MSG_TIMEOUT); + return true; +} + +bool Session::Redirect(const std::string& target) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + // Redirect is sent in response to an initiate or modify, to redirect the + // request + if (state_ != STATE_RECEIVEDINITIATE) + return false; + + initiator_ = false; + redirect_target_ = target; + SendSessionMessage(SessionMessage::TYPE_REDIRECT, NULL, NULL, NULL); + + // A redirect puts us in the same state as reject. It just sends a different + // kind of reject message, if you like. + set_state(STATE_SENTREDIRECT); + + return true; +} + +bool Session::Reject() { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + // Reject is sent in response to an initiate or modify, to reject the + // request + if (state_ != STATE_RECEIVEDINITIATE && state_ != STATE_RECEIVEDMODIFY) + return false; + + initiator_ = false; + SendSessionMessage(SessionMessage::TYPE_REJECT, NULL, NULL, NULL); + set_state(STATE_SENTREJECT); + + return true; +} + +bool Session::Terminate() { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + // Either side can terminate, at any time. + if (state_ == STATE_SENTTERMINATE && state_ != STATE_RECEIVEDTERMINATE) + return false; + + // But we don't need to terminate if we already rejected. The other client + // already knows that we're done with this session. + if (state_ != STATE_SENTREDIRECT) + SendSessionMessage(SessionMessage::TYPE_TERMINATE, NULL, NULL, NULL); + + set_state(STATE_SENTTERMINATE); + + return true; +} + +void Session::OnIncomingError(const SessionMessage &m) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + // If a candidate message errors out or gets dropped for some reason we + // ignore the error. + if (m.type() != SessionMessage::TYPE_CANDIDATES) { + set_error(ERROR_RESPONSE); + } +} + +void Session::OnIncomingMessage(const SessionMessage &m) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + switch (m.type()) { + case SessionMessage::TYPE_INITIATE: + remote_description_ = m.description(); + remote_address_ = m.from(); + name_ = m.name(); + initiator_ = false; + set_state(STATE_RECEIVEDINITIATE); + + // Let the socket manager know we now want the initial candidates + socket_manager_->StartProcessingCandidates(); + break; + + case SessionMessage::TYPE_ACCEPT: + remote_description_ = m.description(); + set_state(STATE_RECEIVEDACCEPT); + break; + + case SessionMessage::TYPE_MODIFY: + remote_description_ = m.description(); + set_state(STATE_RECEIVEDMODIFY); + break; + + case SessionMessage::TYPE_CANDIDATES: + socket_manager_->AddRemoteCandidates(m.candidates()); + break; + + case SessionMessage::TYPE_REJECT: + set_state(STATE_RECEIVEDREJECT); + break; + + case SessionMessage::TYPE_REDIRECT: + OnRedirectMessage(m); + break; + + case SessionMessage::TYPE_TERMINATE: + set_state(STATE_RECEIVEDTERMINATE); + break; + } +} + +void Session::OnRedirectMessage(const SessionMessage &m) { + ASSERT(state_ == STATE_SENTINITIATE); + if (state_ != STATE_SENTINITIATE) + return; + + ASSERT(m.redirect_target().size() != 0); + remote_address_ = m.redirect_target(); + + SendSessionMessage(SessionMessage::TYPE_INITIATE, description_, NULL, + m.redirect_cookie()->Copy()); + + // Restart the session timeout. + session_manager_->signaling_thread()->Clear(this, MSG_TIMEOUT); + session_manager_->signaling_thread()->PostDelayed(session_manager_->session_timeout() * 1000, this, MSG_TIMEOUT); + + // Reset all of the sockets back into the initial state. + socket_manager_->ResetSockets(); +} + +Session::State Session::state() { + return state_; +} + +void Session::set_state(State state) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + if (state != state_) { + state_ = state; + SignalState(this, state); + session_manager_->signaling_thread()->Post(this, MSG_STATE); + } +} + +Session::Error Session::error() { + return error_; +} + +void Session::set_error(Error error) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + if (error != error_) { + error_ = error; + SignalError(this, error); + session_manager_->signaling_thread()->Post(this, MSG_ERROR); + } +} + +const std::string &Session::name() { + return name_; +} + +const std::string &Session::remote_address() { + return remote_address_; +} + +bool Session::initiator() { + return initiator_; +} + +const SessionID& Session::id() { + return id_; +} + +const SessionDescription *Session::description() { + return description_; +} + +const SessionDescription *Session::remote_description() { + return remote_description_; +} + +SessionManager *Session::session_manager() { + return session_manager_; +} + +void Session::OnMessage(Message *pmsg) { + switch(pmsg->message_id) { + case MSG_TIMEOUT: + // Session timeout has occured. Check to see if the session is still trying + // to signal. If so, the session has timed out. + // The Sockets have their own timeout for connectivity. + set_error(ERROR_TIME); + break; + + case MSG_ERROR: + switch (error_) { + case ERROR_RESPONSE: + // This state could be reached if we get an error in response to an IQ + // or if the network is so slow we time out on an individual IQ exchange. + // In either case, Terminate (send more messages) and ignore the likely + // cascade of more errors. + + // fall through + case ERROR_NETWORK: + case ERROR_TIME: + // Time ran out - no response + Terminate(); + break; + + default: + break; + } + break; + + case MSG_STATE: + switch (state_) { + case STATE_SENTACCEPT: + case STATE_RECEIVEDACCEPT: + set_state(STATE_INPROGRESS); + session_manager_->signaling_thread()->Clear(this, MSG_TIMEOUT); + OnSocketState(); // Update the writability timeout state. + break; + + case STATE_SENTREJECT: + case STATE_SENTREDIRECT: + case STATE_RECEIVEDREJECT: + Terminate(); + break; + + case STATE_SENTTERMINATE: + case STATE_RECEIVEDTERMINATE: + session_manager_->DestroySession(this); + break; + + default: + // explicitly ignoring some states here + break; + } + break; + } +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/session.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/session.h new file mode 100644 index 00000000..1414a375 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/session.h @@ -0,0 +1,140 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _SESSION_H_ +#define _SESSION_H_ + +#include "talk/base/socketaddress.h" +#include "talk/p2p/base/sessiondescription.h" +#include "talk/p2p/base/sessionmanager.h" +#include "talk/p2p/base/sessionmessage.h" +#include "talk/p2p/base/socketmanager.h" +#include "talk/p2p/base/p2psocket.h" +#include "talk/p2p/base/port.h" +#include + +namespace cricket { + +class SessionManager; +class SocketManager; + +// A specific Session created by the SessionManager +// A Session manages signaling for session setup and tear down, and connectivity +// with P2PSockets + +class Session : public MessageHandler, public sigslot::has_slots<> { +public: + enum State { + STATE_INIT = 0, + STATE_SENTINITIATE, // sent initiate, waiting for Accept or Reject + STATE_RECEIVEDINITIATE, // received an initiate. Call Accept or Reject + STATE_SENTACCEPT, // sent accept. begin connectivity establishment + STATE_RECEIVEDACCEPT, // received accept. begin connectivity establishment + STATE_SENTMODIFY, // sent modify, waiting for Accept or Reject + STATE_RECEIVEDMODIFY, // received modify, call Accept or Reject + STATE_SENTREJECT, // sent reject after receiving initiate + STATE_RECEIVEDREJECT, // received reject after sending initiate + STATE_SENTREDIRECT, // sent direct after receiving initiate + STATE_SENTTERMINATE, // sent terminate (any time / either side) + STATE_RECEIVEDTERMINATE, // received terminate (any time / either side) + STATE_INPROGRESS, // session accepted and in progress + }; + + enum Error { + ERROR_NONE = 0, // no error + ERROR_TIME, // no response to signaling + ERROR_RESPONSE, // error during signaling + ERROR_NETWORK, // network error, could not allocate network resources + }; + + Session(SessionManager *session_manager, const std::string &name, const SessionID& id); + ~Session(); + + // From MessageHandler + void OnMessage(Message *pmsg); + + P2PSocket *CreateSocket(const std::string & name); + void DestroySocket(P2PSocket *socket); + + bool Initiate(const std::string &to, const SessionDescription *description); + bool Accept(const SessionDescription *description); + bool Modify(const SessionDescription *description); + bool Reject(); + bool Redirect(const std::string& target); + bool Terminate(); + + SessionManager *session_manager(); + const std::string &name(); + const std::string &remote_address(); + bool initiator(); + const SessionID& id(); + const SessionDescription *description(); + const SessionDescription *remote_description(); + + State state(); + Error error(); + + void OnSignalingReady(); + void OnIncomingMessage(const SessionMessage &m); + void OnIncomingError(const SessionMessage &m); + + sigslot::signal2 SignalState; + sigslot::signal2 SignalError; + sigslot::signal2 SignalOutgoingMessage; + sigslot::signal0<> SignalRequestSignaling; + +private: + void SendSessionMessage(SessionMessage::Type type, + const SessionDescription* description, + const std::vector* candidates, + SessionMessage::Cookie* redirect_cookie); + void OnCandidatesReady(const std::vector& candidates); + void OnNetworkError(); + void OnSocketState(); + void OnRequestSignaling(); + void OnRedirectMessage(const SessionMessage &m); + + void set_state(State state); + void set_error(Error error); + + bool initiator_; + SessionManager *session_manager_; + SessionID id_; + SocketManager *socket_manager_; + std::string name_; + std::string remote_address_; + const SessionDescription *description_; + const SessionDescription *remote_description_; + std::string redirect_target_; + State state_; + Error error_; + CriticalSection crit_; +}; + +} // namespace cricket + +#endif // _SESSION_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessiondescription.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessiondescription.h new file mode 100644 index 00000000..28b70845 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessiondescription.h @@ -0,0 +1,42 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _SESSIONDESCRIPTION_H_ +#define _SESSIONDESCRIPTION_H_ + +namespace cricket { + +// The client overrides this with whatever + +class SessionDescription { +public: + virtual ~SessionDescription() {} +}; + +} // namespace cricket + +#endif // _SESSIONDESCRIPTION_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionid.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionid.h new file mode 100644 index 00000000..a12535c0 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionid.h @@ -0,0 +1,94 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _SESSIONID_H_ +#define _SESSIONID_H_ + +#include "talk/base/basictypes.h" +#include +#include + +namespace cricket { + +// Each session is identified by a pair (from,id), where id is only +// assumed to be unique to the machine identified by from. +class SessionID { +public: + SessionID() : id_str_("0") { + } + SessionID(const std::string& initiator, uint32 id) + : initiator_(initiator) { + set_id(id); + } + SessionID(const SessionID& sid) + : id_str_(sid.id_str_), initiator_(sid.initiator_) { + } + + void set_id(uint32 id) { + std::stringstream st; + st << id; + st >> id_str_; + } + const std::string id_str() const { + return id_str_; + } + void set_id_str(const std::string &id_str) { + id_str_ = id_str; + } + + const std::string &initiator() const { + return initiator_; + } + void set_initiator(const std::string &initiator) { + initiator_ = initiator; + } + + bool operator <(const SessionID& sid) const { + int r = initiator_.compare(sid.initiator_); + if (r == 0) + r = id_str_.compare(sid.id_str_); + return r < 0; + } + + bool operator ==(const SessionID& sid) const { + return (id_str_ == sid.id_str_) && (initiator_ == sid.initiator_); + } + + SessionID& operator =(const SessionID& sid) { + id_str_ = sid.id_str_; + initiator_ = sid.initiator_; + return *this; + } + +private: + std::string id_str_; + std::string initiator_; +}; + +} // namespace cricket + +#endif // _SESSIONID_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionmanager.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionmanager.cc new file mode 100644 index 00000000..4c1c09d9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionmanager.cc @@ -0,0 +1,173 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/base/common.h" +#include "talk/p2p/base/helpers.h" +#include "sessionmanager.h" + +namespace cricket { + +SessionManager::SessionManager(PortAllocator *allocator, Thread *worker) { + allocator_ = allocator; + signaling_thread_ = Thread::Current(); + if (worker == NULL) { + worker_thread_ = Thread::Current(); + } else { + worker_thread_ = worker; + } + timeout_ = 50; +} + +SessionManager::~SessionManager() { + // Note: Session::Terminate occurs asynchronously, so it's too late to + // delete them now. They better be all gone. + ASSERT(session_map_.empty()); + //TerminateAll(); +} + +Session *SessionManager::CreateSession(const std::string &name, const std::string& initiator) { + return CreateSession(name, SessionID(initiator, CreateRandomId()), false); +} + +Session *SessionManager::CreateSession(const std::string &name, const SessionID& id, bool received_initiate) { + Session *session = new Session(this, name, id); + session_map_[session->id()] = session; + session->SignalRequestSignaling.connect(this, &SessionManager::OnRequestSignaling); + SignalSessionCreate(session, received_initiate); + return session; +} + +void SessionManager::DestroySession(Session *session) { + if (session != NULL) { + std::map::iterator it = session_map_.find(session->id()); + if (it != session_map_.end()) { + SignalSessionDestroy(session); + session_map_.erase(it); + delete session; + } + } +} + +Session *SessionManager::GetSession(const SessionID& id) { + // If the id isn't present, the [] operator will make a NULL entry + std::map::iterator it = session_map_.find(id); + if (it != session_map_.end()) + return (*it).second; + return NULL; +} + +void SessionManager::TerminateAll() { + while (session_map_.begin() != session_map_.end()) { + Session *session = session_map_.begin()->second; + session->Terminate(); + } +} + +void SessionManager::OnIncomingError(const SessionMessage &m) { + // Incoming signaling error. This means, as the result of trying + // to send message m, and error was generated. In all cases, a + // session should already exist + + Session *session; + switch (m.type()) { + case SessionMessage::TYPE_INITIATE: + case SessionMessage::TYPE_ACCEPT: + case SessionMessage::TYPE_MODIFY: + case SessionMessage::TYPE_CANDIDATES: + case SessionMessage::TYPE_REJECT: + case SessionMessage::TYPE_TERMINATE: + session = GetSession(m.session_id()); + break; + + default: + return; + } + + if (session != NULL) + session->OnIncomingError(m); + +} + +void SessionManager::OnIncomingMessage(const SessionMessage &m) { + // In the case of an incoming initiate, there is no session yet, and one needs to be created. + // The other cases have sessions already. + + Session *session; + switch (m.type()) { + case SessionMessage::TYPE_INITIATE: + session = CreateSession(m.name(), m.session_id(), true); + break; + + case SessionMessage::TYPE_ACCEPT: + case SessionMessage::TYPE_MODIFY: + case SessionMessage::TYPE_CANDIDATES: + case SessionMessage::TYPE_REJECT: + case SessionMessage::TYPE_REDIRECT: + case SessionMessage::TYPE_TERMINATE: + session = GetSession(m.session_id()); + break; + + default: + return; + } + + if (session != NULL) + session->OnIncomingMessage(m); +} + +void SessionManager::OnSignalingReady() { + for (std::map::iterator it = session_map_.begin(); + it != session_map_.end(); ++it) { + it->second->OnSignalingReady(); + } +} + +void SessionManager::OnRequestSignaling() { + SignalRequestSignaling(); +} + +PortAllocator *SessionManager::port_allocator() const { + return allocator_; +} + +Thread *SessionManager::worker_thread() const { + return worker_thread_; +} + +Thread *SessionManager::signaling_thread() const { + return signaling_thread_; +} + +int SessionManager::session_timeout() { + return timeout_; +} + +void SessionManager::set_session_timeout(int timeout) { + timeout_ = timeout; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionmanager.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionmanager.h new file mode 100644 index 00000000..5ce0e4c5 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionmanager.h @@ -0,0 +1,86 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _SESSIONMANAGER_H_ +#define _SESSIONMANAGER_H_ + +#include "talk/base/thread.h" +#include "talk/p2p/base/portallocator.h" +#include "talk/p2p/base/session.h" +#include "talk/p2p/base/sessionmessage.h" +#include "talk/base/sigslot.h" + +#include +#include +#include + +namespace cricket { + +class Session; + +// SessionManager manages session instances + +class SessionManager : public sigslot::has_slots<> { +public: + SessionManager(PortAllocator *allocator, Thread *worker_thread = NULL); + virtual ~SessionManager(); + + Session *CreateSession(const std::string &name, const std::string& initiator); + void DestroySession(Session *session); + Session *GetSession(const SessionID& id); + void TerminateAll(); + void OnIncomingMessage(const SessionMessage &m); + void OnIncomingError(const SessionMessage &m); + void OnSignalingReady(); + + PortAllocator *port_allocator() const; + Thread *worker_thread() const; + Thread *signaling_thread() const; + int session_timeout(); + void set_session_timeout(int timeout); + + sigslot::signal2 SignalSessionCreate; + sigslot::signal1 SignalSessionDestroy; + + // Note: you can connect this directly to OnSignalingReady(), if a signalling + // check is not required. + sigslot::signal0<> SignalRequestSignaling; + +private: + Session *CreateSession(const std::string &name, const SessionID& id, bool received_initiate); + void OnRequestSignaling(); + + int timeout_; + Thread *worker_thread_; + Thread *signaling_thread_; + PortAllocator *allocator_; + std::map session_map_; +}; + +} // namespace cricket + +#endif // _SESSIONMANAGER_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionmessage.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionmessage.h new file mode 100644 index 00000000..fc1b0323 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/sessionmessage.h @@ -0,0 +1,133 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _SESSIONMESSAGE_H_ +#define _SESSIONMESSAGE_H_ + +#include "talk/p2p/base/candidate.h" +#include "talk/p2p/base/sessiondescription.h" +#include "talk/p2p/base/sessionid.h" +#include "talk/base/basictypes.h" +#include +#include +#include + +namespace cricket { + +class SessionMessage { +public: + enum Type { + TYPE_INITIATE = 0, // Initiate message + TYPE_ACCEPT, // Accept message + TYPE_MODIFY, // Modify message + TYPE_CANDIDATES, // Candidates message + TYPE_REJECT, // Reject message + TYPE_REDIRECT, // Reject message + TYPE_TERMINATE, // Terminate message + }; + + class Cookie { + public: + virtual ~Cookie() {} + + // Returns a copy of this cookie. + virtual Cookie* Copy() = 0; + }; + + Type type() const { + return type_; + } + void set_type(Type type) { + type_ = type; + } + const SessionID& session_id() const { + return id_; + } + SessionID& session_id() { + return id_; + } + void set_session_id(const SessionID& id) { + id_ = id; + } + const std::string &from() const { + return from_; + } + void set_from(const std::string &from) { + from_ = from; + } + const std::string &to() const { + return to_; + } + void set_to(const std::string &to) { + to_ = to; + } + const std::string &name() const { + return name_; + } + void set_name(const std::string &name) { + name_ = name; + } + const std::string &redirect_target() const { + return redirect_target_; + } + void set_redirect_target(const std::string &redirect_target) { + redirect_target_ = redirect_target; + } + Cookie *redirect_cookie() const { + return redirect_cookie_; + } + void set_redirect_cookie(Cookie* redirect_cookie) { + redirect_cookie_ = redirect_cookie; + } + const SessionDescription *description() const { + return description_; + } + void set_description(const SessionDescription *description) { + description_ = description; + } + const std::vector &candidates() const { + return candidates_; + } + void set_candidates(const std::vector &candidates) { + candidates_ = candidates; + } + +private: + Type type_; + SessionID id_; + std::string from_; + std::string to_; + std::string name_; + const SessionDescription *description_; + std::vector candidates_; + std::string redirect_target_; + Cookie* redirect_cookie_; +}; + +} // namespace cricket + +#endif // _SESSIONMESSAGE_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/socketmanager.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/socketmanager.cc new file mode 100644 index 00000000..2f0d67b8 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/socketmanager.cc @@ -0,0 +1,273 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "socketmanager.h" +#include + +namespace cricket { + +const uint32 MSG_CREATESOCKET = 1; +const uint32 MSG_DESTROYSOCKET = 2; +const uint32 MSG_ONSIGNALINGREADY = 3; +const uint32 MSG_CANDIDATESREADY = 4; +const uint32 MSG_ADDREMOTECANDIDATES = 5; +const uint32 MSG_ONREQUESTSIGNALING = 6; +const uint32 MSG_RESETSOCKETS = 7; + +struct CreateParams { + CreateParams() {} + P2PSocket *socket; + std::string name; +}; + +SocketManager::SocketManager(SessionManager *session_manager) { + session_manager_ = session_manager; + candidates_requested_ = false; + writable_ = false; +} + +SocketManager::~SocketManager() { + assert(Thread::Current() == session_manager_->signaling_thread()); + + // Are the sockets destroyed? If not, destroy them + + critSM_.Enter(); + while (sockets_.size() != 0) { + P2PSocket *socket = sockets_[0]; + critSM_.Leave(); + DestroySocket(socket); + critSM_.Enter(); + } + critSM_.Leave(); + + // Clear queues + + session_manager_->signaling_thread()->Clear(this); + session_manager_->worker_thread()->Clear(this); +} + +P2PSocket *SocketManager::CreateSocket(const std::string &name) { + // Can occur on any thread + CreateParams params; + params.name = name; + params.socket = NULL; + TypedMessageData data(¶ms); + session_manager_->worker_thread()->Send(this, MSG_CREATESOCKET, &data); + return data.data()->socket; +} + +P2PSocket *SocketManager::CreateSocket_w(const std::string &name) { + // Only on worker thread + assert(Thread::Current() == session_manager_->worker_thread()); + CritScope cs(&critSM_); + P2PSocket *socket = new P2PSocket(name, session_manager_->port_allocator()); + socket->SignalCandidatesReady.connect(this, &SocketManager::OnCandidatesReady); + socket->SignalState.connect(this, &SocketManager::OnSocketState); + socket->SignalRequestSignaling.connect(this, &SocketManager::OnRequestSignaling); + sockets_.push_back(socket); + socket->StartProcessingCandidates(); + return socket; +} + +void SocketManager::DestroySocket(P2PSocket *socket) { + // Can occur on any thread + TypedMessageData data(socket); + session_manager_->worker_thread()->Send(this, MSG_DESTROYSOCKET, &data); +} + +void SocketManager::DestroySocket_w(P2PSocket *socket) { + // Only on worker thread + assert(Thread::Current() == session_manager_->worker_thread()); + + // Only if socket exists + CritScope cs(&critSM_); + std::vector::iterator it; + it = std::find(sockets_.begin(), sockets_.end(), socket); + if (it == sockets_.end()) + return; + sockets_.erase(it); + delete socket; +} + +void SocketManager::StartProcessingCandidates() { + // Only on signaling thread + assert(Thread::Current() == session_manager_->signaling_thread()); + + // When sockets are created, their candidates are requested. + // When the candidates are ready, the client is signaled + // on the signaling thread + candidates_requested_ = true; + session_manager_->signaling_thread()->Post(this, MSG_CANDIDATESREADY); +} + +void SocketManager::OnSignalingReady() { + session_manager_->worker_thread()->Post(this, MSG_ONSIGNALINGREADY); +} + +void SocketManager::OnSignalingReady_w() { + // Only on worker thread + assert(Thread::Current() == session_manager_->worker_thread()); + for (uint32 i = 0; i < sockets_.size(); ++i) { + sockets_[i]->OnSignalingReady(); + } +} + +void SocketManager::OnCandidatesReady( + P2PSocket *socket, const std::vector& candidates) { + // Only on worker thread + assert(Thread::Current() == session_manager_->worker_thread()); + + // Remember candidates + CritScope cs(&critSM_); + std::vector::const_iterator it; + for (it = candidates.begin(); it != candidates.end(); it++) + candidates_.push_back(*it); + + // If candidates requested, tell signaling thread + if (candidates_requested_) + session_manager_->signaling_thread()->Post(this, MSG_CANDIDATESREADY); +} + +void SocketManager::ResetSockets() { + assert(Thread::Current() == session_manager_->signaling_thread()); + session_manager_->worker_thread()->Post(this, MSG_RESETSOCKETS); +} + +void SocketManager::ResetSockets_w() { + assert(Thread::Current() == session_manager_->worker_thread()); + + for (size_t i = 0; i < sockets_.size(); ++i) + sockets_[i]->Reset(); +} + +void SocketManager::OnSocketState(P2PSocket* socket, P2PSocket::State state) { + assert(Thread::Current() == session_manager_->worker_thread()); + + bool writable = false; + for (uint32 i = 0; i < sockets_.size(); ++i) + if (sockets_[i]->writable()) + writable = true; + + if (writable_ != writable) { + writable_ = writable; + SignalState(); + } +} + +void SocketManager::OnRequestSignaling() { + assert(Thread::Current() == session_manager_->worker_thread()); + session_manager_->signaling_thread()->Post(this, MSG_ONREQUESTSIGNALING); +} + + +void SocketManager::AddRemoteCandidates(const std::vector &remote_candidates) { + assert(Thread::Current() == session_manager_->signaling_thread()); + TypedMessageData > *data = new TypedMessageData >(remote_candidates); + session_manager_->worker_thread()->Post(this, MSG_ADDREMOTECANDIDATES, data); +} + +void SocketManager::AddRemoteCandidates_w(const std::vector &remote_candidates) { + assert(Thread::Current() == session_manager_->worker_thread()); + + // Local and remote candidates now exist, so connectivity checking can + // commence. Tell the P2PSockets about the remote candidates. + // Group candidates by socket name + + CritScope cs(&critSM_); + std::vector::iterator it_socket; + for (it_socket = sockets_.begin(); it_socket != sockets_.end(); it_socket++) { + // Create a vector of remote candidates for each socket + std::string name = (*it_socket)->name(); + std::vector candidate_bundle; + std::vector::const_iterator it_candidate; + for (it_candidate = remote_candidates.begin(); it_candidate != remote_candidates.end(); it_candidate++) { + if ((*it_candidate).name() == name) + candidate_bundle.push_back(*it_candidate); + } + if (candidate_bundle.size() != 0) + (*it_socket)->AddRemoteCandidates(candidate_bundle); + } +} + +void SocketManager::OnMessage(Message *message) { + switch (message->message_id) { + case MSG_CREATESOCKET: + { + assert(Thread::Current() == session_manager_->worker_thread()); + TypedMessageData *params = static_cast *>(message->pdata); + params->data()->socket = CreateSocket_w(params->data()->name); + } + break; + + case MSG_DESTROYSOCKET: + { + assert(Thread::Current() == session_manager_->worker_thread()); + TypedMessageData *data = static_cast *>(message->pdata); + DestroySocket_w(data->data()); + } + break; + + case MSG_ONSIGNALINGREADY: + assert(Thread::Current() == session_manager_->worker_thread()); + OnSignalingReady_w(); + break; + + case MSG_ONREQUESTSIGNALING: + assert(Thread::Current() == session_manager_->signaling_thread()); + SignalRequestSignaling(); + break; + + case MSG_CANDIDATESREADY: + assert(Thread::Current() == session_manager_->signaling_thread()); + if (candidates_requested_) { + CritScope cs(&critSM_); + if (candidates_.size() > 0) { + SignalCandidatesReady(candidates_); + candidates_.clear(); + } + } + break; + + case MSG_ADDREMOTECANDIDATES: + { + assert(Thread::Current() == session_manager_->worker_thread()); + TypedMessageData > *data = static_cast > *>(message->pdata); + AddRemoteCandidates_w(data->data()); + delete data; + } + break; + + case MSG_RESETSOCKETS: + ResetSockets_w(); + break; + } +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/socketmanager.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/socketmanager.h new file mode 100644 index 00000000..3ca1cf74 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/socketmanager.h @@ -0,0 +1,101 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _SOCKETMANAGER_H_ +#define _SOCKETMANAGER_H_ + +#include "talk/base/criticalsection.h" +#include "talk/base/messagequeue.h" +#include "talk/base/sigslot.h" +#include "talk/p2p/base/sessionmanager.h" +#include "talk/p2p/base/candidate.h" +#include "talk/p2p/base/p2psocket.h" +#include "talk/p2p/base/socketmanager.h" + +#include +#include + +namespace cricket { + +class SessionManager; + +// Manages P2PSocket creation/destruction/readiness. +// Provides thread separation between session and sockets. +// This allows session to execute on the signaling thread, +// and sockets to execute on the worker thread, if desired, +// which is good for some media types (audio/video for example). + +class SocketManager : public MessageHandler, public sigslot::has_slots<> { +public: + SocketManager(SessionManager *session_manager); + virtual ~SocketManager(); + + // Determines whether any of the created sockets are currently writable. + bool writable() { return writable_; } + + P2PSocket *CreateSocket(const std::string & name); + void DestroySocket(P2PSocket *socket); + + // Start discovering local candidates + void StartProcessingCandidates(); + + // Adds the given candidates that were sent by the remote side. + void AddRemoteCandidates(const std::vector& candidates); + + // signaling channel is up, ready to transmit candidates as they are discovered + void OnSignalingReady(); + + // Put all of the sockets back into the initial state. + void ResetSockets(); + + sigslot::signal1&> SignalCandidatesReady; + sigslot::signal0<> SignalNetworkError; + sigslot::signal0<> SignalState; + sigslot::signal0<> SignalRequestSignaling; + +private: + P2PSocket *CreateSocket_w(const std::string &name); + void DestroySocket_w(P2PSocket *socket); + void OnSignalingReady_w(); + void AddRemoteCandidates_w(const std::vector &candidates); + virtual void OnMessage(Message *message); + void OnCandidatesReady(P2PSocket *socket, const std::vector&); + void OnSocketState(P2PSocket* socket, P2PSocket::State state); + void OnRequestSignaling(void); + void ResetSockets_w(); + + SessionManager *session_manager_; + std::vector candidates_; + CriticalSection critSM_; + std::vector sockets_; + bool candidates_requested_; + bool writable_; +}; + +} + +#endif // _SOCKETMANAGER_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stun.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stun.cc new file mode 100644 index 00000000..6a22b238 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stun.cc @@ -0,0 +1,576 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/base/logging.h" +#include "talk/p2p/base/stun.h" +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::memcpy; +} +#endif + +namespace cricket { + +const std::string STUN_ERROR_REASON_BAD_REQUEST = "BAD REQUEST"; +const std::string STUN_ERROR_REASON_UNAUTHORIZED = "UNAUTHORIZED"; +const std::string STUN_ERROR_REASON_UNKNOWN_ATTRIBUTE = "UNKNOWN ATTRIBUTE"; +const std::string STUN_ERROR_REASON_STALE_CREDENTIALS = "STALE CREDENTIALS"; +const std::string STUN_ERROR_REASON_INTEGRITY_CHECK_FAILURE = "INTEGRITY CHECK FAILURE"; +const std::string STUN_ERROR_REASON_MISSING_USERNAME = "MISSING USERNAME"; +const std::string STUN_ERROR_REASON_USE_TLS = "USE TLS"; +const std::string STUN_ERROR_REASON_SERVER_ERROR = "SERVER ERROR"; +const std::string STUN_ERROR_REASON_GLOBAL_FAILURE = "GLOBAL FAILURE"; + +StunMessage::StunMessage() : type_(0), length_(0), + transaction_id_("0000000000000000") { + assert(transaction_id_.size() == 16); + attrs_ = new std::vector(); +} + +StunMessage::~StunMessage() { + for (unsigned i = 0; i < attrs_->size(); i++) + delete (*attrs_)[i]; + delete attrs_; +} + +void StunMessage::SetTransactionID(const std::string& str) { + assert(str.size() == 16); + transaction_id_ = str; +} + +void StunMessage::AddAttribute(StunAttribute* attr) { + attrs_->push_back(attr); + length_ += attr->length() + 4; +} + +const StunAddressAttribute* +StunMessage::GetAddress(StunAttributeType type) const { + switch (type) { + case STUN_ATTR_MAPPED_ADDRESS: + case STUN_ATTR_RESPONSE_ADDRESS: + case STUN_ATTR_SOURCE_ADDRESS: + case STUN_ATTR_CHANGED_ADDRESS: + case STUN_ATTR_REFLECTED_FROM: + case STUN_ATTR_ALTERNATE_SERVER: + case STUN_ATTR_DESTINATION_ADDRESS: + case STUN_ATTR_SOURCE_ADDRESS2: + return reinterpret_cast(GetAttribute(type)); + + default: + assert(0); + return 0; + } +} + +const StunUInt32Attribute* +StunMessage::GetUInt32(StunAttributeType type) const { + switch (type) { + case STUN_ATTR_CHANGE_REQUEST: + case STUN_ATTR_LIFETIME: + case STUN_ATTR_BANDWIDTH: + case STUN_ATTR_OPTIONS: + return reinterpret_cast(GetAttribute(type)); + + default: + assert(0); + return 0; + } +} + +const StunByteStringAttribute* +StunMessage::GetByteString(StunAttributeType type) const { + switch (type) { + case STUN_ATTR_USERNAME: + case STUN_ATTR_PASSWORD: + case STUN_ATTR_MESSAGE_INTEGRITY: + case STUN_ATTR_DATA: + case STUN_ATTR_MAGIC_COOKIE: + return reinterpret_cast(GetAttribute(type)); + + default: + assert(0); + return 0; + } +} + +const StunErrorCodeAttribute* StunMessage::GetErrorCode() const { + return reinterpret_cast( + GetAttribute(STUN_ATTR_ERROR_CODE)); +} + +const StunUInt16ListAttribute* StunMessage::GetUnknownAttributes() const { + return reinterpret_cast( + GetAttribute(STUN_ATTR_UNKNOWN_ATTRIBUTES)); +} + +const StunTransportPrefsAttribute* StunMessage::GetTransportPrefs() const { + return reinterpret_cast( + GetAttribute(STUN_ATTR_TRANSPORT_PREFERENCES)); +} + +const StunAttribute* StunMessage::GetAttribute(StunAttributeType type) const { + for (unsigned i = 0; i < attrs_->size(); i++) { + if ((*attrs_)[i]->type() == type) + return (*attrs_)[i]; + } + return 0; +} + +bool StunMessage::Read(ByteBuffer* buf) { + if (!buf->ReadUInt16(type_)) + return false; + + if (!buf->ReadUInt16(length_)) + return false; + + std::string transaction_id; + if (!buf->ReadString(transaction_id, 16)) + return false; + assert(transaction_id.size() == 16); + transaction_id_ = transaction_id; + + if (length_ > buf->Length()) + return false; + + attrs_->resize(0); + + size_t rest = buf->Length() - length_; + while (buf->Length() > rest) { + uint16 attr_type, attr_length; + if (!buf->ReadUInt16(attr_type)) + return false; + if (!buf->ReadUInt16(attr_length)) + return false; + + StunAttribute* attr = StunAttribute::Create(attr_type, attr_length); + if (!attr || !attr->Read(buf)) + return false; + + attrs_->push_back(attr); + } + + if (buf->Length() != rest) { + // fixme: shouldn't be doing this + LOG(LERROR) << "wrong message length" + << " (" << (int)rest << " != " << (int)buf->Length() << ")"; + return false; + } + + return true; +} + +void StunMessage::Write(ByteBuffer* buf) const { + buf->WriteUInt16(type_); + buf->WriteUInt16(length_); + buf->WriteString(transaction_id_); + + for (unsigned i = 0; i < attrs_->size(); i++) { + buf->WriteUInt16((*attrs_)[i]->type()); + buf->WriteUInt16((*attrs_)[i]->length()); + (*attrs_)[i]->Write(buf); + } +} + +StunAttribute::StunAttribute(uint16 type, uint16 length) + : type_(type), length_(length) { +} + +StunAttribute* StunAttribute::Create(uint16 type, uint16 length) { + switch (type) { + case STUN_ATTR_MAPPED_ADDRESS: + case STUN_ATTR_RESPONSE_ADDRESS: + case STUN_ATTR_SOURCE_ADDRESS: + case STUN_ATTR_CHANGED_ADDRESS: + case STUN_ATTR_REFLECTED_FROM: + case STUN_ATTR_ALTERNATE_SERVER: + case STUN_ATTR_DESTINATION_ADDRESS: + case STUN_ATTR_SOURCE_ADDRESS2: + if (length != StunAddressAttribute::SIZE) + return 0; + return new StunAddressAttribute(type); + + case STUN_ATTR_CHANGE_REQUEST: + case STUN_ATTR_LIFETIME: + case STUN_ATTR_BANDWIDTH: + case STUN_ATTR_OPTIONS: + if (length != StunUInt32Attribute::SIZE) + return 0; + return new StunUInt32Attribute(type); + + case STUN_ATTR_USERNAME: + case STUN_ATTR_PASSWORD: + case STUN_ATTR_MAGIC_COOKIE: + return (length % 4 == 0) ? new StunByteStringAttribute(type, length) : 0; + + case STUN_ATTR_MESSAGE_INTEGRITY: + return (length == 20) ? new StunByteStringAttribute(type, length) : 0; + + case STUN_ATTR_DATA: + return new StunByteStringAttribute(type, length); + + case STUN_ATTR_ERROR_CODE: + if (length < StunErrorCodeAttribute::MIN_SIZE) + return 0; + return new StunErrorCodeAttribute(type, length); + + case STUN_ATTR_UNKNOWN_ATTRIBUTES: + return (length % 2 == 0) ? new StunUInt16ListAttribute(type, length) : 0; + + case STUN_ATTR_TRANSPORT_PREFERENCES: + if ((length != StunTransportPrefsAttribute::SIZE1) && + (length != StunTransportPrefsAttribute::SIZE2)) + return 0; + return new StunTransportPrefsAttribute(type, length); + + default: + return 0; + } +} + +StunAddressAttribute* StunAttribute::CreateAddress(uint16 type) { + switch (type) { + case STUN_ATTR_MAPPED_ADDRESS: + case STUN_ATTR_RESPONSE_ADDRESS: + case STUN_ATTR_SOURCE_ADDRESS: + case STUN_ATTR_CHANGED_ADDRESS: + case STUN_ATTR_REFLECTED_FROM: + case STUN_ATTR_ALTERNATE_SERVER: + case STUN_ATTR_DESTINATION_ADDRESS: + case STUN_ATTR_SOURCE_ADDRESS2: + return new StunAddressAttribute(type); + + default: + assert(false); + return 0; + } +} + +StunUInt32Attribute* StunAttribute::CreateUInt32(uint16 type) { + switch (type) { + case STUN_ATTR_CHANGE_REQUEST: + case STUN_ATTR_LIFETIME: + case STUN_ATTR_BANDWIDTH: + case STUN_ATTR_OPTIONS: + return new StunUInt32Attribute(type); + + default: + assert(false); + return 0; + } +} + +StunByteStringAttribute* StunAttribute::CreateByteString(uint16 type) { + switch (type) { + case STUN_ATTR_USERNAME: + case STUN_ATTR_PASSWORD: + case STUN_ATTR_MESSAGE_INTEGRITY: + case STUN_ATTR_DATA: + case STUN_ATTR_MAGIC_COOKIE: + return new StunByteStringAttribute(type, 0); + + default: + assert(false); + return 0; + } +} + +StunErrorCodeAttribute* StunAttribute::CreateErrorCode() { + return new StunErrorCodeAttribute( + STUN_ATTR_ERROR_CODE, StunErrorCodeAttribute::MIN_SIZE); +} + +StunUInt16ListAttribute* StunAttribute::CreateUnknownAttributes() { + return new StunUInt16ListAttribute(STUN_ATTR_UNKNOWN_ATTRIBUTES, 0); +} + +StunTransportPrefsAttribute* StunAttribute::CreateTransportPrefs() { + return new StunTransportPrefsAttribute( + STUN_ATTR_TRANSPORT_PREFERENCES, StunTransportPrefsAttribute::SIZE1); +} + +StunAddressAttribute::StunAddressAttribute(uint16 type) + : StunAttribute(type, SIZE), family_(0), port_(0), ip_(0) { +} + +bool StunAddressAttribute::Read(ByteBuffer* buf) { + uint8 dummy; + if (!buf->ReadUInt8(dummy)) + return false; + if (!buf->ReadUInt8(family_)) + return false; + if (!buf->ReadUInt16(port_)) + return false; + if (!buf->ReadUInt32(ip_)) + return false; + return true; +} + +void StunAddressAttribute::Write(ByteBuffer* buf) const { + buf->WriteUInt8(0); + buf->WriteUInt8(family_); + buf->WriteUInt16(port_); + buf->WriteUInt32(ip_); +} + +StunUInt32Attribute::StunUInt32Attribute(uint16 type) + : StunAttribute(type, SIZE), bits_(0) { +} + +bool StunUInt32Attribute::GetBit(int index) const { + assert((0 <= index) && (index < 32)); + return static_cast((bits_ >> index) & 0x1); +} + +void StunUInt32Attribute::SetBit(int index, bool value) { + assert((0 <= index) && (index < 32)); + bits_ &= ~(1 << index); + bits_ |= value ? (1 << index) : 0; +} + +bool StunUInt32Attribute::Read(ByteBuffer* buf) { + if (!buf->ReadUInt32(bits_)) + return false; + return true; +} + +void StunUInt32Attribute::Write(ByteBuffer* buf) const { + buf->WriteUInt32(bits_); +} + +StunByteStringAttribute::StunByteStringAttribute(uint16 type, uint16 length) + : StunAttribute(type, length), bytes_(0) { +} + +StunByteStringAttribute::~StunByteStringAttribute() { + delete [] bytes_; +} + +void StunByteStringAttribute::SetBytes(char* bytes, uint16 length) { + delete [] bytes_; + bytes_ = bytes; + SetLength(length); +} + +void StunByteStringAttribute::CopyBytes(const char* bytes) { + CopyBytes(bytes, (uint16)strlen(bytes)); +} + +void StunByteStringAttribute::CopyBytes(const void* bytes, uint16 length) { + char* new_bytes = new char[length]; + std::memcpy(new_bytes, bytes, length); + SetBytes(new_bytes, length); +} + +uint8 StunByteStringAttribute::GetByte(int index) const { + assert(bytes_); + assert((0 <= index) && (index < length())); + return static_cast(bytes_[index]); +} + +void StunByteStringAttribute::SetByte(int index, uint8 value) { + assert(bytes_); + assert((0 <= index) && (index < length())); + bytes_[index] = value; +} + +bool StunByteStringAttribute::Read(ByteBuffer* buf) { + bytes_ = new char[length()]; + if (!buf->ReadBytes(bytes_, length())) + return false; + return true; +} + +void StunByteStringAttribute::Write(ByteBuffer* buf) const { + buf->WriteBytes(bytes_, length()); +} + +StunErrorCodeAttribute::StunErrorCodeAttribute(uint16 type, uint16 length) + : StunAttribute(type, length), class_(0), number_(0) { +} + +StunErrorCodeAttribute::~StunErrorCodeAttribute() { +} + +void StunErrorCodeAttribute::SetErrorCode(uint32 code) { + class_ = (uint8)((code >> 8) & 0x7); + number_ = (uint8)(code & 0xff); +} + +void StunErrorCodeAttribute::SetReason(const std::string& reason) { + SetLength(MIN_SIZE + (uint16)reason.size()); + reason_ = reason; +} + +bool StunErrorCodeAttribute::Read(ByteBuffer* buf) { + uint32 val; + if (!buf->ReadUInt32(val)) + return false; + + if ((val >> 11) != 0) + LOG(LERROR) << "error-code bits not zero"; + + SetErrorCode(val); + + if (!buf->ReadString(reason_, length() - 4)) + return false; + + return true; +} + +void StunErrorCodeAttribute::Write(ByteBuffer* buf) const { + buf->WriteUInt32(error_code()); + buf->WriteString(reason_); +} + +StunUInt16ListAttribute::StunUInt16ListAttribute(uint16 type, uint16 length) + : StunAttribute(type, length) { + attr_types_ = new std::vector(); +} + +StunUInt16ListAttribute::~StunUInt16ListAttribute() { + delete attr_types_; +} + +size_t StunUInt16ListAttribute::Size() const { + return attr_types_->size(); +} + +uint16 StunUInt16ListAttribute::GetType(int index) const { + return (*attr_types_)[index]; +} + +void StunUInt16ListAttribute::SetType(int index, uint16 value) { + (*attr_types_)[index] = value; +} + +void StunUInt16ListAttribute::AddType(uint16 value) { + attr_types_->push_back(value); + SetLength((uint16)attr_types_->size() * 2); +} + +bool StunUInt16ListAttribute::Read(ByteBuffer* buf) { + for (int i = 0; i < length() / 2; i++) { + uint16 attr; + if (!buf->ReadUInt16(attr)) + return false; + attr_types_->push_back(attr); + } + return true; +} + +void StunUInt16ListAttribute::Write(ByteBuffer* buf) const { + for (unsigned i = 0; i < attr_types_->size(); i++) + buf->WriteUInt16((*attr_types_)[i]); +} + +StunTransportPrefsAttribute::StunTransportPrefsAttribute( + uint16 type, uint16 length) + : StunAttribute(type, length), preallocate_(false), prefs_(0), addr_(0) { +} + +StunTransportPrefsAttribute::~StunTransportPrefsAttribute() { + delete addr_; +} + +void StunTransportPrefsAttribute::SetPreallocateAddress( + StunAddressAttribute* addr) { + if (!addr) { + preallocate_ = false; + addr_ = 0; + SetLength(SIZE1); + } else { + preallocate_ = true; + addr_ = addr; + SetLength(SIZE2); + } +} + +bool StunTransportPrefsAttribute::Read(ByteBuffer* buf) { + uint32 val; + if (!buf->ReadUInt32(val)) + return false; + + if ((val >> 3) != 0) + LOG(LERROR) << "transport-preferences bits not zero"; + + preallocate_ = static_cast((val >> 2) & 0x1); + prefs_ = (uint8)(val & 0x3); + + if (preallocate_ && (prefs_ == 3)) + LOG(LERROR) << "transport-preferences imcompatible P and Typ"; + + if (!preallocate_) { + if (length() != StunUInt32Attribute::SIZE) + return false; + } else { + if (length() != StunUInt32Attribute::SIZE + StunAddressAttribute::SIZE) + return false; + + addr_ = new StunAddressAttribute(STUN_ATTR_SOURCE_ADDRESS); + addr_->Read(buf); + } + + return true; +} + +void StunTransportPrefsAttribute::Write(ByteBuffer* buf) const { + buf->WriteUInt32((preallocate_ ? 4 : 0) | prefs_); + + if (preallocate_) + addr_->Write(buf); +} + +StunMessageType GetStunResponseType(StunMessageType request_type) { + switch (request_type) { + case STUN_SHARED_SECRET_REQUEST: + return STUN_SHARED_SECRET_RESPONSE; + case STUN_ALLOCATE_REQUEST: + return STUN_ALLOCATE_RESPONSE; + case STUN_SEND_REQUEST: + return STUN_SEND_RESPONSE; + default: + return STUN_BINDING_RESPONSE; + } +} + +StunMessageType GetStunErrorResponseType(StunMessageType request_type) { + switch (request_type) { + case STUN_SHARED_SECRET_REQUEST: + return STUN_SHARED_SECRET_ERROR_RESPONSE; + case STUN_ALLOCATE_REQUEST: + return STUN_ALLOCATE_ERROR_RESPONSE; + case STUN_SEND_REQUEST: + return STUN_SEND_ERROR_RESPONSE; + default: + return STUN_BINDING_ERROR_RESPONSE; + } +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stun.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stun.h new file mode 100644 index 00000000..27a8e4be --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stun.h @@ -0,0 +1,364 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __STUN_H__ +#define __STUN_H__ + +// This file contains classes for dealing with the STUN and TURN protocols. +// Both protocols use the same wire format. + +#include "talk/base/basictypes.h" +#include "talk/base/bytebuffer.h" +#include +#include + +namespace cricket { + +// These are the types of STUN & TURN messages as of last check. +enum StunMessageType { + STUN_BINDING_REQUEST = 0x0001, + STUN_BINDING_RESPONSE = 0x0101, + STUN_BINDING_ERROR_RESPONSE = 0x0111, + STUN_SHARED_SECRET_REQUEST = 0x0002, + STUN_SHARED_SECRET_RESPONSE = 0x0102, + STUN_SHARED_SECRET_ERROR_RESPONSE = 0x0112, + STUN_ALLOCATE_REQUEST = 0x0003, + STUN_ALLOCATE_RESPONSE = 0x0103, + STUN_ALLOCATE_ERROR_RESPONSE = 0x0113, + STUN_SEND_REQUEST = 0x0004, + STUN_SEND_RESPONSE = 0x0104, + STUN_SEND_ERROR_RESPONSE = 0x0114, + STUN_DATA_INDICATION = 0x0115 +}; + +// These are the types of attributes defined in STUN & TURN. Next to each is +// the name of the class (T is StunTAttribute) that implements that type. +enum StunAttributeType { + STUN_ATTR_MAPPED_ADDRESS = 0x0001, // Address + STUN_ATTR_RESPONSE_ADDRESS = 0x0002, // Address + STUN_ATTR_CHANGE_REQUEST = 0x0003, // UInt32 + STUN_ATTR_SOURCE_ADDRESS = 0x0004, // Address + STUN_ATTR_CHANGED_ADDRESS = 0x0005, // Address + STUN_ATTR_USERNAME = 0x0006, // ByteString, multiple of 4 bytes + STUN_ATTR_PASSWORD = 0x0007, // ByteString, multiple of 4 bytes + STUN_ATTR_MESSAGE_INTEGRITY = 0x0008, // ByteString, 20 bytes + STUN_ATTR_ERROR_CODE = 0x0009, // ErrorCode + STUN_ATTR_UNKNOWN_ATTRIBUTES = 0x000a, // UInt16List + STUN_ATTR_REFLECTED_FROM = 0x000b, // Address + STUN_ATTR_TRANSPORT_PREFERENCES = 0x000c, // TransportPrefs + STUN_ATTR_LIFETIME = 0x000d, // UInt32 + STUN_ATTR_ALTERNATE_SERVER = 0x000e, // Address + STUN_ATTR_MAGIC_COOKIE = 0x000f, // ByteString, 4 bytes + STUN_ATTR_BANDWIDTH = 0x0010, // UInt32 + STUN_ATTR_DESTINATION_ADDRESS = 0x0011, // Address + STUN_ATTR_SOURCE_ADDRESS2 = 0x0012, // Address + STUN_ATTR_DATA = 0x0013, // ByteString + STUN_ATTR_OPTIONS = 0x8001 // UInt32 +}; + +enum StunErrorCodes { + STUN_ERROR_BAD_REQUEST = 400, + STUN_ERROR_UNAUTHORIZED = 401, + STUN_ERROR_UNKNOWN_ATTRIBUTE = 420, + STUN_ERROR_STALE_CREDENTIALS = 430, + STUN_ERROR_INTEGRITY_CHECK_FAILURE = 431, + STUN_ERROR_MISSING_USERNAME = 432, + STUN_ERROR_USE_TLS = 433, + STUN_ERROR_SERVER_ERROR = 500, + STUN_ERROR_GLOBAL_FAILURE = 600 +}; + +extern const std::string STUN_ERROR_REASON_BAD_REQUEST; +extern const std::string STUN_ERROR_REASON_UNAUTHORIZED; +extern const std::string STUN_ERROR_REASON_UNKNOWN_ATTRIBUTE; +extern const std::string STUN_ERROR_REASON_STALE_CREDENTIALS; +extern const std::string STUN_ERROR_REASON_INTEGRITY_CHECK_FAILURE; +extern const std::string STUN_ERROR_REASON_MISSING_USERNAME; +extern const std::string STUN_ERROR_REASON_USE_TLS; +extern const std::string STUN_ERROR_REASON_SERVER_ERROR; +extern const std::string STUN_ERROR_REASON_GLOBAL_FAILURE; + +class StunAttribute; +class StunAddressAttribute; +class StunUInt32Attribute; +class StunByteStringAttribute; +class StunErrorCodeAttribute; +class StunUInt16ListAttribute; +class StunTransportPrefsAttribute; + +// Records a complete STUN/TURN message. Each message consists of a type and +// any number of attributes. Each attribute is parsed into an instance of an +// appropriate class (see above). The Get* methods will return instances of +// that attribute class. +class StunMessage { +public: + StunMessage(); + ~StunMessage(); + + StunMessageType type() const { return static_cast(type_); } + uint16 length() const { return length_; } + const std::string& transaction_id() const { return transaction_id_; } + + void SetType(StunMessageType type) { type_ = type; } + void SetTransactionID(const std::string& str); + + const StunAddressAttribute* GetAddress(StunAttributeType type) const; + const StunUInt32Attribute* GetUInt32(StunAttributeType type) const; + const StunByteStringAttribute* GetByteString(StunAttributeType type) const; + const StunErrorCodeAttribute* GetErrorCode() const; + const StunUInt16ListAttribute* GetUnknownAttributes() const; + const StunTransportPrefsAttribute* GetTransportPrefs() const; + + void AddAttribute(StunAttribute* attr); + + // Parses the STUN/TURN packet in the given buffer and records it here. The + // return value indicates whether this was successful. + bool Read(ByteBuffer* buf); + + // Writes this object into a STUN/TURN packet. Return value is true if + // successful. + void Write(ByteBuffer* buf) const; + +private: + uint16 type_; + uint16 length_; + std::string transaction_id_; + std::vector* attrs_; + + const StunAttribute* GetAttribute(StunAttributeType type) const; +}; + +// Base class for all STUN/TURN attributes. +class StunAttribute { +public: + virtual ~StunAttribute() {} + + StunAttributeType type() const { + return static_cast(type_); + } + uint16 length() const { return length_; } + + // Reads the body (not the type or length) for this type of attribute from + // the given buffer. Return value is true if successful. + virtual bool Read(ByteBuffer* buf) = 0; + + // Writes the body (not the type or length) to the given buffer. Return + // value is true if successful. + virtual void Write(ByteBuffer* buf) const = 0; + + // Creates an attribute object with the given type and len. + static StunAttribute* Create(uint16 type, uint16 length); + + // Creates an attribute object with the given type and smallest length. + static StunAddressAttribute* CreateAddress(uint16 type); + static StunUInt32Attribute* CreateUInt32(uint16 type); + static StunByteStringAttribute* CreateByteString(uint16 type); + static StunErrorCodeAttribute* CreateErrorCode(); + static StunUInt16ListAttribute* CreateUnknownAttributes(); + static StunTransportPrefsAttribute* CreateTransportPrefs(); + +protected: + StunAttribute(uint16 type, uint16 length); + + void SetLength(uint16 length) { length_ = length; } + +private: + uint16 type_; + uint16 length_; +}; + +// Implements STUN/TURN attributes that record an Internet address. +class StunAddressAttribute : public StunAttribute { +public: + StunAddressAttribute(uint16 type); + +#if (_MSC_VER < 1300) + enum { SIZE = 8 }; +#else + static const uint16 SIZE = 8; +#endif + + uint8 family() const { return family_; } + uint16 port() const { return port_; } + uint32 ip() const { return ip_; } + + void SetFamily(uint8 family) { family_ = family; } + void SetIP(uint32 ip) { ip_ = ip; } + void SetPort(uint16 port) { port_ = port; } + + bool Read(ByteBuffer* buf); + void Write(ByteBuffer* buf) const; + +private: + uint8 family_; + uint16 port_; + uint32 ip_; +}; + +// Implements STUN/TURN attributs that record a 32-bit integer. +class StunUInt32Attribute : public StunAttribute { +public: + StunUInt32Attribute(uint16 type); + +#if (_MSC_VER < 1300) + enum { SIZE = 4 }; +#else + static const uint16 SIZE = 4; +#endif + + uint32 value() const { return bits_; } + + void SetValue(uint32 bits) { bits_ = bits; } + + bool GetBit(int index) const; + void SetBit(int index, bool value); + + bool Read(ByteBuffer* buf); + void Write(ByteBuffer* buf) const; + +private: + uint32 bits_; +}; + +// Implements STUN/TURN attributs that record an arbitrary byte string +class StunByteStringAttribute : public StunAttribute { +public: + StunByteStringAttribute(uint16 type, uint16 length); + ~StunByteStringAttribute(); + + const char* bytes() const { return bytes_; } + + void SetBytes(char* bytes, uint16 length); + + void CopyBytes(const char* bytes); // uses strlen + void CopyBytes(const void* bytes, uint16 length); + + uint8 GetByte(int index) const; + void SetByte(int index, uint8 value); + + bool Read(ByteBuffer* buf); + void Write(ByteBuffer* buf) const; + +private: + char* bytes_; +}; + +// Implements STUN/TURN attributs that record an error code. +class StunErrorCodeAttribute : public StunAttribute { +public: + StunErrorCodeAttribute(uint16 type, uint16 length); + ~StunErrorCodeAttribute(); + +#if (_MSC_VER < 1300) + enum { MIN_SIZE = 4 }; +#else + static const uint16 MIN_SIZE = 4; +#endif + + uint32 error_code() const { return (class_ << 8) | number_; } + uint8 error_class() const { return class_; } + uint8 number() const { return number_; } + const std::string& reason() const { return reason_; } + + void SetErrorCode(uint32 code); + void SetErrorClass(uint8 eclass) { class_ = eclass; } + void SetNumber(uint8 number) { number_ = number; } + void SetReason(const std::string& reason); + + bool Read(ByteBuffer* buf); + void Write(ByteBuffer* buf) const; + +private: + uint8 class_; + uint8 number_; + std::string reason_; +}; + +// Implements STUN/TURN attributs that record a list of attribute names. +class StunUInt16ListAttribute : public StunAttribute { +public: + StunUInt16ListAttribute(uint16 type, uint16 length); + ~StunUInt16ListAttribute(); + + size_t Size() const; + uint16 GetType(int index) const; + void SetType(int index, uint16 value); + void AddType(uint16 value); + + bool Read(ByteBuffer* buf); + void Write(ByteBuffer* buf) const; + +private: + std::vector* attr_types_; +}; + +// Implements the TURN TRANSPORT-PREFS attribute, which provides information +// about the ports to allocate. +class StunTransportPrefsAttribute : public StunAttribute { +public: + StunTransportPrefsAttribute(uint16 type, uint16 length); + ~StunTransportPrefsAttribute(); + +#if (_MSC_VER < 1300) + enum { SIZE1 = 4, SIZE2 = 12 }; +#else + static const uint16 SIZE1 = 4; + static const uint16 SIZE2 = 12; +#endif + + bool preallocate() const { return preallocate_; } + uint8 preference_type() const { return prefs_; } + const StunAddressAttribute* address() const { return addr_; } + + void SetPreferenceType(uint8 prefs) { prefs_ = prefs; } + + // Sets the preallocate address to the given value, or if 0 is given, it sets + // to not preallocate. + void SetPreallocateAddress(StunAddressAttribute* addr); + + bool Read(ByteBuffer* buf); + void Write(ByteBuffer* buf) const; + +private: + bool preallocate_; + uint8 prefs_; + StunAddressAttribute* addr_; +}; + +// The special MAGIC-COOKIE attribute is used to distinguish TURN packets from +// other kinds of traffic. +const char STUN_MAGIC_COOKIE_VALUE[] = { 0x72, char(0xc6), 0x4b, char(0xc6) }; + +// Returns the (successful) response type for the given request type. +StunMessageType GetStunResponseType(StunMessageType request_type); + +// Returns the error response type for the given request type. +StunMessageType GetStunErrorResponseType(StunMessageType request_type); + +} // namespace cricket + +#endif // __STUN_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunport.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunport.cc new file mode 100644 index 00000000..6d1dc6b1 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunport.cc @@ -0,0 +1,171 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/base/logging.h" +#include "talk/p2p/base/stunport.h" +#include "talk/p2p/base/helpers.h" +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::strerror; +} +#endif + +#ifdef POSIX +extern "C" { +#include +} +#endif // POSIX + +namespace cricket { + +const int KEEPALIVE_DELAY = 10 * 1000; // 10 seconds - sort timeouts +const int RETRY_DELAY = 50; // 50ms, from ICE spec +const uint32 RETRY_TIMEOUT = 50 * 1000; // ICE says 50 secs + +// Handles a binding request sent to the STUN server. +class StunPortBindingRequest : public StunRequest { +public: + StunPortBindingRequest(StunPort* port) : port_(port) { + start_time_ = GetMillisecondCount(); + } + + virtual ~StunPortBindingRequest() { + } + + virtual void Prepare(StunMessage* request) { + request->SetType(STUN_BINDING_REQUEST); + } + + virtual void OnResponse(StunMessage* response) { + const StunAddressAttribute* addr_attr = + response->GetAddress(STUN_ATTR_MAPPED_ADDRESS); + if (!addr_attr) { + LOG(LERROR) << "Binding response missing mapped address."; + } else if (addr_attr->family() != 1) { + LOG(LERROR) << "Binding address has bad family"; + } else { + SocketAddress addr(addr_attr->ip(), addr_attr->port()); + if (port_->candidates().empty()) + port_->add_address(addr, "udp"); + } + + // We will do a keep-alive regardless of whether this request suceeds. + // This should have almost no impact on network usage. + port_->requests_.SendDelayed(new StunPortBindingRequest(port_), KEEPALIVE_DELAY); + } + + virtual void OnErrorResponse(StunMessage* response) { + const StunErrorCodeAttribute* attr = response->GetErrorCode(); + if (!attr) { + LOG(LERROR) << "Bad allocate response error code"; + } else { + LOG(LERROR) << "Binding error response:" + << " class=" << attr->error_class() + << " number=" << attr->number() + << " reason='" << attr->reason() << "'"; + } + + if (GetMillisecondCount() - start_time_ <= RETRY_TIMEOUT) + port_->requests_.SendDelayed(new StunPortBindingRequest(port_), KEEPALIVE_DELAY); + } + + virtual void OnTimeout() { + LOG(LERROR) << "Binding request timed out"; + if (GetMillisecondCount() - start_time_ <= RETRY_TIMEOUT) + port_->requests_.SendDelayed(new StunPortBindingRequest(port_), RETRY_DELAY); + } + +private: + uint32 start_time_; + StunPort* port_; +}; + +const std::string STUN_PORT_TYPE("stun"); + +StunPort::StunPort(Thread* thread, SocketFactory* factory, Network* network, + const SocketAddress& local_addr, + const SocketAddress& server_addr) + : UDPPort(thread, STUN_PORT_TYPE, factory, network), + server_addr_(server_addr), requests_(thread), error_(0) { + + socket_ = CreatePacketSocket(PROTO_UDP); + socket_->SignalReadPacket.connect(this, &StunPort::OnReadPacket); + if (socket_->Bind(local_addr) < 0) + PLOG(LERROR, socket_->GetError()) << "bind"; + + requests_.SignalSendPacket.connect(this, &StunPort::OnSendPacket); +} + +StunPort::~StunPort() { + delete socket_; +} + +void StunPort::PrepareAddress() { + requests_.Send(new StunPortBindingRequest(this)); +} + +int StunPort::SendTo( + const void* data, size_t size, const SocketAddress& addr, bool payload) { + int sent = socket_->SendTo(data, size, addr); + if (sent < 0) + error_ = socket_->GetError(); + return sent; +} + +int StunPort::SetOption(Socket::Option opt, int value) { + return socket_->SetOption(opt, value); +} + +int StunPort::GetError() { + return error_; +} + +void StunPort::OnReadPacket( + const char* data, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket) { + assert(socket == socket_); + + // Look for a response to a binding request. + if (requests_.CheckResponse(data, size)) + return; + + // Process this data packet in the normal manner. + UDPPort::OnReadPacket(data, size, remote_addr); +} + +void StunPort::OnSendPacket(const void* data, size_t size) { + if (socket_->SendTo(data, size, server_addr_) < 0) + PLOG(LERROR, socket_->GetError()) << "sendto"; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunport.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunport.h new file mode 100644 index 00000000..f042ae14 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunport.h @@ -0,0 +1,72 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __STUNPORT_H__ +#define __STUNPORT_H__ + +#include "talk/base/asyncudpsocket.h" +#include "talk/p2p/base/udpport.h" +#include "talk/p2p/base/stunrequest.h" + +namespace cricket { + +extern const std::string STUN_PORT_TYPE; + +// Communicates using the address on the outside of a NAT. +class StunPort : public UDPPort { +public: + StunPort(Thread* thread, SocketFactory* factory, Network* network, + const SocketAddress& local_addr, const SocketAddress& server_addr); + virtual ~StunPort(); + + virtual void PrepareAddress(); + + virtual int SetOption(Socket::Option opt, int value); + virtual int GetError(); + +protected: + virtual int SendTo(const void* data, size_t size, const SocketAddress& addr, bool payload); + + void OnReadPacket( + const char* data, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket); + +private: + AsyncPacketSocket* socket_; + SocketAddress server_addr_; + StunRequestManager requests_; + int error_; + + friend class StunPortBindingRequest; + + // Sends STUN requests to the server. + void OnSendPacket(const void* data, size_t size); +}; + +} // namespace cricket + +#endif // __STUNPORT_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunrequest.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunrequest.cc new file mode 100644 index 00000000..14d64735 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunrequest.cc @@ -0,0 +1,198 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/base/logging.h" +#include "talk/p2p/base/stunrequest.h" +#include "talk/p2p/base/helpers.h" +#include +#include + +namespace cricket { + +const uint32 MSG_STUN_SEND = 1; + +const int MAX_SENDS = 9; +const int DELAY_UNIT = 100; // 100 milliseconds +const int DELAY_MAX_FACTOR = 16; + +StunRequestManager::StunRequestManager(Thread* thread) : thread_(thread) { +} + +StunRequestManager::~StunRequestManager() { + while (requests_.begin() != requests_.end()) { + StunRequest *request = requests_.begin()->second; + requests_.erase(requests_.begin()); + delete request; + } +} + +void StunRequestManager::Send(StunRequest* request) { + SendDelayed(request, 0); +} + +void StunRequestManager::SendDelayed(StunRequest* request, int delay) { + request->set_manager(this); + assert(requests_.find(request->id()) == requests_.end()); + requests_[request->id()] = request; + thread_->PostDelayed(delay, request, MSG_STUN_SEND, NULL); +} + +void StunRequestManager::Remove(StunRequest* request) { + assert(request->manager() == this); + RequestMap::iterator iter = requests_.find(request->id()); + if (iter != requests_.end()) { + assert(iter->second == request); + requests_.erase(iter); + thread_->Clear(request); + } +} + +void StunRequestManager::Clear() { + std::vector requests; + for (RequestMap::iterator i = requests_.begin(); i != requests_.end(); ++i) + requests.push_back(i->second); + + for (uint32 i = 0; i < requests.size(); ++i) + Remove(requests[i]); +} + +bool StunRequestManager::CheckResponse(StunMessage* msg) { + RequestMap::iterator iter = requests_.find(msg->transaction_id()); + if (iter == requests_.end()) + return false; + + StunRequest* request = iter->second; + if (msg->type() == GetStunResponseType(request->type())) { + request->OnResponse(msg); + } else if (msg->type() == GetStunErrorResponseType(request->type())) { + request->OnErrorResponse(msg); + } else { + LOG(LERROR) << "Received response with wrong type: " << msg->type() + << " (expecting " << GetStunResponseType(request->type()) << ")"; + return false; + } + + delete request; + return true; +} + +bool StunRequestManager::CheckResponse(const char* data, size_t size) { + // Check the appropriate bytes of the stream to see if they match the + // transaction ID of a response we are expecting. + + if (size < 20) + return false; + + std::string id; + id.append(data + 4, 16); + + RequestMap::iterator iter = requests_.find(id); + if (iter == requests_.end()) + return false; + + // Parse the STUN message and continue processing as usual. + + ByteBuffer buf(data, size); + StunMessage msg; + if (!msg.Read(&buf)) + return false; + + return CheckResponse(&msg); +} + +StunRequest::StunRequest() + : manager_(0), id_(CreateRandomString(16)), msg_(0), count_(0), + timeout_(false), tstamp_(0) { +} + +StunRequest::StunRequest(StunMessage* request) + : manager_(0), id_(request->transaction_id()), msg_(request), + count_(0), timeout_(false) { +} + +StunRequest::~StunRequest() { + assert(manager_ != NULL); + if (manager_) { + manager_->Remove(this); + manager_->thread_->Clear(this); + } + delete msg_; +} + +const StunMessageType StunRequest::type() { + assert(msg_); + return msg_->type(); +} + +void StunRequest::set_manager(StunRequestManager* manager) { + assert(!manager_); + manager_ = manager; +} + +void StunRequest::OnMessage(Message* pmsg) { + assert(manager_); + assert(pmsg->message_id == MSG_STUN_SEND); + + if (!msg_) { + msg_ = new StunMessage(); + msg_->SetTransactionID(id_); + Prepare(msg_); + assert(msg_->transaction_id() == id_); + } + + if (timeout_) { + OnTimeout(); + delete this; + return; + } + + tstamp_ = GetMillisecondCount(); + + ByteBuffer buf; + msg_->Write(&buf); + manager_->SignalSendPacket(buf.Data(), buf.Length()); + + int delay = GetNextDelay(); + manager_->thread_->PostDelayed(delay, this, MSG_STUN_SEND, NULL); +} + +uint32 StunRequest::Elapsed() const { + return (GetMillisecondCount() - tstamp_); +} + +int StunRequest::GetNextDelay() { + int delay = DELAY_UNIT * _min(1 << count_, DELAY_MAX_FACTOR); + count_ += 1; + if (count_ == MAX_SENDS) + timeout_ = true; + return delay; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunrequest.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunrequest.h new file mode 100644 index 00000000..86acff91 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunrequest.h @@ -0,0 +1,126 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __STUNREQUESTMANAGER_H__ +#define __STUNREQUESTMANAGER_H__ + +#include "talk/base/sigslot.h" +#include "talk/base/thread.h" +#include "talk/p2p/base/stun.h" +#include +#include + +namespace cricket { + +class StunRequest; + +// Manages a set of STUN requests, sending and resending until we receive a +// response or determine that the request has timed out. +class StunRequestManager { +public: + StunRequestManager(Thread* thread); + ~StunRequestManager(); + + // Starts sending the given request (perhaps after a delay). + void Send(StunRequest* request); + void SendDelayed(StunRequest* request, int delay); + + // Removes a stun request that was added previously. This will happen + // automatically when a request succeeds, fails, or times out. + void Remove(StunRequest* request); + + // Removes all stun requests that were added previously. + void Clear(); + + // Determines whether the given message is a response to one of the + // outstanding requests, and if so, processes it appropriately. + bool CheckResponse(StunMessage* msg); + bool CheckResponse(const char* data, size_t size); + + // Raised when there are bytes to be sent. + sigslot::signal2 SignalSendPacket; + +private: + typedef std::map RequestMap; + + Thread* thread_; + RequestMap requests_; + + friend class StunRequest; +}; + +// Represents an individual request to be sent. The STUN message can either be +// constructed beforehand or built on demand. +class StunRequest : public MessageHandler { +public: + StunRequest(); + StunRequest(StunMessage* request); + virtual ~StunRequest(); + + // The manager handling this request (if it has been scheduled for sending). + StunRequestManager* manager() { return manager_; } + + // Returns the transaction ID of this request. + const std::string& id() { return id_; } + + // Returns the STUN type of the request message. + const StunMessageType type(); + + // Handles messages for sending and timeout. + void OnMessage(Message* pmsg); + + // Time elapsed since last send (in ms) + uint32 Elapsed() const; + +protected: + int count_; + bool timeout_; + + // Fills in the actual request to be sent. Note that the transaction ID will + // already be set and cannot be changed. + virtual void Prepare(StunMessage* request) {} + + // Called when the message receives a response or times out. + virtual void OnResponse(StunMessage* response) {} + virtual void OnErrorResponse(StunMessage* response) {} + virtual void OnTimeout() {} + virtual int GetNextDelay(); + +private: + StunRequestManager* manager_; + std::string id_; + StunMessage* msg_; + uint32 tstamp_; + + void set_manager(StunRequestManager* manager); + + friend class StunRequestManager; +}; + +} // namespace cricket + +#endif // __STUNREQUESTMANAGER_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver.cc new file mode 100644 index 00000000..6e4f6b66 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver.cc @@ -0,0 +1,160 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/base/bytebuffer.h" +#include "talk/p2p/base/stunserver.h" +#include + +#ifdef POSIX +extern "C" { +#include +} +#endif // POSIX + +namespace cricket { + +StunServer::StunServer(AsyncUDPSocket* socket) : socket_(socket) { + socket_->SignalReadPacket.connect(this, &StunServer::OnPacket); +} + +StunServer::~StunServer() { + socket_->SignalReadPacket.disconnect(this); +} + +void StunServer::OnPacket( + const char* buf, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket) { + + // TODO: If appropriate, look for the magic cookie before parsing. + + // Parse the STUN message. + ByteBuffer bbuf(buf, size); + StunMessage msg; + if (!msg.Read(&bbuf)) { + SendErrorResponse(msg, remote_addr, 400, "Bad Request"); + return; + } + + // TODO: If this is UDP, then we shouldn't allow non-fully-parsed messages. + + // TODO: If unknown non-optiional (<= 0x7fff) attributes are found, send a + // 420 "Unknown Attribute" response. + + // TODO: Check that a message-integrity attribute was given (or send 401 + // "Unauthorized"). Check that a username attribute was given (or send + // 432 "Missing Username"). Look up the username and password. If it + // is missing or the HMAC is wrong, send 431 "Integrity Check Failure". + + // Send the message to the appropriate handler function. + switch (msg.type()) { + case STUN_BINDING_REQUEST: + OnBindingRequest(&msg, remote_addr); + return; + + case STUN_ALLOCATE_REQUEST: + OnAllocateRequest(&msg, remote_addr); + return; + + default: + SendErrorResponse(msg, remote_addr, 600, "Operation Not Supported"); + } +} + +void StunServer::OnBindingRequest( + StunMessage* msg, const SocketAddress& remote_addr) { + StunMessage response; + response.SetType(STUN_BINDING_RESPONSE); + response.SetTransactionID(msg->transaction_id()); + + // Tell the user the address that we received their request from. + StunAddressAttribute* mapped_addr = + StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS); + mapped_addr->SetFamily(1); + mapped_addr->SetPort(remote_addr.port()); + mapped_addr->SetIP(remote_addr.ip()); + response.AddAttribute(mapped_addr); + + // Tell the user the address that we are sending the response from. + SocketAddress local_addr = socket_->GetLocalAddress(); + StunAddressAttribute* source_addr = + StunAttribute::CreateAddress(STUN_ATTR_SOURCE_ADDRESS); + source_addr->SetFamily(1); + source_addr->SetPort(local_addr.port()); + source_addr->SetIP(local_addr.ip()); + response.AddAttribute(source_addr); + + // TODO: Add username and message-integrity. + + // TODO: Add changed-address. (Keep information about three other servers.) + + SendResponse(response, remote_addr); +} + +void StunServer::OnAllocateRequest( + StunMessage* msg, const SocketAddress& addr) { + SendErrorResponse(*msg, addr, 600, "Operation Not Supported"); +} + +void StunServer::OnSharedSecretRequest( + StunMessage* msg, const SocketAddress& addr) { + SendErrorResponse(*msg, addr, 600, "Operation Not Supported"); +} + +void StunServer::OnSendRequest(StunMessage* msg, const SocketAddress& addr) { + SendErrorResponse(*msg, addr, 600, "Operation Not Supported"); +} + +void StunServer::SendErrorResponse( + const StunMessage& msg, const SocketAddress& addr, int error_code, + const char* error_desc) { + + StunMessage err_msg; + err_msg.SetType(GetStunErrorResponseType(msg.type())); + err_msg.SetTransactionID(msg.transaction_id()); + + StunErrorCodeAttribute* err_code = StunAttribute::CreateErrorCode(); + err_code->SetErrorClass(error_code / 100); + err_code->SetNumber(error_code % 100); + err_code->SetReason(error_desc); + err_msg.AddAttribute(err_code); + + SendResponse(err_msg, addr); +} + +void StunServer::SendResponse( + const StunMessage& msg, const SocketAddress& addr) { + + ByteBuffer buf; + msg.Write(&buf); + + // TODO: Allow response addr attribute if sent from another stun server. + + if (socket_->SendTo(buf.Data(), buf.Length(), addr) < 0) + std::cerr << "sendto: " << std::strerror(errno) << std::endl; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver.h new file mode 100644 index 00000000..3043645d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver.h @@ -0,0 +1,73 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __STUNSERVER_H__ +#define __STUNSERVER_H__ + +#include "talk/base/asyncudpsocket.h" +#include "talk/p2p/base/stun.h" + +namespace cricket { + +const int STUN_SERVER_PORT = 3478; + +class StunServer : public sigslot::has_slots<> { +public: + // Creates a STUN server, which will listen on the given socket. + StunServer(AsyncUDPSocket* socket); + + // Removes the STUN server from the socket, but does not delete the socket. + ~StunServer(); + +protected: + + // Slot for AsyncSocket.PacketRead: + void OnPacket( + const char* buf, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket); + + // Handlers for the different types of STUN/TURN requests: + void OnBindingRequest(StunMessage* msg, const SocketAddress& addr); + void OnAllocateRequest(StunMessage* msg, const SocketAddress& addr); + void OnSharedSecretRequest(StunMessage* msg, const SocketAddress& addr); + void OnSendRequest(StunMessage* msg, const SocketAddress& addr); + + // Sends an error response to the given message back to the user. + void SendErrorResponse( + const StunMessage& msg, const SocketAddress& addr, int error_code, + const char* error_desc); + + // Sends the given message to the appropriate destination. + void SendResponse(const StunMessage& msg, const SocketAddress& addr); + +private: + AsyncUDPSocket* socket_; +}; + +} // namespace cricket + +#endif // __STUNSERVER_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver.pro b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver.pro new file mode 100644 index 00000000..dce92ec4 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver.pro @@ -0,0 +1,14 @@ +TEMPLATE = app +INCLUDEPATH = ../../.. +DEFINES += POSIX + +include(../../../../../conf.pri) + +# Input +SOURCES += \ + stunserver.cc \ + stunserver_main.cc \ + ../../base/host.cc #\ +# ../../base/socketaddresspair.cc + +LIBS += ../../../liblibjingle.a diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver_main.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver_main.cc new file mode 100644 index 00000000..bd8a96e5 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/stunserver_main.cc @@ -0,0 +1,66 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/base/host.h" +#include "talk/base/thread.h" +#include "talk/p2p/base/stunserver.h" +#include + +#ifdef POSIX +extern "C" { +#include +} +#endif // POSIX + +using namespace cricket; + +int main(int argc, char* argv[]) { + if (argc != 1) { + std::cerr << "usage: stunserver" << std::endl; + return 1; + } + + SocketAddress server_addr(LocalHost().networks()[1]->ip(), 7000); + + Thread *pthMain = Thread::Current(); + + AsyncUDPSocket* server_socket = CreateAsyncUDPSocket(pthMain->socketserver()); + if (server_socket->Bind(server_addr) < 0) { + std::cerr << "bind: " << std::strerror(errno) << std::endl; + return 1; + } + + StunServer* server = new StunServer(server_socket); + + std::cout << "Listening at " << server_addr.ToString() << std::endl; + + pthMain->Loop(); + + delete server; + delete server_socket; + return 0; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/tcpport.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/tcpport.cc new file mode 100644 index 00000000..a2d2adc6 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/tcpport.cc @@ -0,0 +1,250 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/p2p/base/tcpport.h" +#include "talk/base/logging.h" +#ifdef WIN32 +#include "talk/base/winfirewall.h" +#endif // WIN32 +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::strerror; +} +#endif + +#ifdef POSIX +extern "C" { +#include +} +#endif // POSIX + +namespace cricket { + +#ifdef WIN32 +static WinFirewall win_firewall; +#endif // WIN32 + +TCPPort::TCPPort(Thread* thread, SocketFactory* factory, Network* network, + const SocketAddress& address) + : Port(thread, LOCAL_PORT_TYPE, factory, network), error_(0) { + incoming_only_ = (address.port() != 0); + socket_ = thread->socketserver()->CreateAsyncSocket(SOCK_STREAM); + socket_->SignalReadEvent.connect(this, &TCPPort::OnAcceptEvent); + if (socket_->Bind(address) < 0) + LOG(INFO) << "bind: " << std::strerror(socket_->GetError()); +} + +TCPPort::~TCPPort() { + delete socket_; +} + +Connection* TCPPort::CreateConnection(const Candidate& address, CandidateOrigin origin) { + // We only support TCP protocols + if ((address.protocol() != "tcp") && (address.protocol() != "ssltcp")) + return 0; + + // We can't accept TCP connections incoming on other ports + if (origin == ORIGIN_OTHER_PORT) + return 0; + + // Check if we are allowed to make outgoing TCP connections + if (incoming_only_ && (origin == ORIGIN_MESSAGE)) + return 0; + + // We don't know how to act as an ssl server yet + if ((address.protocol() == "ssltcp") && (origin == ORIGIN_THIS_PORT)) + return 0; + + TCPConnection* conn = 0; + if (AsyncTCPSocket * socket = GetIncoming(address.address(), true)) { + socket->SignalReadPacket.disconnect(this); + conn = new TCPConnection(this, address, socket); + } else { + conn = new TCPConnection(this, address); + } + AddConnection(conn); + return conn; +} + +void TCPPort::PrepareAddress() { + assert(socket_); + + bool allow_listen = true; +#ifdef WIN32 + if (win_firewall.Initialize()) { + char module_path[MAX_PATH + 1] = { 0 }; + ::GetModuleFileNameA(NULL, module_path, MAX_PATH); + if (win_firewall.Enabled() && !win_firewall.Authorized(module_path)) { + allow_listen = false; + } + } +#endif // WIN32 + if (allow_listen) { + if (socket_->Listen(5) < 0) + LOG(INFO) << "listen: " << std::strerror(socket_->GetError()); + } else { + LOG(INFO) << "not listening due to firewall restrictions"; + } + // Note: We still add the address, since otherwise the remote side won't recognize + // our incoming TCP connections. + add_address(socket_->GetLocalAddress(), "tcp"); +} + +int TCPPort::SendTo(const void* data, size_t size, const SocketAddress& addr, bool payload) { + AsyncTCPSocket * socket = 0; + + if (TCPConnection * conn = static_cast(GetConnection(addr))) { + socket = conn->socket(); + } else { + socket = GetIncoming(addr); + } + if (!socket) { + LOG(INFO) << "Unknown destination for SendTo: " << addr.ToString(); + return -1; // TODO: Set error_ + } + + //LOG(INFO) << "TCPPort::SendTo(" << size << ", " << addr.ToString() << ")"; + + int sent = socket->Send(data, size); + if (sent < 0) + error_ = socket->GetError(); + return sent; +} + +int TCPPort::SetOption(Socket::Option opt, int value) { + return socket_->SetOption(opt, value); +} + +int TCPPort::GetError() { + assert(socket_); + return error_; +} + +void TCPPort::OnAcceptEvent(AsyncSocket* socket) { + assert(socket == socket_); + + Incoming incoming; + AsyncSocket * newsocket = static_cast(socket->Accept(&incoming.addr)); + if (!newsocket) { + // TODO: Do something better like forwarding the error to the user. + LOG(INFO) << "accept: " << socket_->GetError() << " " << std::strerror(socket_->GetError()); + return; + } + incoming.socket = new AsyncTCPSocket(newsocket); + incoming.socket->SignalReadPacket.connect(this, &TCPPort::OnReadPacket); + + LOG(INFO) << "accepted incoming connection from " << incoming.addr.ToString(); + incoming_.push_back(incoming); + + // Prime a read event in case data is waiting + newsocket->SignalReadEvent(newsocket); +} + +AsyncTCPSocket * TCPPort::GetIncoming(const SocketAddress& addr, bool remove) { + AsyncTCPSocket * socket = 0; + for (std::list::iterator it = incoming_.begin(); it != incoming_.end(); ++it) { + if (it->addr == addr) { + socket = it->socket; + if (remove) + incoming_.erase(it); + break; + } + } + return socket; +} + +void TCPPort::OnReadPacket(const char* data, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket) { + Port::OnReadPacket(data, size, remote_addr); +} + +TCPConnection::TCPConnection(TCPPort* port, const Candidate& candidate, AsyncTCPSocket* socket) + : Connection(port, 0, candidate), socket_(socket), error_(0) { + bool outgoing = (socket_ == 0); + if (outgoing) { + socket_ = static_cast(port->CreatePacketSocket( + (candidate.protocol() == "ssltcp") ? PROTO_SSLTCP : PROTO_TCP)); + } + socket_->SignalReadPacket.connect(this, &TCPConnection::OnReadPacket); + socket_->SignalClose.connect(this, &TCPConnection::OnClose); + if (outgoing) { + connected_ = false; + socket_->SignalConnect.connect(this, &TCPConnection::OnConnect); + socket_->Connect(candidate.address()); + LOG(INFO) << "Connecting to " << candidate.address().ToString(); + } +} + +TCPConnection::~TCPConnection() { +} + +int TCPConnection::Send(const void* data, size_t size) { + if (write_state() != STATE_WRITABLE) + return 0; + + int sent = socket_->Send(data, size); + if (sent < 0) { + error_ = socket_->GetError(); + } else { + sent_total_bytes_ += sent; + } + return sent; +} + +int TCPConnection::GetError() { + return error_; +} + +TCPPort* TCPConnection::tcpport() { + return static_cast(port_); +} + +void TCPConnection::OnConnect(AsyncTCPSocket* socket) { + assert(socket == socket_); + LOG(INFO) << "tcp connected to " << socket->GetRemoteAddress().ToString(); + set_connected(true); +} + +void TCPConnection::OnClose(AsyncTCPSocket* socket, int error) { + assert(socket == socket_); + LOG(INFO) << "tcp closed with error: " << error; + set_connected(false); +} + +void TCPConnection::OnReadPacket(const char* data, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket) { + assert(socket == socket_); + Connection::OnReadPacket(data, size); +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/tcpport.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/tcpport.h new file mode 100644 index 00000000..f6a9beb7 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/tcpport.h @@ -0,0 +1,116 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __TCPPORT_H__ +#define __TCPPORT_H__ + +#include +#include "talk/base/asynctcpsocket.h" +#include "talk/p2p/base/port.h" + +namespace cricket { + +class TCPConnection; + +extern const std::string LOCAL_PORT_TYPE; // type of TCP ports + +// Communicates using a local TCP port. +// +// This class is designed to allow subclasses to take advantage of the +// connection management provided by this class. A subclass should take of all +// packet sending and preparation, but when a packet is received, it should +// call this TCPPort::OnReadPacket (3 arg) to dispatch to a connection. +class TCPPort : public Port { +public: + TCPPort(Thread* thread, SocketFactory* factory, Network* network, + const SocketAddress& address); + virtual ~TCPPort(); + + virtual Connection* CreateConnection(const Candidate& address, CandidateOrigin origin); + + virtual void PrepareAddress(); + + virtual int SetOption(Socket::Option opt, int value); + virtual int GetError(); + +protected: + // Handles sending using the local TCP socket. + virtual int SendTo(const void* data, size_t size, const SocketAddress& addr, bool payload); + + // Creates TCPConnection for incoming sockets + void OnAcceptEvent(AsyncSocket* socket); + + AsyncSocket* socket() { return socket_; } + +private: + bool incoming_only_; + AsyncSocket* socket_; + int error_; + + struct Incoming { + SocketAddress addr; + AsyncTCPSocket * socket; + }; + std::list incoming_; + + AsyncTCPSocket * GetIncoming(const SocketAddress& addr, bool remove = false); + + // Receives packet signal from the local TCP Socket. + void OnReadPacket(const char* data, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket); + + friend class TCPConnection; +}; + +class TCPConnection : public Connection { +public: + // Connection is outgoing unless socket is specified + TCPConnection(TCPPort* port, const Candidate& candidate, AsyncTCPSocket* socket = 0); + virtual ~TCPConnection(); + + virtual int Send(const void* data, size_t size); + virtual int GetError(); + + AsyncTCPSocket * socket() { return socket_; } + +private: + TCPPort* tcpport(); + AsyncTCPSocket* socket_; + bool connected_; + int error_; + + void OnConnect(AsyncTCPSocket* socket); + void OnClose(AsyncTCPSocket* socket, int error); + void OnReadPacket(const char* data, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket); + + friend class TCPPort; +}; + +} // namespace cricket + +#endif // __TCPPORT_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/udpport.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/udpport.cc new file mode 100644 index 00000000..fabbb25b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/udpport.cc @@ -0,0 +1,117 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/base/logging.h" +#include "talk/p2p/base/udpport.h" +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::strerror; +} +#endif + +#ifdef POSIX +extern "C" { +#include +} +#endif // POSIX + +namespace cricket { + +const std::string LOCAL_PORT_TYPE("local"); + +UDPPort::UDPPort(Thread* thread, SocketFactory* factory, Network* network, + const SocketAddress& address) + : Port(thread, LOCAL_PORT_TYPE, factory, network), error_(0) { + socket_ = CreatePacketSocket(PROTO_UDP); + socket_->SignalReadPacket.connect(this, &UDPPort::OnReadPacketSlot); + if (socket_->Bind(address) < 0) + PLOG(LERROR, socket_->GetError()) << "bind"; +} + +UDPPort::UDPPort(Thread* thread, const std::string &type, + SocketFactory* factory, Network* network) + : Port(thread, type, factory, network), socket_(0), error_(0) { +} + +UDPPort::~UDPPort() { + delete socket_; +} + +void UDPPort::PrepareAddress() { + assert(socket_); + add_address(socket_->GetLocalAddress(), "udp"); +} + +Connection* UDPPort::CreateConnection(const Candidate& address, CandidateOrigin origin) { + if (address.protocol() != "udp") + return 0; + + Connection * conn = new ProxyConnection(this, 0, address); + AddConnection(conn); + return conn; +} + +int UDPPort::SendTo(const void* data, size_t size, const SocketAddress& addr, bool payload) { + assert(socket_); + int sent = socket_->SendTo(data, size, addr); + if (sent < 0) + error_ = socket_->GetError(); + return sent; +} + +int UDPPort::SetOption(Socket::Option opt, int value) { + return socket_->SetOption(opt, value); +} + +int UDPPort::GetError() { + assert(socket_); + return error_; +} + +void UDPPort::OnReadPacketSlot( + const char* data, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket) { + assert(socket == socket_); + OnReadPacket(data, size, remote_addr); +} + +void UDPPort::OnReadPacket( + const char* data, size_t size, const SocketAddress& remote_addr) { + if (Connection* conn = GetConnection(remote_addr)) { + conn->OnReadPacket(data, size); + } else { + Port::OnReadPacket(data, size, remote_addr); + } +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/udpport.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/udpport.h new file mode 100644 index 00000000..4bcd113e --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/base/udpport.h @@ -0,0 +1,81 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef __UDPPORT_H__ +#define __UDPPORT_H__ + +#include "talk/base/asyncudpsocket.h" +#include "talk/p2p/base/port.h" + +namespace cricket { + +extern const std::string LOCAL_PORT_TYPE; // type of UDP ports + +// Communicates using a local UDP port. +// +// This class is designed to allow subclasses to take advantage of the +// connection management provided by this class. A subclass should take of all +// packet sending and preparation, but when a packet is received, it should +// call this UDPPort::OnReadPacket (3 arg) to dispatch to a connection. +class UDPPort : public Port { +public: + UDPPort(Thread* thread, SocketFactory* factory, Network* network, + const SocketAddress& address); + virtual ~UDPPort(); + + virtual void PrepareAddress(); + virtual Connection* CreateConnection(const Candidate& address, CandidateOrigin origin); + + virtual int SetOption(Socket::Option opt, int value); + virtual int GetError(); + +protected: + UDPPort(Thread* thread, const std::string &type, SocketFactory* factory, + Network* network); + + // Handles sending using the local UDP socket. + virtual int SendTo(const void* data, size_t size, const SocketAddress& addr, bool payload); + + // Dispatches the given packet to the port or connection as appropriate. + void OnReadPacket( + const char* data, size_t size, const SocketAddress& remote_addr); + + AsyncPacketSocket* socket() { return socket_; } + +private: + AsyncPacketSocket* socket_; + int error_; + + // Receives packet signal from the local UDP Socket. + void OnReadPacketSlot( + const char* data, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket); +}; + +} // namespace cricket + +#endif // __UDPPORT_H__ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/Makefile.am new file mode 100644 index 00000000..2bdd95ff --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/Makefile.am @@ -0,0 +1,11 @@ +libcricketp2pclient_la_SOURCES = sessionclient.cc \ + basicportallocator.cc \ + socketmonitor.cc + +noinst_HEADERS = basicportallocator.h \ + sessionclient.h \ + socketmonitor.h + +AM_CPPFLAGS = -I$(srcdir)/../../.. -DLINUX -DPOSIX -DINTERNAL_BUILD + +noinst_LTLIBRARIES = libcricketp2pclient.la diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/basicportallocator.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/basicportallocator.cc new file mode 100644 index 00000000..5192595c --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/basicportallocator.cc @@ -0,0 +1,667 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/base/host.h" +#include "talk/base/logging.h" +#include "talk/p2p/client/basicportallocator.h" +#include "talk/p2p/base/port.h" +#include "talk/p2p/base/udpport.h" +#include "talk/p2p/base/tcpport.h" +#include "talk/p2p/base/stunport.h" +#include "talk/p2p/base/relayport.h" +#include "talk/p2p/base/helpers.h" +#include + +namespace { + +const uint32 MSG_CONFIG_START = 1; +const uint32 MSG_CONFIG_READY = 2; +const uint32 MSG_ALLOCATE = 3; +const uint32 MSG_ALLOCATION_PHASE = 4; +const uint32 MSG_SHAKE = 5; + +const uint32 ALLOCATE_DELAY = 250; +const uint32 ALLOCATION_STEP_DELAY = 1 * 1000; + +const int PHASE_UDP = 0; +const int PHASE_RELAY = 1; +const int PHASE_TCP = 2; +const int PHASE_SSLTCP = 3; +const int kNumPhases = 4; + +const float PREF_LOCAL_UDP = 1.0f; +const float PREF_LOCAL_STUN = 0.9f; +const float PREF_LOCAL_TCP = 0.8f; +const float PREF_RELAY = 0.5f; + +const float RELAY_PRIMARY_PREF_MODIFIER = 0.0f; // modifiers of the above constants +const float RELAY_BACKUP_PREF_MODIFIER = -0.2f; + + +// Returns the phase in which a given local candidate (or rather, the port that +// gave rise to that local candidate) would have been created. +int LocalCandidateToPhase(const cricket::Candidate& candidate) { + cricket::ProtocolType proto; + bool result = cricket::StringToProto(candidate.protocol().c_str(), proto); + if (result) { + if (candidate.type() == cricket::LOCAL_PORT_TYPE) { + switch (proto) { + case cricket::PROTO_UDP: return PHASE_UDP; + case cricket::PROTO_TCP: return PHASE_TCP; + default: assert(false); + } + } else if (candidate.type() == cricket::STUN_PORT_TYPE) { + return PHASE_UDP; + } else if (candidate.type() == cricket::RELAY_PORT_TYPE) { + switch (proto) { + case cricket::PROTO_UDP: return PHASE_RELAY; + case cricket::PROTO_TCP: return PHASE_TCP; + case cricket::PROTO_SSLTCP: return PHASE_SSLTCP; + default: assert(false); + } + } else { + assert(false); + } + } else { + assert(false); + } + return PHASE_UDP; // reached only with assert failure +} + +const int SHAKE_MIN_DELAY = 45 * 1000; // 45 seconds +const int SHAKE_MAX_DELAY = 90 * 1000; // 90 seconds + +int ShakeDelay() { + int range = SHAKE_MAX_DELAY - SHAKE_MIN_DELAY + 1; + return SHAKE_MIN_DELAY + cricket::CreateRandomId() % range; +} + +} + +namespace cricket { + +// Performs the allocation of ports, in a sequenced (timed) manner, for a given +// network and IP address. +class AllocationSequence: public MessageHandler { +public: + AllocationSequence(BasicPortAllocatorSession* session, + Network* network, + PortConfiguration* config); + ~AllocationSequence(); + + // Determines whether this sequence is operating on an equivalent network + // setup to the one given. + bool IsEquivalent(Network* network); + + // Starts and stops the sequence. When started, it will continue allocating + // new ports on its own timed schedule. + void Start(); + void Stop(); + + // MessageHandler: + void OnMessage(Message* msg); + + void EnableProtocol(ProtocolType proto); + bool ProtocolEnabled(ProtocolType proto) const; + +private: + BasicPortAllocatorSession* session_; + Network* network_; + uint32 ip_; + PortConfiguration* config_; + bool running_; + int step_; + int step_of_phase_[kNumPhases]; + + typedef std::vector ProtocolList; + ProtocolList protocols_; + + void CreateUDPPorts(); + void CreateTCPPorts(); + void CreateStunPorts(); + void CreateRelayPorts(); +}; + + +// BasicPortAllocator + +BasicPortAllocator::BasicPortAllocator(NetworkManager* network_manager) + : network_manager_(network_manager), best_writable_phase_(-1), stun_address_(NULL), relay_address_(NULL) { +} + +BasicPortAllocator::BasicPortAllocator(NetworkManager* network_manager, SocketAddress* stun_address, SocketAddress *relay_address) + : network_manager_(network_manager), best_writable_phase_(-1), stun_address_(stun_address), relay_address_(relay_address) { +} + +BasicPortAllocator::~BasicPortAllocator() { +} + +int BasicPortAllocator::best_writable_phase() const { + // If we are configured with an HTTP proxy, the best bet is to use the relay + if ((best_writable_phase_ == -1) + && ((proxy().type == PROXY_HTTPS) || (proxy().type == PROXY_UNKNOWN))) { + return PHASE_RELAY; + } + return best_writable_phase_; +} + +PortAllocatorSession *BasicPortAllocator::CreateSession(const std::string &name) { + return new BasicPortAllocatorSession(this, name, stun_address_, relay_address_); +} + +void BasicPortAllocator::AddWritablePhase(int phase) { + if ((best_writable_phase_ == -1) || (phase < best_writable_phase_)) + best_writable_phase_ = phase; +} + +// BasicPortAllocatorSession + +BasicPortAllocatorSession::BasicPortAllocatorSession( + BasicPortAllocator *allocator, + const std::string &name) + : allocator_(allocator), name_(name), network_thread_(NULL), + config_thread_(NULL), allocation_started_(false), running_(false), + stun_address_(NULL), relay_address_(NULL) { +} + +BasicPortAllocatorSession::BasicPortAllocatorSession( + BasicPortAllocator *allocator, + const std::string &name, + SocketAddress *stun_address, + SocketAddress *relay_address) + : allocator_(allocator), name_(name), network_thread_(NULL), + config_thread_(NULL), allocation_started_(false), running_(false), + stun_address_(stun_address), relay_address_(relay_address) { +} + +BasicPortAllocatorSession::~BasicPortAllocatorSession() { + if (config_thread_ != NULL) + config_thread_->Clear(this); + if (network_thread_ != NULL) + network_thread_->Clear(this); + + std::vector::iterator it; + for (it = ports_.begin(); it != ports_.end(); it++) + delete it->port; + + for (uint32 i = 0; i < configs_.size(); ++i) + delete configs_[i]; + + for (uint32 i = 0; i < sequences_.size(); ++i) + delete sequences_[i]; +} + +void BasicPortAllocatorSession::GetInitialPorts() { + network_thread_ = Thread::Current(); + if (!config_thread_) + config_thread_ = network_thread_; + + config_thread_->Post(this, MSG_CONFIG_START); + + if (allocator()->flags() & PORTALLOCATOR_ENABLE_SHAKER) + network_thread_->PostDelayed(ShakeDelay(), this, MSG_SHAKE); +} + +void BasicPortAllocatorSession::StartGetAllPorts() { + assert(Thread::Current() == network_thread_); + running_ = true; + if (allocation_started_) + network_thread_->PostDelayed(ALLOCATE_DELAY, this, MSG_ALLOCATE); + for (uint32 i = 0; i < sequences_.size(); ++i) + sequences_[i]->Start(); + for (size_t i = 0; i < ports_.size(); ++i) + ports_[i].port->Start(); +} + +void BasicPortAllocatorSession::StopGetAllPorts() { + assert(Thread::Current() == network_thread_); + running_ = false; + network_thread_->Clear(this, MSG_ALLOCATE); + for (uint32 i = 0; i < sequences_.size(); ++i) + sequences_[i]->Stop(); +} + +void BasicPortAllocatorSession::OnMessage(Message *message) { + switch (message->message_id) { + case MSG_CONFIG_START: + assert(Thread::Current() == config_thread_); + GetPortConfigurations(); + break; + + case MSG_CONFIG_READY: + assert(Thread::Current() == network_thread_); + OnConfigReady(static_cast(message->pdata)); + break; + + case MSG_ALLOCATE: + assert(Thread::Current() == network_thread_); + OnAllocate(); + break; + + case MSG_SHAKE: + assert(Thread::Current() == network_thread_); + OnShake(); + break; + + default: + assert(false); + } +} + +void BasicPortAllocatorSession::GetPortConfigurations() { + PortConfiguration* config = NULL; + if (stun_address_ != NULL) + config = new PortConfiguration(*stun_address_, + CreateRandomString(16), + CreateRandomString(16), + ""); + PortConfiguration::PortList ports; + if (relay_address_ != NULL) { + ports.push_back(ProtocolAddress(*relay_address_, PROTO_UDP)); + config->AddRelay(ports, RELAY_PRIMARY_PREF_MODIFIER); + } + + ConfigReady(config); +} + +void BasicPortAllocatorSession::ConfigReady(PortConfiguration* config) { + network_thread_->Post(this, MSG_CONFIG_READY, config); +} + +// Adds a configuration to the list. +void BasicPortAllocatorSession::OnConfigReady(PortConfiguration* config) { + if (config) + configs_.push_back(config); + + AllocatePorts(); +} + +void BasicPortAllocatorSession::AllocatePorts() { + assert(Thread::Current() == network_thread_); + + if (allocator_->proxy().type != PROXY_NONE) + Port::set_proxy(allocator_->proxy()); + + network_thread_->Post(this, MSG_ALLOCATE); +} + +// For each network, see if we have a sequence that covers it already. If not, +// create a new sequence to create the appropriate ports. +void BasicPortAllocatorSession::OnAllocate() { + std::vector networks; + allocator_->network_manager()->GetNetworks(networks); + + for (uint32 i = 0; i < networks.size(); ++i) { + if (HasEquivalentSequence(networks[i])) + continue; + + PortConfiguration* config = NULL; + if (configs_.size() > 0) + config = configs_.back(); + + AllocationSequence* sequence = + new AllocationSequence(this, networks[i], config); + if (running_) + sequence->Start(); + + sequences_.push_back(sequence); + } + + allocation_started_ = true; + if (running_) + network_thread_->PostDelayed(ALLOCATE_DELAY, this, MSG_ALLOCATE); +} + +bool BasicPortAllocatorSession::HasEquivalentSequence(Network* network) { + for (uint32 i = 0; i < sequences_.size(); ++i) + if (sequences_[i]->IsEquivalent(network)) + return true; + return false; +} + +void BasicPortAllocatorSession::AddAllocatedPort(Port* port, + AllocationSequence * seq, + float pref, + bool prepare_address) { + if (!port) + return; + + port->set_name(name_); + port->set_preference(pref); + port->set_generation(generation()); + PortData data; + data.port = port; + data.sequence = seq; + data.ready = false; + ports_.push_back(data); + port->SignalAddressReady.connect(this, &BasicPortAllocatorSession::OnAddressReady); + port->SignalConnectionCreated.connect(this, &BasicPortAllocatorSession::OnConnectionCreated); + port->SignalDestroyed.connect(this, &BasicPortAllocatorSession::OnPortDestroyed); + if (prepare_address) + port->PrepareAddress(); + if (running_) + port->Start(); +} + +void BasicPortAllocatorSession::OnAddressReady(Port *port) { + assert(Thread::Current() == network_thread_); + std::vector::iterator it = std::find(ports_.begin(), ports_.end(), port); + assert(it != ports_.end()); + assert(!it->ready); + it->ready = true; + SignalPortReady(this, port); + + // Only accumulate the candidates whose protocol has been enabled + std::vector candidates; + const std::vector& potentials = port->candidates(); + for (size_t i=0; isequence->ProtocolEnabled(pvalue)) { + candidates.push_back(potentials[i]); + } + } + if (!candidates.empty()) { + SignalCandidatesReady(this, candidates); + } +} + +void BasicPortAllocatorSession::OnProtocolEnabled(AllocationSequence * seq, ProtocolType proto) { + std::vector candidates; + for (std::vector::iterator it = ports_.begin(); it != ports_.end(); ++it) { + if (!it->ready || (it->sequence != seq)) + continue; + + const std::vector& potentials = it->port->candidates(); + for (size_t i=0; i::iterator iter = + find(ports_.begin(), ports_.end(), port); + assert(iter != ports_.end()); + ports_.erase(iter); + + LOG(INFO) << "Removed port from allocator: " + << static_cast(ports_.size()) << " remaining"; +} + +void BasicPortAllocatorSession::OnConnectionCreated(Port* port, Connection* conn) { + conn->SignalStateChange.connect(this, &BasicPortAllocatorSession::OnConnectionStateChange); +} + +void BasicPortAllocatorSession::OnConnectionStateChange(Connection* conn) { + if (conn->write_state() == Connection::STATE_WRITABLE) + allocator_->AddWritablePhase(LocalCandidateToPhase(conn->local_candidate())); +} + +void BasicPortAllocatorSession::OnShake() { + LOG(INFO) << ">>>>> SHAKE <<<<< >>>>> SHAKE <<<<< >>>>> SHAKE <<<<<"; + + std::vector ports; + std::vector connections; + + for (size_t i = 0; i < ports_.size(); ++i) { + if (ports_[i].ready) + ports.push_back(ports_[i].port); + } + + for (size_t i = 0; i < ports.size(); ++i) { + Port::AddressMap::const_iterator iter; + for (iter = ports[i]->connections().begin(); + iter != ports[i]->connections().end(); + ++iter) { + connections.push_back(iter->second); + } + } + + LOG(INFO) << ">>>>> Destroying " << (int)ports.size() << " ports and " + << (int)connections.size() << " connections"; + + for (size_t i = 0; i < connections.size(); ++i) + connections[i]->Destroy(); + + if (running_ || (ports.size() > 0) || (connections.size() > 0)) + network_thread_->PostDelayed(ShakeDelay(), this, MSG_SHAKE); +} + +// AllocationSequence + +AllocationSequence::AllocationSequence(BasicPortAllocatorSession* session, + Network* network, + PortConfiguration* config) + : session_(session), network_(network), ip_(network->ip()), config_(config), + running_(false), step_(0) { + + // All of the phases up until the best-writable phase so far run in step 0. + // The other phases follow sequentially in the steps after that. If there is + // no best-writable so far, then only phase 0 occurs in step 0. + int last_phase_in_step_zero = + _max(0, session->allocator()->best_writable_phase()); + for (int phase = 0; phase < kNumPhases; ++phase) + step_of_phase_[phase] = _max(0, phase - last_phase_in_step_zero); + + // Immediately perform phase 0. + OnMessage(NULL); +} + +AllocationSequence::~AllocationSequence() { + session_->network_thread()->Clear(this); +} + +bool AllocationSequence::IsEquivalent(Network* network) { + return (network == network_) && (ip_ == network->ip()); +} + +void AllocationSequence::Start() { + running_ = true; + session_->network_thread()->PostDelayed(ALLOCATION_STEP_DELAY, + this, + MSG_ALLOCATION_PHASE); +} + +void AllocationSequence::Stop() { + running_ = false; + session_->network_thread()->Clear(this, MSG_ALLOCATION_PHASE); +} + +void AllocationSequence::OnMessage(Message* msg) { + assert(Thread::Current() == session_->network_thread()); + if (msg) + assert(msg->message_id == MSG_ALLOCATION_PHASE); + + // Perform all of the phases in the current step. + for (int phase = 0; phase < kNumPhases; phase++) { + if (step_of_phase_[phase] != step_) + continue; + + switch (phase) { + case PHASE_UDP: + LOG(INFO) << "Phase=UDP Step=" << step_; + CreateUDPPorts(); + CreateStunPorts(); + EnableProtocol(PROTO_UDP); + break; + + case PHASE_RELAY: + LOG(INFO) << "Phase=RELAY Step=" << step_; + CreateRelayPorts(); + break; + + case PHASE_TCP: + LOG(INFO) << "Phase=TCP Step=" << step_; + CreateTCPPorts(); + EnableProtocol(PROTO_TCP); + break; + + case PHASE_SSLTCP: + LOG(INFO) << "Phase=SSLTCP Step=" << step_; + EnableProtocol(PROTO_SSLTCP); + break; + + default: + // Nothing else we can do. + return; + } + } + + // TODO: use different delays for each stage + step_ += 1; + if (running_) { + session_->network_thread()->PostDelayed(ALLOCATION_STEP_DELAY, + this, + MSG_ALLOCATION_PHASE); + } +} + +void AllocationSequence::EnableProtocol(ProtocolType proto) { + if (!ProtocolEnabled(proto)) { + protocols_.push_back(proto); + session_->OnProtocolEnabled(this, proto); + } +} + +bool AllocationSequence::ProtocolEnabled(ProtocolType proto) const { + for (ProtocolList::const_iterator it = protocols_.begin(); it != protocols_.end(); ++it) { + if (*it == proto) + return true; + } + return false; +} + +void AllocationSequence::CreateUDPPorts() { + if (session_->allocator()->flags() & PORTALLOCATOR_DISABLE_UDP) + return; + + Port* port = new UDPPort(session_->network_thread(), NULL, network_, + SocketAddress(ip_, 0)); + session_->AddAllocatedPort(port, this, PREF_LOCAL_UDP); +} + +void AllocationSequence::CreateTCPPorts() { + if (session_->allocator()->flags() & PORTALLOCATOR_DISABLE_TCP) + return; + + Port* port = new TCPPort(session_->network_thread(), NULL, network_, + SocketAddress(ip_, 0)); + session_->AddAllocatedPort(port, this, PREF_LOCAL_TCP); +} + +void AllocationSequence::CreateStunPorts() { + if (session_->allocator()->flags() & PORTALLOCATOR_DISABLE_STUN) + return; + + if (!config_ || config_->stun_address.IsAny()) + return; + + Port* port = new StunPort(session_->network_thread(), NULL, network_, + SocketAddress(ip_, 0), config_->stun_address); + session_->AddAllocatedPort(port, this, PREF_LOCAL_STUN); +} + +void AllocationSequence::CreateRelayPorts() { + if (session_->allocator()->flags() & PORTALLOCATOR_DISABLE_RELAY) + return; + + if (!config_) + return; + + PortConfiguration::RelayList::const_iterator relay; + for (relay = config_->relays.begin(); + relay != config_->relays.end(); + ++relay) { + + RelayPort *port = new RelayPort(session_->network_thread(), NULL, network_, + SocketAddress(ip_, 0), + config_->username, config_->password, + config_->magic_cookie); + // Note: We must add the allocated port before we add addresses because + // the latter will create candidates that need name and preference + // settings. However, we also can't prepare the address (normally + // done by AddAllocatedPort) until we have these addresses. So we + // wait to do that until below. + session_->AddAllocatedPort(port, this, PREF_RELAY + relay->pref_modifier, false); + + // Add the addresses of this protocol. + PortConfiguration::PortList::const_iterator relay_port; + for (relay_port = relay->ports.begin(); + relay_port != relay->ports.end(); + ++relay_port) { + port->AddServerAddress(*relay_port); + port->AddExternalAddress(*relay_port); + } + + // Start fetching an address for this port. + port->PrepareAddress(); + } +} + +// PortConfiguration + +PortConfiguration::PortConfiguration(const SocketAddress& sa, + const std::string& un, + const std::string& pw, + const std::string& mc) + : stun_address(sa), username(un), password(pw), magic_cookie(mc) { +} + +void PortConfiguration::AddRelay(const PortList& ports, float pref_modifier) { + RelayServer relay; + relay.ports = ports; + relay.pref_modifier = pref_modifier; + relays.push_back(relay); +} + +bool PortConfiguration::SupportsProtocol( + const PortConfiguration::RelayServer& relay, ProtocolType type) { + PortConfiguration::PortList::const_iterator relay_port; + for (relay_port = relay.ports.begin(); + relay_port != relay.ports.end(); + ++relay_port) { + if (relay_port->proto == type) + return true; + } + return false; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/basicportallocator.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/basicportallocator.h new file mode 100644 index 00000000..0f7b96b4 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/basicportallocator.h @@ -0,0 +1,172 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _BASICPORTALLOCATOR_H_ +#define _BASICPORTALLOCATOR_H_ + +#include "talk/base/thread.h" +#include "talk/base/messagequeue.h" +#include "talk/base/network.h" +#include "talk/p2p/base/portallocator.h" +#include +#include + +namespace cricket { + +class BasicPortAllocator: public PortAllocator { +public: + BasicPortAllocator(NetworkManager* network_manager); + BasicPortAllocator(NetworkManager* network_manager, SocketAddress *stun_server, SocketAddress *relay_server); + virtual ~BasicPortAllocator(); + + NetworkManager* network_manager() { return network_manager_; } + + // Returns the best (highest preference) phase that has produced a port that + // produced a writable connection. If no writable connections have been + // produced, this returns -1. + int best_writable_phase() const; + + virtual PortAllocatorSession *CreateSession(const std::string &name); + + // Called whenever a connection becomes writable with the argument being the + // phase that the corresponding port was created in. + void AddWritablePhase(int phase); + +private: + NetworkManager* network_manager_; + SocketAddress* stun_address_; + SocketAddress* relay_address_; + int best_writable_phase_; +}; + +struct PortConfiguration; +class AllocationSequence; + +class BasicPortAllocatorSession: public PortAllocatorSession, public MessageHandler { +public: + BasicPortAllocatorSession(BasicPortAllocator *allocator, + const std::string &name); + BasicPortAllocatorSession(BasicPortAllocator *allocator, + const std::string &name, + SocketAddress *stun_address, + SocketAddress *relay_address); + ~BasicPortAllocatorSession(); + + BasicPortAllocator* allocator() { return allocator_; } + const std::string& name() const { return name_; } + Thread* network_thread() { return network_thread_; } + + Thread* config_thread() { return config_thread_; } + void set_config_thread(Thread* thread) { config_thread_ = thread; } + + virtual void GetInitialPorts(); + virtual void StartGetAllPorts(); + virtual void StopGetAllPorts(); + virtual bool IsGettingAllPorts() { return running_; } + +protected: + // Starts the process of getting the port configurations. + virtual void GetPortConfigurations(); + + // Adds a port configuration that is now ready. Once we have one for each + // network (or a timeout occurs), we will start allocating ports. + void ConfigReady(PortConfiguration* config); + + // MessageHandler. Can be overriden if message IDs do not conflict. + virtual void OnMessage(Message *message); + +private: + void OnConfigReady(PortConfiguration* config); + void OnConfigTimeout(); + void AllocatePorts(); + void OnAllocate(); + bool HasEquivalentSequence(Network* network); + void AddAllocatedPort(Port* port, AllocationSequence * seq, float pref, bool prepare_address = true); + void OnAddressReady(Port *port); + void OnProtocolEnabled(AllocationSequence * seq, ProtocolType proto); + void OnPortDestroyed(Port* port); + void OnConnectionCreated(Port* port, Connection* conn); + void OnConnectionStateChange(Connection* conn); + void OnShake(); + + BasicPortAllocator *allocator_; + std::string name_; + Thread* network_thread_; + Thread* config_thread_; + bool configuration_done_; + bool allocation_started_; + bool running_; // set when StartGetAllPorts is called + std::vector configs_; + std::vector sequences_; + SocketAddress *stun_address_; + SocketAddress *relay_address_; + + struct PortData { + Port * port; + AllocationSequence * sequence; + bool ready; + + bool operator==(Port * rhs) const { return (port == rhs); } + }; + std::vector ports_; + + friend class AllocationSequence; +}; + +// Records configuration information useful in creating ports. +struct PortConfiguration: public MessageData { + SocketAddress stun_address; + std::string username; + std::string password; + std::string magic_cookie; + + typedef std::vector PortList; + struct RelayServer { + PortList ports; + float pref_modifier; // added to the protocol modifier to get the + // preference for this particular server + }; + + typedef std::vector RelayList; + RelayList relays; + + PortConfiguration(const SocketAddress& stun_address, + const std::string& username, + const std::string& password, + const std::string& magic_cookie); + + // Adds another relay server, with the given ports and modifier, to the list. + void AddRelay(const PortList& ports, float pref_modifier); + + // Determines whether the given relay server supports the given protocol. + static bool SupportsProtocol(const PortConfiguration::RelayServer& relay, + ProtocolType type); +}; + +} // namespace cricket + +#endif // _BASICPORTALLOCATOR_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/sessionclient.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/sessionclient.cc new file mode 100644 index 00000000..09b38a52 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/sessionclient.cc @@ -0,0 +1,545 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/p2p/client/sessionclient.h" +#include "talk/p2p/base/helpers.h" +#include "talk/base/logging.h" +#include "talk/xmllite/qname.h" +#include "talk/xmpp/constants.h" +#include "talk/xmllite/xmlprinter.h" +#include +#undef SetPort + +namespace { + +// We only allow usernames to be this many characters or fewer. +const size_t kMaxUsernameSize = 16; + +} + +namespace cricket { + +#if 0 +>>>>>> + + + + ... + + + + +<<<<<< + + +>>>>>> + + + + + + +> + +<<<<<< + + +#endif + +const std::string NS_GOOGLESESSION("http://www.google.com/session"); +const buzz::QName QN_GOOGLESESSION_SESSION(true, NS_GOOGLESESSION, "session"); +const buzz::QName QN_GOOGLESESSION_CANDIDATE(true, NS_GOOGLESESSION, "candidate"); +const buzz::QName QN_GOOGLESESSION_TARGET(true, NS_GOOGLESESSION, "target"); +const buzz::QName QN_GOOGLESESSION_COOKIE(true, NS_GOOGLESESSION, "cookie"); +const buzz::QName QN_GOOGLESESSION_REGARDING(true, NS_GOOGLESESSION, "regarding"); + +const buzz::QName QN_TYPE(true, buzz::STR_EMPTY, "type"); +const buzz::QName QN_ID(true, buzz::STR_EMPTY, "id"); +const buzz::QName QN_INITIATOR(true, buzz::STR_EMPTY, "initiator"); +const buzz::QName QN_NAME(true, buzz::STR_EMPTY, "name"); +const buzz::QName QN_PORT(true, buzz::STR_EMPTY, "port"); +const buzz::QName QN_NETWORK(true, buzz::STR_EMPTY, "network"); +const buzz::QName QN_GENERATION(true, buzz::STR_EMPTY, "generation"); +const buzz::QName QN_ADDRESS(true, buzz::STR_EMPTY, "address"); +const buzz::QName QN_USERNAME(true, buzz::STR_EMPTY, "username"); +const buzz::QName QN_PASSWORD(true, buzz::STR_EMPTY, "password"); +const buzz::QName QN_PREFERENCE(true, buzz::STR_EMPTY, "preference"); +const buzz::QName QN_PROTOCOL(true, buzz::STR_EMPTY, "protocol"); +const buzz::QName QN_KEY(true, buzz::STR_EMPTY, "key"); + +class XmlCookie: public SessionMessage::Cookie { +public: + XmlCookie(const buzz::XmlElement* elem) + : elem_(new buzz::XmlElement(*elem)) { + } + + virtual ~XmlCookie() { + delete elem_; + } + + const buzz::XmlElement* elem() const { return elem_; } + + virtual Cookie* Copy() { + return new XmlCookie(elem_); + } + +private: + buzz::XmlElement* elem_; +}; + +SessionClient::SessionClient(SessionManager *session_manager) { + session_manager_ = session_manager; + session_manager_->SignalSessionCreate.connect(this, &SessionClient::OnSessionCreateSlot); + session_manager_->SignalSessionDestroy.connect(this, &SessionClient::OnSessionDestroySlot); +} + +SessionClient::~SessionClient() { +} + +void SessionClient::OnSessionCreateSlot(Session *session, bool received_initiate) { + // Does this session belong to this session client? + if (session->name() == GetSessionDescriptionName()) { + session->SignalOutgoingMessage.connect(this, &SessionClient::OnOutgoingMessage); + OnSessionCreate(session, received_initiate); + } +} + +void SessionClient::OnSessionDestroySlot(Session *session) { + if (session->name() == GetSessionDescriptionName()) { + session->SignalOutgoingMessage.disconnect(this); + OnSessionDestroy(session); + } +} + +bool SessionClient::IsClientStanza(const buzz::XmlElement *stanza) { + // Is it a IQ set stanza? + if (stanza->Name() != buzz::QN_IQ) + return false; + if (stanza->Attr(buzz::QN_TYPE) != buzz::STR_SET) + return false; + + // Make sure it has the right child element + const buzz::XmlElement* element + = stanza->FirstNamed(QN_GOOGLESESSION_SESSION); + if (element == NULL) + return false; + + // Is it one of the allowed types? + std::string type; + if (element->HasAttr(QN_TYPE)) { + type = element->Attr(QN_TYPE); + if (type != "initiate" && type != "accept" && type != "modify" && + type != "candidates" && type != "reject" && type != "redirect" && + type != "terminate") { + return false; + } + } + + // Does this client own the session description namespace? + buzz::QName qn_session_desc(GetSessionDescriptionName(), "description"); + const buzz::XmlElement* description = element->FirstNamed(qn_session_desc); + if (type == "initiate" || type == "accept" || type == "modify") { + if (description == NULL) + return false; + } else { + if (description != NULL) + return false; + } + + // It's good + return true; +} + +void SessionClient::OnIncomingStanza(const buzz::XmlElement *stanza) { + SessionMessage message; + if (!ParseIncomingMessage(stanza, message)) + return; + + session_manager_->OnIncomingMessage(message); +} + +void SessionClient::OnFailedSend(const buzz::XmlElement *original_stanza, + const buzz::XmlElement *failure_stanza) { + SessionMessage message; + if (!ParseIncomingMessage(original_stanza, message)) + return; + + // Note the from/to represents the *original* stanza and not the from/to + // on any return path + session_manager_->OnIncomingError(message); +} + +bool SessionClient::ParseIncomingMessage(const buzz::XmlElement *stanza, + SessionMessage& message) { + // Parse stanza into SessionMessage + const buzz::XmlElement* element + = stanza->FirstNamed(QN_GOOGLESESSION_SESSION); + + std::string type = element->Attr(QN_TYPE); + if (type == "initiate" || type == "accept" || type == "modify") { + ParseInitiateAcceptModify(stanza, message); + } else if (type == "candidates") { + ParseCandidates(stanza, message); + } else if (type == "reject" || type == "terminate") { + ParseRejectTerminate(stanza, message); + } else if (type == "redirect") { + ParseRedirect(stanza, message); + } else { + return false; + } + + return true; +} + +void SessionClient::ParseHeader(const buzz::XmlElement *stanza, SessionMessage &message) { + if (stanza->HasAttr(buzz::QN_FROM)) + message.set_from(stanza->Attr(buzz::QN_FROM)); + if (stanza->HasAttr(buzz::QN_TO)) + message.set_to(stanza->Attr(buzz::QN_TO)); + + const buzz::XmlElement *element + = stanza->FirstNamed(QN_GOOGLESESSION_SESSION); + if (element->HasAttr(QN_ID)) + message.session_id().set_id_str(element->Attr(QN_ID)); + + if (element->HasAttr(QN_INITIATOR)) + message.session_id().set_initiator(element->Attr(QN_INITIATOR)); + + std::string type = element->Attr(QN_TYPE); + if (type == "initiate") { + message.set_type(SessionMessage::TYPE_INITIATE); + } else if (type == "accept") { + message.set_type(SessionMessage::TYPE_ACCEPT); + } else if (type == "modify") { + message.set_type(SessionMessage::TYPE_MODIFY); + } else if (type == "candidates") { + message.set_type(SessionMessage::TYPE_CANDIDATES); + } else if (type == "reject") { + message.set_type(SessionMessage::TYPE_REJECT); + } else if (type == "redirect") { + message.set_type(SessionMessage::TYPE_REDIRECT); + } else if (type == "terminate") { + message.set_type(SessionMessage::TYPE_TERMINATE); + } else { + assert(false); + } +} + +void SessionClient::ParseInitiateAcceptModify(const buzz::XmlElement *stanza, SessionMessage &message) { + // Pull the standard header pieces out + ParseHeader(stanza, message); + + // Parse session description + const buzz::XmlElement *session + = stanza->FirstNamed(QN_GOOGLESESSION_SESSION); + buzz::QName qn_session_desc(GetSessionDescriptionName(), "description"); + const buzz::XmlElement* desc_elem = session->FirstNamed(qn_session_desc); + const SessionDescription *description = NULL; + if (desc_elem) + description = CreateSessionDescription(desc_elem); + message.set_name(GetSessionDescriptionName()); + message.set_description(description); +} + +void SessionClient::ParseCandidates(const buzz::XmlElement *stanza, SessionMessage &message) { + // Pull the standard header pieces out + ParseHeader(stanza, message); + + // Parse candidates and session description + std::vector candidates; + const buzz::XmlElement *element + = stanza->FirstNamed(QN_GOOGLESESSION_SESSION); + const buzz::XmlElement *child = element->FirstElement(); + while (child != NULL) { + if (child->Name() == QN_GOOGLESESSION_CANDIDATE) { + Candidate candidate; + if (ParseCandidate(child, &candidate)) + candidates.push_back(candidate); + } + child = child->NextElement(); + } + message.set_name(GetSessionDescriptionName()); + message.set_candidates(candidates); +} + +void SessionClient::ParseRejectTerminate(const buzz::XmlElement *stanza, SessionMessage &message) { + // Reject and terminate are very simple + ParseHeader(stanza, message); +} + +bool SessionClient::ParseCandidate(const buzz::XmlElement *child, + Candidate* candidate) { + // Check for all of the required attributes. + if (!child->HasAttr(QN_NAME) || + !child->HasAttr(QN_ADDRESS) || + !child->HasAttr(QN_PORT) || + !child->HasAttr(QN_USERNAME) || + !child->HasAttr(QN_PREFERENCE) || + !child->HasAttr(QN_PROTOCOL) || + !child->HasAttr(QN_GENERATION)) { + LOG(LERROR) << "Candidate missing required attribute"; + return false; + } + + SocketAddress address; + address.SetIP(child->Attr(QN_ADDRESS)); + std::istringstream ist(child->Attr(QN_PORT)); + int port; + ist >> port; + address.SetPort(port); + + if (address.IsAny()) { + LOG(LERROR) << "Candidate has address 0"; + return false; + } + + // Always disallow addresses that refer to the local host. + if (address.IsLocalIP()) { + LOG(LERROR) << "Candidate has local IP address"; + return false; + } + + // Disallow all ports below 1024, except for 80 and 443 on public addresses. + if (port < 1024) { + if ((port != 80) && (port != 443)) { + LOG(LERROR) << "Candidate has port below 1024, not 80 or 443"; + return false; + } + if (address.IsPrivateIP()) { + LOG(LERROR) << "Candidate has port of 80 or 443 with private IP address"; + return false; + } + } + + candidate->set_name(child->Attr(QN_NAME)); + candidate->set_address(address); + candidate->set_username(child->Attr(QN_USERNAME)); + candidate->set_preference_str(child->Attr(QN_PREFERENCE)); + candidate->set_protocol(child->Attr(QN_PROTOCOL)); + candidate->set_generation_str(child->Attr(QN_GENERATION)); + + // Check that the username is not too long and does not use any bad chars. + if (candidate->username().size() > kMaxUsernameSize) { + LOG(LERROR) << "Candidate username is too long"; + return false; + } + if (!IsBase64Encoded(candidate->username())) { + LOG(LERROR) << "Candidate username has non-base64 encoded characters"; + return false; + } + + // Look for the non-required attributes. + if (child->HasAttr(QN_PASSWORD)) + candidate->set_password(child->Attr(QN_PASSWORD)); + if (child->HasAttr(QN_TYPE)) + candidate->set_type(child->Attr(QN_TYPE)); + if (child->HasAttr(QN_NETWORK)) + candidate->set_network_name(child->Attr(QN_NETWORK)); + + return true; +} + +void SessionClient::ParseRedirect(const buzz::XmlElement *stanza, SessionMessage &message) { + // Pull the standard header pieces out + ParseHeader(stanza, message); + const buzz::XmlElement *session = stanza->FirstNamed(QN_GOOGLESESSION_SESSION); + + // Parse the target and cookie. + + const buzz::XmlElement* target = session->FirstNamed(QN_GOOGLESESSION_TARGET); + if (target) + message.set_redirect_target(target->Attr(QN_NAME)); + + const buzz::XmlElement* cookie = session->FirstNamed(QN_GOOGLESESSION_COOKIE); + if (cookie) + message.set_redirect_cookie(new XmlCookie(cookie)); +} + +void SessionClient::OnOutgoingMessage(Session *session, const SessionMessage &message) { + // Translate the message into an XMPP stanza + + buzz::XmlElement *result = NULL; + switch (message.type()) { + case SessionMessage::TYPE_INITIATE: + case SessionMessage::TYPE_ACCEPT: + case SessionMessage::TYPE_MODIFY: + result = TranslateInitiateAcceptModify(message); + break; + + case SessionMessage::TYPE_CANDIDATES: + result = TranslateCandidates(message); + break; + + case SessionMessage::TYPE_REJECT: + case SessionMessage::TYPE_TERMINATE: + result = TranslateRejectTerminate(message); + break; + + case SessionMessage::TYPE_REDIRECT: + result = TranslateRedirect(message); + break; + } + + // Send the stanza. Note that SessionClient is passing on ownership + // of result. + if (result != NULL) { + SignalSendStanza(this, result); + } +} + +buzz::XmlElement *SessionClient::TranslateHeader(const SessionMessage &message) { + buzz::XmlElement *result = new buzz::XmlElement(buzz::QN_IQ); + result->AddAttr(buzz::QN_TO, message.to()); + result->AddAttr(buzz::QN_TYPE, buzz::STR_SET); + buzz::XmlElement *session = new buzz::XmlElement(QN_GOOGLESESSION_SESSION, true); + result->AddElement(session); + switch (message.type()) { + case SessionMessage::TYPE_INITIATE: + session->AddAttr(QN_TYPE, "initiate"); + break; + case SessionMessage::TYPE_ACCEPT: + session->AddAttr(QN_TYPE, "accept"); + break; + case SessionMessage::TYPE_MODIFY: + session->AddAttr(QN_TYPE, "modify"); + break; + case SessionMessage::TYPE_CANDIDATES: + session->AddAttr(QN_TYPE, "candidates"); + break; + case SessionMessage::TYPE_REJECT: + session->AddAttr(QN_TYPE, "reject"); + break; + case SessionMessage::TYPE_REDIRECT: + session->AddAttr(QN_TYPE, "redirect"); + break; + case SessionMessage::TYPE_TERMINATE: + session->AddAttr(QN_TYPE, "terminate"); + break; + } + session->AddAttr(QN_ID, message.session_id().id_str()); + session->AddAttr(QN_INITIATOR, message.session_id().initiator()); + return result; +} + +buzz::XmlElement *SessionClient::TranslateCandidate(const Candidate &candidate) { + buzz::XmlElement *result = new buzz::XmlElement(QN_GOOGLESESSION_CANDIDATE); + result->AddAttr(QN_NAME, candidate.name()); + result->AddAttr(QN_ADDRESS, candidate.address().IPAsString()); + result->AddAttr(QN_PORT, candidate.address().PortAsString()); + result->AddAttr(QN_USERNAME, candidate.username()); + result->AddAttr(QN_PASSWORD, candidate.password()); + result->AddAttr(QN_PREFERENCE, candidate.preference_str()); + result->AddAttr(QN_PROTOCOL, candidate.protocol()); + result->AddAttr(QN_TYPE, candidate.type()); + result->AddAttr(QN_NETWORK, candidate.network_name()); + result->AddAttr(QN_GENERATION, candidate.generation_str()); + return result; +} + +buzz::XmlElement *SessionClient::TranslateInitiateAcceptModify(const SessionMessage &message) { + // Header info common to all message types + buzz::XmlElement *result = TranslateHeader(message); + buzz::XmlElement *session = result->FirstNamed(QN_GOOGLESESSION_SESSION); + + // Candidates + assert(message.candidates().size() == 0); + + // Session Description + buzz::XmlElement* description = TranslateSessionDescription(message.description()); + assert(description->Name().LocalPart() == "description"); + assert(description->Name().Namespace() == GetSessionDescriptionName()); + session->AddElement(description); + + if (message.redirect_cookie() != NULL) { + const buzz::XmlElement* cookie = + reinterpret_cast(message.redirect_cookie())->elem(); + for (const buzz::XmlElement* elem = cookie->FirstElement(); elem; elem = elem->NextElement()) + session->AddElement(new buzz::XmlElement(*elem)); + } + + return result; +} + +buzz::XmlElement *SessionClient::TranslateCandidates(const SessionMessage &message) { + // Header info common to all message types + buzz::XmlElement *result = TranslateHeader(message); + buzz::XmlElement *session = result->FirstNamed(QN_GOOGLESESSION_SESSION); + + // Candidates + std::vector::const_iterator it; + for (it = message.candidates().begin(); it != message.candidates().end(); it++) + session->AddElement(TranslateCandidate(*it)); + + return result; +} + +buzz::XmlElement *SessionClient::TranslateRejectTerminate(const SessionMessage &message) { + // These messages are simple, and only have a header + return TranslateHeader(message); +} + +buzz::XmlElement *SessionClient::TranslateRedirect(const SessionMessage &message) { + // Header info common to all message types + buzz::XmlElement *result = TranslateHeader(message); + buzz::XmlElement *session = result->FirstNamed(QN_GOOGLESESSION_SESSION); + + assert(message.candidates().size() == 0); + assert(message.description() == NULL); + + assert(message.redirect_target().size() > 0); + buzz::XmlElement* target = new buzz::XmlElement(QN_GOOGLESESSION_TARGET); + target->AddAttr(QN_NAME, message.redirect_target()); + session->AddElement(target); + + buzz::XmlElement* cookie = new buzz::XmlElement(QN_GOOGLESESSION_COOKIE); + session->AddElement(cookie); + + // If the message does not have a redirect cookie, then this is a redirect + // initiated by us. We will automatically add a regarding cookie. + if (message.redirect_cookie() == NULL) { + buzz::XmlElement* regarding = new buzz::XmlElement(QN_GOOGLESESSION_REGARDING); + regarding->AddAttr(QN_NAME, GetJid().BareJid().Str()); + cookie->AddElement(regarding); + } else { + const buzz::XmlElement* cookie_elem = + reinterpret_cast(message.redirect_cookie())->elem(); + const buzz::XmlElement* elem; + for (elem = cookie_elem->FirstElement(); elem; elem = elem->NextElement()) + cookie->AddElement(new buzz::XmlElement(*elem)); + } + + return result; +} + +SessionManager *SessionClient::session_manager() { + return session_manager_; +} + +} // namespace cricket diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/sessionclient.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/sessionclient.h new file mode 100644 index 00000000..69a18422 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/sessionclient.h @@ -0,0 +1,104 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _SESSIONCLIENT_H_ +#define _SESSIONCLIENT_H_ + +#include "talk/p2p/base/sessiondescription.h" +#include "talk/p2p/base/sessionmessage.h" +#include "talk/p2p/base/sessionmanager.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmpp/jid.h" +namespace cricket { + +// Generic XMPP session client. This class knows how to translate +// a SessionMessage to and from XMPP stanzas. The SessionDescription +// is a custom description implemented by the client. + +// This class knows how to talk to the session manager, however the +// session manager doesn't have knowledge of a particular SessionClient. + +class SessionClient : public sigslot::has_slots<> { +public: + SessionClient(SessionManager *psm); + virtual ~SessionClient(); + + // Call this method to determine if a stanza is for this session client + bool IsClientStanza(const buzz::XmlElement *stanza); + + // Call this method to deliver a stanza to this session client + void OnIncomingStanza(const buzz::XmlElement *stanza); + + // Call this whenever an error is recieved in response to an outgoing + // session IQ. Include the original stanza and any failure stanza. If + // the failure is due to a time out, the failure_stanza should be NULL + void OnFailedSend(const buzz::XmlElement* original_stanza, + const buzz::XmlElement* failure_stanza); + + SessionManager *session_manager(); + + // Implement this method for stanza sending + sigslot::signal2 SignalSendStanza; + +protected: + // Override these to know when sessions belonging to this client create/destroy + + virtual void OnSessionCreate(Session * /*session*/, bool /*received_initiate*/) {} + virtual void OnSessionDestroy(Session * /*session*/) {} + + // Implement these methods for a custom session description + virtual const SessionDescription *CreateSessionDescription(const buzz::XmlElement *element) = 0; + virtual buzz::XmlElement *TranslateSessionDescription(const SessionDescription *description) = 0; + virtual const std::string &GetSessionDescriptionName() = 0; + virtual const buzz::Jid &GetJid() const = 0; + + SessionManager *session_manager_; + +private: + void OnSessionCreateSlot(Session *session, bool received_initiate); + void OnSessionDestroySlot(Session *session); + void OnOutgoingMessage(Session *session, const SessionMessage &message); + void ParseHeader(const buzz::XmlElement *stanza, SessionMessage &message); + bool ParseCandidate(const buzz::XmlElement *child, Candidate* candidate); + bool ParseIncomingMessage(const buzz::XmlElement *stanza, + SessionMessage& message); + void ParseInitiateAcceptModify(const buzz::XmlElement *stanza, SessionMessage &message); + void ParseCandidates(const buzz::XmlElement *stanza, SessionMessage &message); + void ParseRejectTerminate(const buzz::XmlElement *stanza, SessionMessage &message); + void ParseRedirect(const buzz::XmlElement *stanza, SessionMessage &message); + buzz::XmlElement *TranslateHeader(const SessionMessage &message); + buzz::XmlElement *TranslateCandidate(const Candidate &candidate); + buzz::XmlElement *TranslateInitiateAcceptModify(const SessionMessage &message); + buzz::XmlElement *TranslateCandidates(const SessionMessage &message); + buzz::XmlElement *TranslateRejectTerminate(const SessionMessage &message); + buzz::XmlElement *TranslateRedirect(const SessionMessage &message); + +}; + +} // namespace cricket + +#endif // _SESSIONCLIENT_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/socketmonitor.cc b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/socketmonitor.cc new file mode 100644 index 00000000..dd9fa67c --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/socketmonitor.cc @@ -0,0 +1,149 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "socketmonitor.h" +#include + +namespace cricket { + +const uint32 MSG_MONITOR_POLL = 1; +const uint32 MSG_MONITOR_START = 2; +const uint32 MSG_MONITOR_STOP = 3; +const uint32 MSG_MONITOR_SIGNAL = 4; + +SocketMonitor::SocketMonitor(P2PSocket *socket, Thread *monitor_thread) { + socket_ = socket; + monitoring_thread_ = monitor_thread; + monitoring_ = false; +} + +SocketMonitor::~SocketMonitor() { + socket_->thread()->Clear(this); + monitoring_thread_->Clear(this); +} + +void SocketMonitor::Start(int milliseconds) { + rate_ = milliseconds; + if (rate_ < 250) + rate_ = 250; + socket_->thread()->Post(this, MSG_MONITOR_START); +} + +void SocketMonitor::Stop() { + socket_->thread()->Post(this, MSG_MONITOR_STOP); +} + +void SocketMonitor::OnMessage(Message *message) { + CritScope cs(&crit_); + + switch (message->message_id) { + case MSG_MONITOR_START: + assert(Thread::Current() == socket_->thread()); + if (!monitoring_) { + monitoring_ = true; + socket_->SignalConnectionMonitor.connect(this, &SocketMonitor::OnConnectionMonitor); + PollSocket(true); + } + break; + + case MSG_MONITOR_STOP: + assert(Thread::Current() == socket_->thread()); + if (monitoring_) { + monitoring_ = false; + socket_->SignalConnectionMonitor.disconnect(this); + socket_->thread()->Clear(this); + } + break; + + case MSG_MONITOR_POLL: + assert(Thread::Current() == socket_->thread()); + PollSocket(true); + break; + + case MSG_MONITOR_SIGNAL: + { + assert(Thread::Current() == monitoring_thread_); + std::vector infos = connection_infos_; + crit_.Leave(); + SignalUpdate(this, infos); + crit_.Enter(); + } + break; + } +} + +void SocketMonitor::OnConnectionMonitor(P2PSocket *socket) { + CritScope cs(&crit_); + if (monitoring_) + PollSocket(false); +} + +void SocketMonitor::PollSocket(bool poll) { + CritScope cs(&crit_); + assert(Thread::Current() == socket_->thread()); + + // Gather connection infos + + connection_infos_.clear(); + const std::vector &connections = socket_->connections(); + std::vector::const_iterator it; + for (it = connections.begin(); it != connections.end(); it++) { + Connection *connection = *it; + ConnectionInfo info; + info.best_connection = socket_->best_connection() == connection; + info.readable = connection->read_state() == Connection::STATE_READABLE; + info.writable = connection->write_state() == Connection::STATE_WRITABLE; + info.timeout = connection->write_state() == Connection::STATE_WRITE_TIMEOUT; + info.new_connection = false; // connection->new_connection(); + info.rtt = connection->rtt(); + info.sent_total_bytes = connection->sent_total_bytes(); + info.sent_bytes_second = connection->sent_bytes_second(); + info.recv_total_bytes = connection->recv_total_bytes(); + info.recv_bytes_second = connection->recv_bytes_second(); + info.local_candidate = connection->local_candidate(); + info.remote_candidate = connection->remote_candidate(); + info.est_quality = connection->port()->network()->quality(); + info.key = reinterpret_cast(connection); + connection_infos_.push_back(info); + } + + // Signal the monitoring thread, start another poll timer + + monitoring_thread_->Post(this, MSG_MONITOR_SIGNAL); + if (poll) + socket_->thread()->PostDelayed(rate_, this, MSG_MONITOR_POLL); +} + +P2PSocket *SocketMonitor::socket() { + return socket_; +} + +Thread *SocketMonitor::monitor_thread() { + return monitoring_thread_; +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/socketmonitor.h b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/socketmonitor.h new file mode 100644 index 00000000..549e90b6 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/p2p/client/socketmonitor.h @@ -0,0 +1,85 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _SOCKETMONITOR_H_ +#define _SOCKETMONITOR_H_ + +#include "talk/base/thread.h" +#include "talk/base/sigslot.h" +#include "talk/base/criticalsection.h" +#include "talk/p2p/base/p2psocket.h" +#include "talk/p2p/base/port.h" +#include + +namespace cricket { + +struct ConnectionInfo { + bool best_connection; + bool writable; + bool readable; + bool timeout; + bool new_connection; + size_t rtt; + size_t sent_total_bytes; + size_t sent_bytes_second; + size_t recv_total_bytes; + size_t recv_bytes_second; + Candidate local_candidate; + Candidate remote_candidate; + double est_quality; + void *key; +}; + +class SocketMonitor : public MessageHandler, public sigslot::has_slots<> { +public: + SocketMonitor(P2PSocket *socket, Thread *monitor_thread); + ~SocketMonitor(); + + void Start(int cms); + void Stop(); + + P2PSocket *socket(); + Thread *monitor_thread(); + + sigslot::signal2 &> SignalUpdate; + +protected: + void OnMessage(Message *message); + void OnConnectionMonitor(P2PSocket *socket); + void PollSocket(bool poll); + + std::vector connection_infos_; + P2PSocket *socket_; + Thread *monitoring_thread_; + CriticalSection crit_; + uint32 rate_; + bool monitoring_; +}; + +} + +#endif // _SOCKETMONITOR_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/session/Makefile.am new file mode 100644 index 00000000..6cfc5b24 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/Makefile.am @@ -0,0 +1,3 @@ +noinst_HEADERS = receiver.h sessionsendtask.h +SUBDIRS = phone + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/Makefile.am new file mode 100644 index 00000000..b2acbf81 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/Makefile.am @@ -0,0 +1,18 @@ +libcricketsessionphone_la_SOURCES = audiomonitor.cc \ + channelmanager.cc \ + voicechannel.cc \ + call.cc \ + phonesessionclient.cc \ + linphonemediaengine.cc + +noinst_HEADERS = audiomonitor.h \ + channelmanager.h \ + linphonemediaengine.h \ + mediaengine.h \ + phonesessionclient.h \ + voicechannel.h \ + call.h \ + mediachannel.h + +AM_CPPFLAGS = -DPOSIX $(ORTP_CFLAGS) $(ILBC_CFLAGS) -I$(srcdir)/../../../talk/third_party/mediastreamer -I$(srcdir)/../../.. $(GLIB_CFLAGS) $(SPEEX_CFLAGS) +noinst_LTLIBRARIES = libcricketsessionphone.la diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/audiomonitor.cc b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/audiomonitor.cc new file mode 100644 index 00000000..c1b63d1b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/audiomonitor.cc @@ -0,0 +1,119 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/session/phone/audiomonitor.h" +#include "talk/session/phone/voicechannel.h" +#include + +namespace cricket { + +const uint32 MSG_MONITOR_POLL = 1; +const uint32 MSG_MONITOR_START = 2; +const uint32 MSG_MONITOR_STOP = 3; +const uint32 MSG_MONITOR_SIGNAL = 4; + +AudioMonitor::AudioMonitor(VoiceChannel *voice_channel, Thread *monitor_thread) { + voice_channel_ = voice_channel; + monitoring_thread_ = monitor_thread; + monitoring_ = false; +} + +AudioMonitor::~AudioMonitor() { + voice_channel_->worker_thread()->Clear(this); + monitoring_thread_->Clear(this); +} + +void AudioMonitor::Start(int milliseconds) { + rate_ = milliseconds; + if (rate_ < 100) + rate_ = 100; + voice_channel_->worker_thread()->Post(this, MSG_MONITOR_START); +} + +void AudioMonitor::Stop() { + voice_channel_->worker_thread()->Post(this, MSG_MONITOR_STOP); +} + +void AudioMonitor::OnMessage(Message *message) { + CritScope cs(&crit_); + + switch (message->message_id) { + case MSG_MONITOR_START: + assert(Thread::Current() == voice_channel_->worker_thread()); + if (!monitoring_) { + monitoring_ = true; + PollVoiceChannel(); + } + break; + + case MSG_MONITOR_STOP: + assert(Thread::Current() == voice_channel_->worker_thread()); + if (monitoring_) { + monitoring_ = false; + voice_channel_->worker_thread()->Clear(this); + } + break; + + case MSG_MONITOR_POLL: + assert(Thread::Current() == voice_channel_->worker_thread()); + PollVoiceChannel(); + break; + + case MSG_MONITOR_SIGNAL: + { + assert(Thread::Current() == monitoring_thread_); + AudioInfo info = audio_info_; + crit_.Leave(); + SignalUpdate(this, audio_info_); + crit_.Enter(); + } + break; + } +} + +void AudioMonitor::PollVoiceChannel() { + CritScope cs(&crit_); + assert(Thread::Current() == voice_channel_->worker_thread()); + + // Gather connection infos + audio_info_.input_level = voice_channel_->GetInputLevel_w(); + audio_info_.output_level = voice_channel_->GetOutputLevel_w(); + + // Signal the monitoring thread, start another poll timer + monitoring_thread_->Post(this, MSG_MONITOR_SIGNAL); + voice_channel_->worker_thread()->PostDelayed(rate_, this, MSG_MONITOR_POLL); +} + +VoiceChannel *AudioMonitor::voice_channel() { + return voice_channel_; +} + +Thread *AudioMonitor::monitor_thread() { + return monitoring_thread_; +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/audiomonitor.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/audiomonitor.h new file mode 100644 index 00000000..96b95bd7 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/audiomonitor.h @@ -0,0 +1,73 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _CRICKET_PHONE_AUDIOMONITOR_H_ +#define _CRICKET_PHONE_AUDIOMONITOR_H_ + +#include "talk/base/thread.h" +#include "talk/base/sigslot.h" +#include "talk/p2p/base/port.h" +#include + +namespace cricket { + +class VoiceChannel; + + +struct AudioInfo { + int input_level; + int output_level; +}; + +class AudioMonitor : public MessageHandler, public sigslot::has_slots<> { +public: + AudioMonitor(VoiceChannel* voice_channel, Thread *monitor_thread); + ~AudioMonitor(); + + void Start(int cms); + void Stop(); + + VoiceChannel* voice_channel(); + Thread *monitor_thread(); + + sigslot::signal2 SignalUpdate; + +protected: + void OnMessage(Message *message); + void PollVoiceChannel(); + + AudioInfo audio_info_; + VoiceChannel* voice_channel_; + Thread* monitoring_thread_; + CriticalSection crit_; + uint32 rate_; + bool monitoring_; +}; + +} + +#endif // _CRICKET_PHONE_AUDIOMONITOR_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/call.cc b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/call.cc new file mode 100644 index 00000000..31b12e92 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/call.cc @@ -0,0 +1,258 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/base/thread.h" +#include "talk/p2p/base/helpers.h" +#include "talk/session/phone/call.h" + +namespace cricket { + +const uint32 MSG_CHECKAUTODESTROY = 1; + +Call::Call(PhoneSessionClient *session_client) : muted_(false) { + session_client_ = session_client; + id_ = CreateRandomId(); +} + +Call::~Call() { + while (sessions_.begin() != sessions_.end()) { + Session *session = sessions_[0]; + RemoveSession(session); + session_client_->session_manager()->DestroySession(session); + } + Thread::Current()->Clear(this); +} + +Session *Call::InitiateSession(const buzz::Jid &jid) { + Session *session = session_client_->CreateSession(this); + AddSession(session); + session->Initiate(jid.Str(), session_client_->CreateOfferSessionDescription()); + return session; +} + +void Call::AcceptSession(Session *session) { + std::vector::iterator it; + it = std::find(sessions_.begin(), sessions_.end(), session); + assert(it != sessions_.end()); + if (it != sessions_.end()) + session->Accept(session_client_->CreateAcceptSessionDescription(session->remote_description())); +} + +void Call::RedirectSession(Session *session, const buzz::Jid &to) { + std::vector::iterator it; + it = std::find(sessions_.begin(), sessions_.end(), session); + assert(it != sessions_.end()); + if (it != sessions_.end()) + session->Redirect(to.Str()); +} + +void Call::RejectSession(Session *session) { + std::vector::iterator it; + it = std::find(sessions_.begin(), sessions_.end(), session); + assert(it != sessions_.end()); + if (it != sessions_.end()) + session->Reject(); +} + +void Call::TerminateSession(Session *session) { + assert(std::find(sessions_.begin(), sessions_.end(), session) != sessions_.end()); + std::vector::iterator it; + it = std::find(sessions_.begin(), sessions_.end(), session); + if (it != sessions_.end()) + (*it)->Terminate(); +} + +void Call::Terminate() { + // There may be more than one session to terminate + std::vector::iterator it = sessions_.begin(); + for (it = sessions_.begin(); it != sessions_.end(); it++) + TerminateSession(*it); +} + +void Call::OnMessage(Message *message) { + switch (message->message_id) { + case MSG_CHECKAUTODESTROY: + // If no more sessions for this call, delete it + if (sessions_.size() == 0) + session_client_->DestroyCall(this); + break; + } +} + +const std::vector &Call::sessions() { + return sessions_; +} + +void Call::AddSession(Session *session) { + // Add session to list, create voice channel for this session + sessions_.push_back(session); + session->SignalState.connect(this, &Call::OnSessionState); + session->SignalError.connect(this, &Call::OnSessionError); + + VoiceChannel *channel = session_client_->channel_manager()->CreateVoiceChannel(session); + channel_map_[session->id()] = channel; + + // If this call has the focus, enable this channel + if (session_client_->GetFocus() == this) + channel->Enable(true); + + // Signal client + SignalAddSession(this, session); +} + +void Call::RemoveSession(Session *session) { + // Remove session from list + std::vector::iterator it_session; + it_session = std::find(sessions_.begin(), sessions_.end(), session); + if (it_session == sessions_.end()) + return; + sessions_.erase(it_session); + + // Destroy session channel + std::map::iterator it_channel; + it_channel = channel_map_.find(session->id()); + if (it_channel != channel_map_.end()) { + VoiceChannel *channel = it_channel->second; + channel_map_.erase(it_channel); + session_client_->channel_manager()->DestroyVoiceChannel(channel); + } + + // Signal client + SignalRemoveSession(this, session); + + // The call auto destroys when the lass session is removed + Thread::Current()->Post(this, MSG_CHECKAUTODESTROY); +} + +VoiceChannel* Call::GetChannel(Session* session) { + std::map::iterator it = channel_map_.find(session->id()); + assert(it != channel_map_.end()); + return it->second; +} + +void Call::EnableChannels(bool enable) { + std::vector::iterator it; + for (it = sessions_.begin(); it != sessions_.end(); it++) { + VoiceChannel *channel = channel_map_[(*it)->id()]; + if (channel != NULL) + channel->Enable(enable); + } +} + +void Call::Mute(bool mute) { + muted_ = mute; + std::vector::iterator it; + for (it = sessions_.begin(); it != sessions_.end(); it++) { + VoiceChannel *channel = channel_map_[(*it)->id()]; + if (channel != NULL) + channel->Mute(mute); + } +} + +void Call::Join(Call *call, bool enable) { + while (call->sessions_.size() != 0) { + // Move session + Session *session = call->sessions_[0]; + call->sessions_.erase(call->sessions_.begin()); + sessions_.push_back(session); + session->SignalState.connect(this, &Call::OnSessionState); + session->SignalError.connect(this, &Call::OnSessionError); + + // Move channel + std::map::iterator it_channel; + it_channel = call->channel_map_.find(session->id()); + if (it_channel != call->channel_map_.end()) { + VoiceChannel *channel = (*it_channel).second; + call->channel_map_.erase(it_channel); + channel_map_[session->id()] = channel; + channel->Enable(enable); + } + } +} + +void Call::StartConnectionMonitor(Session *session, int cms) { + std::map::iterator it_channel; + it_channel = channel_map_.find(session->id()); + if (it_channel != channel_map_.end()) { + VoiceChannel *channel = (*it_channel).second; + channel->SignalConnectionMonitor.connect(this, &Call::OnConnectionMonitor); + channel->StartConnectionMonitor(cms); + } +} + +void Call::StopConnectionMonitor(Session *session) { + std::map::iterator it_channel; + it_channel = channel_map_.find(session->id()); + if (it_channel != channel_map_.end()) { + VoiceChannel *channel = (*it_channel).second; + channel->StopConnectionMonitor(); + channel->SignalConnectionMonitor.disconnect(this); + } +} + +void Call::StartAudioMonitor(Session *session, int cms) { + std::map::iterator it_channel; + it_channel = channel_map_.find(session->id()); + if (it_channel != channel_map_.end()) { + VoiceChannel *channel = (*it_channel).second; + channel->SignalAudioMonitor.connect(this, &Call::OnAudioMonitor); + channel->StartAudioMonitor(cms); + } +} + +void Call::StopAudioMonitor(Session *session) { + std::map::iterator it_channel; + it_channel = channel_map_.find(session->id()); + if (it_channel != channel_map_.end()) { + VoiceChannel *channel = (*it_channel).second; + channel->StopAudioMonitor(); + channel->SignalAudioMonitor.disconnect(this); + } +} + + +void Call::OnConnectionMonitor(VoiceChannel *channel, const std::vector &infos) { + SignalConnectionMonitor(this, channel->session(), infos); +} + +void Call::OnAudioMonitor(VoiceChannel *channel, const AudioInfo& info) { + SignalAudioMonitor(this, channel->session(), info); +} + +uint32 Call::id() { + return id_; +} + +void Call::OnSessionState(Session *session, Session::State state) { + SignalSessionState(this, session, state); +} + +void Call::OnSessionError(Session *session, Session::Error error) { + SignalSessionError(this, session, error); +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/call.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/call.h new file mode 100644 index 00000000..209e13c9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/call.h @@ -0,0 +1,97 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _CALL_H_ +#define _CALL_H_ + +#include "talk/base/messagequeue.h" +#include "talk/p2p/base/session.h" +#include "talk/p2p/client/socketmonitor.h" +#include "talk/xmpp/jid.h" +#include "talk/session/phone/phonesessionclient.h" +#include "talk/session/phone/voicechannel.h" +#include "talk/session/phone/audiomonitor.h" + +#include +#include + +namespace cricket { + +class PhoneSessionClient; + +class Call : public MessageHandler, public sigslot::has_slots<> { +public: + Call(PhoneSessionClient *session_client); + ~Call(); + + Session *InitiateSession(const buzz::Jid &jid); + void AcceptSession(Session *session); + void RedirectSession(Session *session, const buzz::Jid &to); + void RejectSession(Session *session); + void TerminateSession(Session *session); + void Terminate(); + void StartConnectionMonitor(Session *session, int cms); + void StopConnectionMonitor(Session *session); + void StartAudioMonitor(Session *session, int cms); + void StopAudioMonitor(Session *session); + void Mute(bool mute); + + const std::vector &sessions(); + uint32 id(); + bool muted() const { return muted_; } + + sigslot::signal2 SignalAddSession; + sigslot::signal2 SignalRemoveSession; + sigslot::signal3 SignalSessionState; + sigslot::signal3 SignalSessionError; + sigslot::signal3 &> SignalConnectionMonitor; + sigslot::signal3 SignalAudioMonitor; + +private: + void OnMessage(Message *message); + void OnSessionState(Session *session, Session::State state); + void OnSessionError(Session *session, Session::Error error); + void AddSession(Session *session); + void RemoveSession(Session *session); + void EnableChannels(bool enable); + void Join(Call *call, bool enable); + void OnConnectionMonitor(VoiceChannel *channel, const std::vector &infos); + void OnAudioMonitor(VoiceChannel *channel, const AudioInfo& info); + VoiceChannel* GetChannel(Session* session); + + uint32 id_; + PhoneSessionClient *session_client_; + std::vector sessions_; + std::map channel_map_; + bool muted_; + + friend class PhoneSessionClient; +}; + +} + +#endif // _CALL_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/channelmanager.cc b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/channelmanager.cc new file mode 100644 index 00000000..98634b12 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/channelmanager.cc @@ -0,0 +1,203 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifdef HAVE_GIPS +#include "talk/session/phone/gipsmediaengine.h" +#else +#include "talk/session/phone/linphonemediaengine.h" +#endif +#include "channelmanager.h" +#include +#include +namespace cricket { + +const uint32 MSG_CREATEVOICECHANNEL = 1; +const uint32 MSG_DESTROYVOICECHANNEL = 2; +const uint32 MSG_SETAUDIOOPTIONS = 3; + +ChannelManager::ChannelManager(Thread *worker_thread) { +#ifdef HAVE_GIPS + media_engine_ = new GipsMediaEngine(); +#else + media_engine_ = new LinphoneMediaEngine(); +#endif + worker_thread_ = worker_thread; + initialized_ = false; + Init(); +} + +ChannelManager::~ChannelManager() { + Exit(); +} + +MediaEngine *ChannelManager::media_engine() { + return media_engine_; +} + +bool ChannelManager::Init() { + initialized_ = media_engine_->Init(); + return initialized_; +} + +void ChannelManager::Exit() { + if (!initialized_) + return; + + // Need to destroy the voice channels + + while (true) { + crit_.Enter(); + VoiceChannel *channel = NULL; + if (channels_.begin() != channels_.end()) + channel = channels_[0]; + crit_.Leave(); + if (channel == NULL) + break; + delete channel; + } + media_engine_->Terminate(); +} + +struct CreateParams { + Session *session; + VoiceChannel *channel; +}; + +VoiceChannel *ChannelManager::CreateVoiceChannel(Session *session) { + CreateParams params; + params.session = session; + params.channel = NULL; + TypedMessageData data(¶ms); + worker_thread_->Send(this, MSG_CREATEVOICECHANNEL, &data); + return params.channel; +} + +VoiceChannel *ChannelManager::CreateVoiceChannel_w(Session *session) { + CritScope cs(&crit_); + + // This is ok to alloc from a thread other than the worker thread + assert(initialized_); + MediaChannel *channel = media_engine_->CreateChannel(); + if (channel == NULL) + return NULL; + + VoiceChannel *voice_channel = new VoiceChannel(this, session, channel); + channels_.push_back(voice_channel); + return voice_channel; +} + +void ChannelManager::DestroyVoiceChannel(VoiceChannel *voice_channel) { + TypedMessageData data(voice_channel); + worker_thread_->Send(this, MSG_DESTROYVOICECHANNEL, &data); +} + +void ChannelManager::DestroyVoiceChannel_w(VoiceChannel *voice_channel) { + CritScope cs(&crit_); + // Destroy voice channel. + assert(initialized_); + std::vector::iterator it = std::find(channels_.begin(), + channels_.end(), voice_channel); + assert(it != channels_.end()); + if (it == channels_.end()) + return; + + channels_.erase(it); + MediaChannel *channel = voice_channel->channel(); + delete voice_channel; + delete channel; +} + +void ChannelManager::SetAudioOptions(bool auto_gain_control, int wave_in_device, + int wave_out_device) { + AudioOptions options; + options.auto_gain_control = auto_gain_control; + options.wave_in_device = wave_in_device; + options.wave_out_device = wave_out_device; + TypedMessageData data(options); + worker_thread_->Send(this, MSG_SETAUDIOOPTIONS, &data); +} + +void ChannelManager::SetAudioOptions_w(AudioOptions options) { + assert(worker_thread_ == Thread::Current()); + + // Set auto gain control on + if (media_engine_->SetAudioOptions(options.auto_gain_control?MediaEngine::AUTO_GAIN_CONTROL:0) != 0) { + // TODO: We need to log these failures. + } + + // Set the audio devices + // This will fail if audio is already playing. Stop all of the media + // start it up again after changing the setting. + { + CritScope cs(&crit_); + for (VoiceChannels::iterator it = channels_.begin(); + it < channels_.end(); + ++it) { + (*it)->PauseMedia_w(); + } + + if (media_engine_->SetSoundDevices(options.wave_in_device, options.wave_out_device) == -1) { + // TODO: We need to log these failures. + } + + for (VoiceChannels::iterator it = channels_.begin(); + it < channels_.end(); + ++it) { + (*it)->UnpauseMedia_w(); + } + } +} + +Thread *ChannelManager::worker_thread() { + return worker_thread_; +} + +void ChannelManager::OnMessage(Message *message) { + switch (message->message_id) { + case MSG_CREATEVOICECHANNEL: + { + TypedMessageData *data = static_cast *>(message->pdata); + data->data()->channel = CreateVoiceChannel_w(data->data()->session); + } + break; + + case MSG_DESTROYVOICECHANNEL: + { + TypedMessageData *data = static_cast *>(message->pdata); + DestroyVoiceChannel_w(data->data()); + } + break; + case MSG_SETAUDIOOPTIONS: + { + TypedMessageData *data = static_cast *>(message->pdata); + SetAudioOptions_w(data->data()); + } + break; + } +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/channelmanager.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/channelmanager.h new file mode 100644 index 00000000..7200f75e --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/channelmanager.h @@ -0,0 +1,81 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _CHANNELMANAGER_H_ +#define _CHANNELMANAGER_H_ + +#include "talk/base/thread.h" +#include "talk/base/criticalsection.h" +#include "talk/p2p/base/session.h" +#include "talk/p2p/base/p2psocket.h" +#include "talk/session/phone/voicechannel.h" +#include "talk/session/phone/mediaengine.h" +#include + +namespace cricket { + +class VoiceChannel; + +class ChannelManager : public MessageHandler { +public: + ChannelManager(Thread *worker_thread); + ~ChannelManager(); + + VoiceChannel *CreateVoiceChannel(Session *session); + void DestroyVoiceChannel(VoiceChannel *voice_channel); + void SetAudioOptions(bool auto_gain_control, int wave_in_device, + int wave_out_device); + + MediaEngine *media_engine(); + Thread *worker_thread(); + +private: + VoiceChannel *CreateVoiceChannel_w(Session *session); + void DestroyVoiceChannel_w(VoiceChannel *voice_channel); + void OnMessage(Message *message); + bool Init(); + void Exit(); + + struct AudioOptions { + bool auto_gain_control; + int wave_in_device; + int wave_out_device; + }; + void SetAudioOptions_w(AudioOptions options); + + Thread *worker_thread_; + MediaEngine *media_engine_; + bool initialized_; + CriticalSection crit_; + + typedef std::vector VoiceChannels; + VoiceChannels channels_; +}; + +} + +#endif // _CHANNELMANAGER_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/linphonemediaengine.cc b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/linphonemediaengine.cc new file mode 100644 index 00000000..7d2305dc --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/linphonemediaengine.cc @@ -0,0 +1,170 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google 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 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// LinphoneMediaEngine is a Linphone implementation of MediaEngine +extern "C" { +#include "talk/third_party/mediastreamer/mediastream.h" +#ifdef HAVE_ILBC +#include "talk/third_party/mediastreamer/msilbcdec.h" +#endif +#ifdef HAVE_SPEEX +#include "talk/third_party/mediastreamer/msspeexdec.h" +#endif +} +#include +#include +#include +#include +#include +#include "talk/session/phone/linphonemediaengine.h" + +using namespace cricket; + +void *thread_function(void *data) +{ + LinphoneMediaChannel *mc =(LinphoneMediaChannel*) data; + while (mc->dying() == false) { + MediaChannel::NetworkInterface *iface = mc->network_interface(); + char *buf[2048]; + int len; + len = read(mc->fd(), buf, sizeof(buf)); + if (iface && (mc->mute()==FALSE)) + iface->SendPacket(buf, len); + } +} + +LinphoneMediaChannel::LinphoneMediaChannel() { + pt_ = 102; + dying_ = false; + pthread_attr_t attr; + audio_stream_ = NULL; + + struct sockaddr_in sockaddr; + sockaddr.sin_family = AF_INET; + sockaddr.sin_addr.s_addr = INADDR_ANY; + sockaddr.sin_port = htons(3000); + fd_ = socket(PF_INET, SOCK_DGRAM, 0); + fcntl(fd_, F_SETFL, 0, O_NONBLOCK); + bind (fd_,(struct sockaddr*)&sockaddr, sizeof(sockaddr)); + + pthread_attr_init(&attr); + pthread_create(&thread_, &attr, &thread_function, this); +} + +LinphoneMediaChannel::~LinphoneMediaChannel() { + dying_ = true; + pthread_join(thread_, NULL); + audio_stream_stop(audio_stream_); + close(fd_); +} + +void LinphoneMediaChannel::SetCodec(const char *codec) { + if (!strcmp(codec, "iLBC")) + pt_ = 102; + else if (!strcmp(codec, "speex")) + pt_ = 110; + else + pt_ = 0; + if (audio_stream_) + audio_stream_stop(audio_stream_); + audio_stream_ = audio_stream_start(&av_profile, 2000, "127.0.0.1", 3000, pt_, 250); +} + +void LinphoneMediaChannel::OnPacketReceived(const void *data, int len) { + struct sockaddr_in sockaddr; + sockaddr.sin_family = AF_INET; + struct hostent *host = gethostbyname("localhost"); + memcpy(&sockaddr.sin_addr.s_addr, host->h_addr, host->h_length); + sockaddr.sin_port = htons(2000); + + char buf[2048]; + memcpy(buf, data, len); + + if (buf[1] == pt_) { + } else if (buf[1] == 13) { + } else if (buf[1] == 102) { + SetCodec("iLBC"); + } else if (buf[1] == 110) { + SetCodec("speex"); + } else if (buf[1] == 0) { + SetCodec("PCMU"); + } + + if (play_ && buf[1] != 13) + sendto(fd_, buf, len, 0, (struct sockaddr*)&sockaddr, sizeof(sockaddr)); +} + +void LinphoneMediaChannel::SetPlayout(bool playout) { + play_ = playout; +} + +void LinphoneMediaChannel::SetSend(bool send) { + mute_ = !send; +} + +float LinphoneMediaChannel::GetCurrentQuality() {} +int LinphoneMediaChannel::GetOutputLevel() {} + +LinphoneMediaEngine::LinphoneMediaEngine() {} +LinphoneMediaEngine::~LinphoneMediaEngine() {} + +static void null_log_handler(const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer user_data) { +} + +bool LinphoneMediaEngine::Init() { + g_log_set_handler("MediaStreamer", G_LOG_LEVEL_MASK, null_log_handler, NULL); + g_log_set_handler("oRTP", G_LOG_LEVEL_MASK, null_log_handler, NULL); + g_log_set_handler("oRTP-stats", G_LOG_LEVEL_MASK, null_log_handler, NULL); + ortp_init(); + ms_init(); + +#ifdef HAVE_SPEEX + ms_speex_codec_init(); + rtp_profile_set_payload(&av_profile, 110, &speex_wb); + codecs_.push_back(Codec(110, "speex", 8)); +#endif + +#ifdef HAVE_ILBC + ms_ilbc_codec_init(); + rtp_profile_set_payload(&av_profile, 102, &payload_type_ilbc); + codecs_.push_back(Codec(102, "iLBC", 4)); +#endif + + rtp_profile_set_payload(&av_profile, 0, &pcmu8000); + codecs_.push_back(Codec(0, "PCMU", 2)); + +return true; +} + +void LinphoneMediaEngine::Terminate() { + +} + +MediaChannel *LinphoneMediaEngine::CreateChannel() { + return new LinphoneMediaChannel(); +} + +int LinphoneMediaEngine::SetAudioOptions(int options) {} +int LinphoneMediaEngine::SetSoundDevices(int wave_in_device, int wave_out_device) {} + +float LinphoneMediaEngine::GetCurrentQuality() {} +int LinphoneMediaEngine::GetInputLevel() {} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/linphonemediaengine.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/linphonemediaengine.h new file mode 100644 index 00000000..ee16d2ee --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/linphonemediaengine.h @@ -0,0 +1,75 @@ +/* + * Jingle call example + * Copyright 2004--2005, Google 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 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// LinphoneMediaEngine is a Linphone implementation of MediaEngine + +#ifndef TALK_SESSION_PHONE_LINPHONEMEDIAENGINE_H_ +#define TALK_SESSION_PHONE_LINPHONEMEDIAENGINE_H_ + +extern "C" { +#include "talk/third_party/mediastreamer/mediastream.h" +} +#include "talk/session/phone/mediaengine.h" + +namespace cricket { + +class LinphoneMediaChannel : public MediaChannel { + public: + LinphoneMediaChannel(); + virtual ~LinphoneMediaChannel(); + virtual void SetCodec(const char *codec); + virtual void OnPacketReceived(const void *data, int len); + + virtual void SetPlayout(bool playout); + virtual void SetSend(bool send); + + virtual float GetCurrentQuality(); + virtual int GetOutputLevel(); + int fd() {return fd_;} + bool mute() {return mute_;} + bool dying() {return dying_;} + private: + AudioStream *audio_stream_; + pthread_t thread_; + int fd_; + int pt_; + bool dying_; + bool mute_; + bool play_; +}; + +class LinphoneMediaEngine : public MediaEngine { + public: + LinphoneMediaEngine(); + ~LinphoneMediaEngine(); + virtual bool Init(); + virtual void Terminate(); + + virtual MediaChannel *CreateChannel(); + + virtual int SetAudioOptions(int options); + virtual int SetSoundDevices(int wave_in_device, int wave_out_device); + + virtual float GetCurrentQuality(); + virtual int GetInputLevel(); +}; + +} // namespace cricket + +#endif // TALK_SESSION_PHONE_LINPHONEMEDIAENGINE_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/mediachannel.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/mediachannel.h new file mode 100644 index 00000000..db2f9654 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/mediachannel.h @@ -0,0 +1,55 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef TALK_SESSION_PHONE_MEDIACHANNEL_H_ +#define TALK_SESSION_PHONE_MEDIACHANNEL_H_ + +namespace cricket { + +class MediaChannel { + public: + class NetworkInterface { + public: + virtual void SendPacket(const void *data, size_t len) = 0; + }; + MediaChannel() {network_interface_ = NULL;} + virtual ~MediaChannel() {}; + void SetInterface(NetworkInterface *iface) {network_interface_ = iface;} + virtual void SetCodec(const char *codec) = 0; + virtual void OnPacketReceived(const void *data, int len) = 0; + virtual void SetPlayout(bool playout) = 0; + virtual void SetSend(bool send) = 0; + virtual float GetCurrentQuality() = 0; + virtual int GetOutputLevel() = 0; + NetworkInterface *network_interface() {return network_interface_;} + protected: + NetworkInterface *network_interface_; +}; + +}; // namespace cricket + +#endif // TALK_SESSION_PHONE_MEDIACHANNEL_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/mediaengine.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/mediaengine.h new file mode 100644 index 00000000..fa07d2ec --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/mediaengine.h @@ -0,0 +1,95 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +// MediaEngine is an abstraction of a media engine which can be subclassed +// to support different media componentry backends. + +#ifndef TALK_SESSION_PHONE_MEDIAENGINE_H_ +#define TALK_SESSION_PHONE_MEDIAENGINE_H_ + +#include +#include +#include "mediachannel.h" + +namespace cricket { + +class MediaEngine { + public: + + struct Codec { + int id; + std::string name; + int preference; + // Creates a codec with the given parameters. + Codec(int pt, const std::string& nm, int pr) : id(pt), name(nm), preference(pr) {} + // Ranks codecs by their preferences. + bool operator <(const Codec& c) const { return preference > c.preference; } + }; + + // Bitmask flags for options that may be supported by the media engine implementation + enum MediaEngineOptions { + AUTO_GAIN_CONTROL = 1 << 1, + }; + + MediaEngine() {} + + // Initialize + virtual bool Init() = 0; + virtual void Terminate() = 0; + virtual MediaChannel *CreateChannel() = 0; + + virtual int SetAudioOptions(int options) = 0; + virtual int SetSoundDevices(int wave_in_device, int wave_out_device) = 0; + virtual int GetInputLevel() = 0; + + std::vector &codecs() { return codecs_; } + + bool FindCodec(const char* codec) { + for (std::vector::iterator i = codecs_.begin(); i < codecs_.end(); i++) { + if ((*i).name == codec) + return true; + } + return false; + } + + bool GetCodecPreference (const char *codec, int & preference) { + for (std::vector::iterator i = codecs_.begin(); i < codecs_.end(); i++) { + if ((*i).name == codec) { + preference = (*i).preference; + return true; + } + } + return false; + } + + protected: + std::vector codecs_; +}; + +} // namespace cricket + +#endif // TALK_SESSION_PHONE_MEDIAENGINE_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/phonesessionclient.cc b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/phonesessionclient.cc new file mode 100644 index 00000000..d8a31df2 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/phonesessionclient.cc @@ -0,0 +1,267 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/base/logging.h" +#include "talk/session/receiver.h" +#include "talk/session/phone/phonesessionclient.h" +#include "talk/xmllite/qname.h" +namespace { + +const std::string NS_PHONE("http://www.google.com/session/phone"); +const std::string NS_EMPTY(""); + +const buzz::QName QN_PHONE_DESCRIPTION(true, NS_PHONE, "description"); +const buzz::QName QN_PHONE_PAYLOADTYPE(true, NS_PHONE, "payload-type"); +const buzz::QName QN_PHONE_PAYLOADTYPE_ID(true, NS_EMPTY, "id"); +const buzz::QName QN_PHONE_PAYLOADTYPE_NAME(true, NS_EMPTY, "name"); + +} + +namespace cricket { + +PhoneSessionClient::PhoneSessionClient(const buzz::Jid& jid, + SessionManager *manager) : jid_(jid), SessionClient(manager) { + + // No call to start, and certainly no call with focus + focus_call_ = NULL; + + // Start up the channel manager on a worker thread + channel_manager_ = new ChannelManager(session_manager_->worker_thread()); +} + +PhoneSessionClient::~PhoneSessionClient() { + // Destroy all calls + std::map::iterator it; + while (calls_.begin() != calls_.end()) { + std::map::iterator it = calls_.begin(); + DestroyCall((*it).second); + } + + // Delete channel manager. This will wait for the channels to exit + delete channel_manager_; +} + +const std::string &PhoneSessionClient::GetSessionDescriptionName() { + return NS_PHONE; +} + +PhoneSessionDescription* PhoneSessionClient::CreateOfferSessionDescription() { + PhoneSessionDescription* session_desc = new PhoneSessionDescription(); + + MediaEngine *me = channel_manager_->media_engine(); + std::vector codecs = me->codecs(); + std::vector::iterator i; + for (i = codecs.begin(); i < codecs.end(); i++) + session_desc->AddCodec(*i); + + session_desc->Sort(); + return session_desc; +} + +PhoneSessionDescription* PhoneSessionClient::CreateAcceptSessionDescription(const SessionDescription* offer) { + const PhoneSessionDescription* offer_desc = + static_cast(offer); + PhoneSessionDescription* accept_desc = new PhoneSessionDescription(); + std::vector codecs = channel_manager_->media_engine()->codecs(); + std::vector::iterator iter; + for (unsigned int i = 0; i < offer_desc->codecs().size(); ++i) { + for (iter = codecs.begin(); iter < codecs.end(); iter++) { + if ((*iter).name == offer_desc->codecs()[i].name) + accept_desc->AddCodec(*iter); + } + } + + accept_desc->Sort(); + return accept_desc; +} + +bool PhoneSessionClient::FindMediaCodec(MediaEngine* me, + const PhoneSessionDescription* desc, + const char** codec) { + for (size_t i = 0; i < desc->codecs().size(); ++i) { + if (me->FindCodec(desc->codecs()[i].name.c_str())) + *codec = desc->codecs()[i].name.c_str(); + return true; + } + + return false; +} + +const SessionDescription *PhoneSessionClient::CreateSessionDescription(const buzz::XmlElement *element) { + PhoneSessionDescription* desc = new PhoneSessionDescription(); + + const buzz::XmlElement* payload_type = element->FirstNamed(QN_PHONE_PAYLOADTYPE); + int num_payload_types = 0; + + while (payload_type) { + if (payload_type->HasAttr(QN_PHONE_PAYLOADTYPE_ID) && + payload_type->HasAttr(QN_PHONE_PAYLOADTYPE_NAME)) { + int id = atoi(payload_type->Attr(QN_PHONE_PAYLOADTYPE_ID).c_str()); + int pref = 0; + std::string name = payload_type->Attr(QN_PHONE_PAYLOADTYPE_NAME); + desc->AddCodec(MediaEngine::Codec(id, name, 0)); + } + + payload_type = payload_type->NextNamed(QN_PHONE_PAYLOADTYPE); + num_payload_types += 1; + } + + // For backward compatability, we can assume the other client is (an old + // version of Talk) if it has no payload types at all. + if (num_payload_types == 0) { + desc->AddCodec(MediaEngine::Codec(103, "ISAC", 1)); + desc->AddCodec(MediaEngine::Codec(0, "PCMU", 0)); + } + + return desc; +} + +buzz::XmlElement *PhoneSessionClient::TranslateSessionDescription(const SessionDescription *_session_desc) { + const PhoneSessionDescription* session_desc = + static_cast(_session_desc); + buzz::XmlElement* description = new buzz::XmlElement(QN_PHONE_DESCRIPTION, true); + + for (size_t i = 0; i < session_desc->codecs().size(); ++i) { + buzz::XmlElement* payload_type = new buzz::XmlElement(QN_PHONE_PAYLOADTYPE, true); + + char buf[32]; + sprintf(buf, "%d", session_desc->codecs()[i].id); + payload_type->AddAttr(QN_PHONE_PAYLOADTYPE_ID, buf); + + payload_type->AddAttr(QN_PHONE_PAYLOADTYPE_NAME, + session_desc->codecs()[i].name.c_str()); + + description->AddElement(payload_type); + } + + return description; +} + +Call *PhoneSessionClient::CreateCall() { + Call *call = new Call(this); + calls_[call->id()] = call; + SignalCallCreate(call); + return call; +} + +void PhoneSessionClient::OnSessionCreate(Session *session, bool received_initiate) { + if (received_initiate) { + session->SignalState.connect(this, &PhoneSessionClient::OnSessionState); + + Call *call = CreateCall(); + session_map_[session->id()] = call; + call->AddSession(session); + } +} + +void PhoneSessionClient::OnSessionState(Session *session, Session::State state) { + if (state == Session::STATE_RECEIVEDINITIATE) { + // If our accept would have no codecs, then we must reject this call. + PhoneSessionDescription* accept_desc = + CreateAcceptSessionDescription(session->remote_description()); + if (accept_desc->codecs().size() == 0) { + // TODO: include an error description with the rejection. + session->Reject(); + } + delete accept_desc; + } +} + +void PhoneSessionClient::DestroyCall(Call *call) { + // Change focus away, signal destruction + + if (call == focus_call_) + SetFocus(NULL); + SignalCallDestroy(call); + + // Remove it from calls_ map and delete + + std::map::iterator it = calls_.find(call->id()); + if (it != calls_.end()) + calls_.erase(it); + + delete call; +} + +void PhoneSessionClient::OnSessionDestroy(Session *session) { + // Find the call this session is in, remove it + + std::map::iterator it = session_map_.find(session->id()); + assert(it != session_map_.end()); + if (it != session_map_.end()) { + Call *call = (*it).second; + session_map_.erase(it); + call->RemoveSession(session); + } +} + +Call *PhoneSessionClient::GetFocus() { + return focus_call_; +} + +void PhoneSessionClient::SetFocus(Call *call) { + Call *old_focus_call = focus_call_; + if (focus_call_ != call) { + if (focus_call_ != NULL) + focus_call_->EnableChannels(false); + focus_call_ = call; + if (focus_call_ != NULL) + focus_call_->EnableChannels(true); + SignalFocus(focus_call_, old_focus_call); + } +} + +void PhoneSessionClient::JoinCalls(Call *call_to_join, Call *call) { + // Move all sessions from call to call_to_join, delete call. + // If call_to_join has focus, added sessions should have enabled channels. + + if (focus_call_ == call) + SetFocus(NULL); + call_to_join->Join(call, focus_call_ == call_to_join); + DestroyCall(call); +} + +Session *PhoneSessionClient::CreateSession(Call *call) { + Session *session = session_manager_->CreateSession( + GetSessionDescriptionName(), jid().Str()); + session_map_[session->id()] = call; + return session; +} + +ChannelManager *PhoneSessionClient::channel_manager() { + return channel_manager_; +} + +const buzz::Jid &PhoneSessionClient::jid() const { + return jid_; +} + +const buzz::Jid &PhoneSessionClient::GetJid() const { + return jid_; +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/phonesessionclient.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/phonesessionclient.h new file mode 100644 index 00000000..150bf34b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/phonesessionclient.h @@ -0,0 +1,122 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _PHONESESSIONCLIENT_H_ +#define _PHONESESSIONCLIENT_H_ + +#include "talk/session/phone/call.h" +#include "talk/session/phone/channelmanager.h" +#include "talk/base/sigslot.h" +#include "talk/base/messagequeue.h" +#include "talk/base/thread.h" +#include "talk/p2p/client/sessionclient.h" +#include "talk/p2p/base/sessionmanager.h" +#include "talk/p2p/base/session.h" +#include "talk/p2p/base/sessiondescription.h" +#include "talk/xmpp/xmppclient.h" +#include + +namespace cricket { + +class Call; +class PhoneSessionDescription; + +class PhoneSessionClient : public SessionClient { +public: + PhoneSessionClient(const buzz::Jid& jid, SessionManager *manager); + ~PhoneSessionClient(); + + const buzz::Jid &jid() const; + + Call *CreateCall(); + void DestroyCall(Call *call); + + Call *GetFocus(); + void SetFocus(Call *call); + + void JoinCalls(Call *call_to_join, Call *call); + + void SetAudioOptions(bool auto_gain_control, int wave_in_device, + int wave_out_device) { + if (channel_manager_) + channel_manager_->SetAudioOptions(auto_gain_control, wave_in_device, + wave_out_device); + } + + sigslot::signal2 SignalFocus; + sigslot::signal1 SignalCallCreate; + sigslot::signal1 SignalCallDestroy; + + PhoneSessionDescription* CreateOfferSessionDescription(); + PhoneSessionDescription* CreateAcceptSessionDescription(const SessionDescription* offer); + + // Returns our preference for the given codec. + static int GetMediaCodecPreference(const char* name); + + // Returns the name of the first codec in the description that + // is found. Return value is false if none was found. + static bool FindMediaCodec(MediaEngine* gips, + const PhoneSessionDescription* desc, + const char **codec); + +private: + void OnSessionCreate(Session *session, bool received_initiate); + void OnSessionState(Session *session, Session::State state); + void OnSessionDestroy(Session *session); + const SessionDescription *CreateSessionDescription(const buzz::XmlElement *element); + buzz::XmlElement *TranslateSessionDescription(const SessionDescription *description); + const std::string &GetSessionDescriptionName(); + const buzz::Jid &GetJid() const; + Session *CreateSession(Call *call); + ChannelManager *channel_manager(); + + buzz::Jid jid_; + Call *focus_call_; + ChannelManager *channel_manager_; + std::map calls_; + std::map session_map_; + + friend class Call; +}; + +class PhoneSessionDescription: public SessionDescription { +public: + // Returns the list of codecs sorted by our preference. + const std::vector& codecs() const { return codecs_; } + + // Adds another codec to the list. + void AddCodec(const MediaEngine::Codec& codec) { codecs_.push_back(codec); } + // Sorts the list of codecs by preference. + void Sort() { /* std::stable_sort(codecs_.begin(), codecs_.end());*/ } + +private: + std::vector codecs_; +}; + +} + +#endif // _PHONESESSIONCLIENT_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/portaudiomediaengine.cc b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/portaudiomediaengine.cc new file mode 100644 index 00000000..b65c9a20 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/portaudiomediaengine.cc @@ -0,0 +1,331 @@ +#include +#include +#include + +// Socket stuff +#ifndef _WIN32 +#ifdef INET6 +#include +#endif +#include +#include +#include +#else +#include +#endif + +#include "talk/session/phone/mediaengine.h" +#include "talk/session/phone/portaudiomediaengine.h" + +// Engine settings +#define ENGINE_BUFFER_SIZE 2048 + +// PortAudio settings +#define FRAMES_PER_BUFFER 256 +#define SAMPLE_RATE 1 + +// Speex settings +//#define SPEEX_QUALITY 8 + +// ORTP settings +#define MAX_RTP_SIZE 1500 // From mediastreamer + + +// ----------------------------------------------------------------------------- + +static int portAudioCallback( void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, PaTimestamp outTime, void *channel_p ) +{ + PortAudioMediaChannel* channel = (PortAudioMediaChannel*) channel_p; + channel->readOutput((float*) outputBuffer, framesPerBuffer); + channel->writeInput((float*) inputBuffer, framesPerBuffer); + return 0; +} + +// ----------------------------------------------------------------------------- + +PortAudioMediaChannel::PortAudioMediaChannel() : mute_(false), play_(false), stream_(NULL), out_buffer_(NULL), in_buffer_(NULL), speex_frame_(NULL) +{ + // Initialize buffers + out_buffer_ = new float[ENGINE_BUFFER_SIZE]; + out_buffer_read_ = out_buffer_write_ = (float*) out_buffer_; + out_buffer_end_ = (float*) out_buffer_ + ENGINE_BUFFER_SIZE; + in_buffer_ = new float[ENGINE_BUFFER_SIZE]; + in_buffer_read_ = in_buffer_write_ = (float*) in_buffer_; + in_buffer_end_ = (float*) in_buffer_ + ENGINE_BUFFER_SIZE; + + // Initialize PortAudio + int err = Pa_OpenDefaultStream(&stream_, 1, 1, paFloat32, SAMPLE_RATE, FRAMES_PER_BUFFER, 0, portAudioCallback, this ); + if (err != paNoError) + fprintf(stderr, "Error creating a PortAudio stream: %s\n", Pa_GetErrorText(err)); + + // Initialize Speex + speex_bits_init(&speex_bits_); + speex_enc_state_ = speex_encoder_init(&speex_nb_mode); + speex_dec_state_ = speex_decoder_init(&speex_nb_mode); + speex_decoder_ctl(speex_dec_state_, SPEEX_GET_FRAME_SIZE, &speex_frame_size_); + speex_frame_ = new float[speex_frame_size_]; + + // int quality = SPEEX_QUALITY; + // speex_encoder_ctl(state, SPEEX_SET_QUALITY, &quality); + + // Initialize ORTP socket + struct sockaddr_in sockaddr; + sockaddr.sin_family = AF_INET; + sockaddr.sin_addr.s_addr = INADDR_ANY; + sockaddr.sin_port = htons(3000); + rtp_socket_ = socket(PF_INET, SOCK_DGRAM, 0); + fcntl(rtp_socket_, F_SETFL, 0, O_NONBLOCK); + bind (rtp_socket_,(struct sockaddr*)&sockaddr, sizeof(sockaddr)); + + // Initialize ORTP Session + rtp_session_ = rtp_session_new(RTP_SESSION_SENDRECV); + rtp_session_max_buf_size_set(rtp_session_, MAX_RTP_SIZE); + rtp_session_set_profile(rtp_session_, &av_profile); + rtp_session_set_local_addr(rtp_session_, "127.0.0.1", 2000); + rtp_session_set_remote_addr(rtp_session_, "127.0.0.1", 3000); + rtp_session_set_scheduling_mode(rtp_session_, 0); + rtp_session_set_blocking_mode(rtp_session_, 0); + rtp_session_set_payload_type(rtp_session_, 110); + rtp_session_set_jitter_compensation(rtp_session_, 250); + rtp_session_enable_adaptive_jitter_compensation(rtp_session_, TRUE); + rtp_timestamp_ = 0; + //rtp_session_signal_connect(rtp_session_, "telephone-event", (RtpCallback) ortpTelephoneCallback,this); +} + +PortAudioMediaChannel::~PortAudioMediaChannel() +{ + if (stream_) { + Pa_CloseStream(stream_); + } + + // Clean up other allocated pointers + + close(rtp_socket_); +} + +void PortAudioMediaChannel::SetCodec(const char *codec) +{ + if (strcmp(codec, "speex")) + printf("Unsupported codec: %s\n", codec); +} + +void PortAudioMediaChannel::OnPacketReceived(const void *data, int len) +{ + struct sockaddr_in sockaddr; + sockaddr.sin_family = AF_INET; + struct hostent *host = gethostbyname("localhost"); + memcpy(&sockaddr.sin_addr.s_addr, host->h_addr, host->h_length); + sockaddr.sin_port = htons(2000); + + char buf[2048]; + memcpy(buf, data, len); + + // Pass packet on to ORTP + if (play_) { + sendto(rtp_socket_, buf, len, 0, (struct sockaddr*)&sockaddr, sizeof(sockaddr)); + } +} + +void PortAudioMediaChannel::SetPlayout(bool playout) +{ + if (!stream_) + return; + + if (play_ && !playout) { + int err = Pa_StopStream(stream_); + if (err != paNoError) { + fprintf(stderr, "Error stopping PortAudio stream: %s\n", Pa_GetErrorText(err)); + return; + } + play_ = false; + } + else if (!play_ && playout) { + int err = Pa_StartStream(stream_); + if (err != paNoError) { + fprintf(stderr, "Error starting PortAudio stream: %s\n", Pa_GetErrorText(err)); + return; + } + play_ = true; + } +} + +void PortAudioMediaChannel::SetSend(bool send) +{ + mute_ = !send; +} + + +float PortAudioMediaChannel::GetCurrentQuality() +{ + return 0; +} + +int PortAudioMediaChannel::GetOutputLevel() +{ + return 0; +} + +void PortAudioMediaChannel::readOutput(float* buf, int len) +{ + //readBuffer(out_buffer_, &out_buffer_read_, out_buffer_write_, out_buffer_end_, buf, len); + + // Receive a packet (if there is one) + mblk_t *mp; + mp = rtp_session_recvm_with_ts(rtp_session_,rtp_timestamp_); + while (mp != NULL) { + gint in_len = mp->b_cont->b_wptr-mp->b_cont->b_rptr; + + // Decode speex stream + speex_bits_read_from(&speex_bits_,mp->b_cont->b_rptr, in_len); + speex_decode(speex_dec_state_, &speex_bits_, speex_frame_); + writeBuffer(out_buffer_, out_buffer_read_, &out_buffer_write_, out_buffer_end_, speex_frame_, speex_frame_size_); + rtp_timestamp_++; + mp = rtp_session_recvm_with_ts(rtp_session_,rtp_timestamp_); + } + + // Read output + readBuffer(out_buffer_, &out_buffer_read_, out_buffer_write_, out_buffer_end_, buf, len); +} + +void PortAudioMediaChannel::writeInput(float* buf, int len) +{ + //writeBuffer(in_buffer_, in_buffer_read_, &in_buffer_write_, in_buffer_end_, buf, len); +} + + +void PortAudioMediaChannel::readBuffer(float* buffer, float** buffer_read_p, float*buffer_write, float* buffer_end, float* target_buffer, int target_len) +{ + float *end, *tmp, *buffer_read = *buffer_read_p; + int remaining; + + // First phase + tmp = buffer_read + target_len; + if (buffer_write < buffer_read && tmp > buffer_end) { + end = buffer_end; + remaining = tmp - buffer_end; + } + else { + end = (tmp > buffer_write ? buffer_write : tmp); + remaining = 0; + } + + while (buffer_read < end) { + *target_buffer++ = *buffer_read++; + } + + // Second phase + if (remaining > 0) { + buffer_read = buffer; + tmp = buffer_read + remaining; + end = (tmp > buffer_write ? buffer_write : tmp); + while (buffer_read < end) { + *target_buffer++ = *buffer_read++; + } + } + + // Finish up + *buffer_read_p = buffer_read; +} + +void PortAudioMediaChannel::writeBuffer(float* buffer, float* buffer_read, float**buffer_write_p, float* buffer_end, float* source_buffer, int source_len) +{ + float *end, *tmp, *buffer_write = *buffer_write_p; + int remaining; + + // First phase + tmp = buffer_write + source_len; + if (buffer_write > buffer_read) { + if (tmp > buffer_end) { + end = buffer_end; + remaining = tmp - buffer_end; + } + else { + end = tmp; + remaining = 0; + } + } + else { + if (tmp > buffer_read) { + printf("Warning: Dropping frame(s)\n"); + end = buffer_read; + remaining = 0; + } + else { + end = tmp; + remaining = 0; + } + } + + while (buffer_write < end) { + *buffer_write++ = *source_buffer++; + } + + // Second phase + if (remaining > 0) { + buffer_write = buffer; + tmp = buffer_write + remaining; + if (tmp > buffer_read) { + printf("Warning: Dropping frame(s)\n"); + end = buffer_read; + } + else { + end = tmp; + } + while (buffer_write < end) { + *buffer_write++ = *source_buffer++; + } + } + + // Finish up + *buffer_write_p = buffer_write; +} + +// ----------------------------------------------------------------------------- + +PortAudioMediaEngine::PortAudioMediaEngine() +{ +} + +PortAudioMediaEngine::~PortAudioMediaEngine() +{ + Pa_Terminate(); +} + +bool PortAudioMediaEngine::Init() +{ + ortp_init(); + + int err = Pa_Initialize(); + if (err != paNoError) { + fprintf(stderr,"Error initializing PortAudio: %s\n",Pa_GetErrorText(err)); + return false; + } + + // Speex + rtp_profile_set_payload(&av_profile, 110, &speex_wb); + codecs_.push_back(Codec(110, "speex", 8)); + + return true; +} + +void PortAudioMediaEngine::Terminate() +{ +} + + +cricket::MediaChannel* PortAudioMediaEngine::CreateChannel() +{ + return new PortAudioMediaChannel(); +} + +int PortAudioMediaEngine::SetAudioOptions(int options) +{ +} + +int PortAudioMediaEngine::SetSoundDevices(int wave_in_device, int wave_out_device) +{ +} + +int PortAudioMediaEngine::GetInputLevel() +{ +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/portaudiomediaengine.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/portaudiomediaengine.h new file mode 100644 index 00000000..95c39a1a --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/portaudiomediaengine.h @@ -0,0 +1,69 @@ +#ifndef PORTAUDIOMEDIAENGINE_H +#define PORTAUDIOMEDIAENGINE_H + +#include +#include +#include + +#include "talk/session/phone/mediaengine.h" + +class PortAudioMediaChannel : public cricket::MediaChannel +{ +public: + PortAudioMediaChannel(); + virtual ~PortAudioMediaChannel(); + virtual void SetCodec(const char *codec); + virtual void OnPacketReceived(const void *data, int len); + + virtual void SetPlayout(bool playout); + virtual void SetSend(bool send); + + virtual float GetCurrentQuality(); + virtual int GetOutputLevel(); + + void readOutput(float*, int); + void writeInput(float*, int); + +protected: + void readBuffer(float*, float**, float*, float*, float*, int); + void writeBuffer(float*, float*, float**, float*, float*, int); + +private: + bool mute_; + bool play_; + PortAudioStream* stream_; + + // Buffers + float *out_buffer_, *out_buffer_read_, *out_buffer_write_, *out_buffer_end_; + float *in_buffer_, *in_buffer_read_, *in_buffer_write_, *in_buffer_end_; + + // Speex + SpeexBits speex_bits_; + void *speex_enc_state_, *speex_dec_state_; + float *speex_frame_; + int speex_frame_size_; + + // ORTP + int rtp_socket_; + RtpSession* rtp_session_; + int rtp_timestamp_; +}; + + +class PortAudioMediaEngine : public cricket::MediaEngine +{ +public: + PortAudioMediaEngine(); + ~PortAudioMediaEngine(); + virtual bool Init(); + virtual void Terminate(); + + virtual cricket::MediaChannel *CreateChannel(); + + virtual int SetAudioOptions(int options); + virtual int SetSoundDevices(int wave_in_device, int wave_out_device); + + virtual int GetInputLevel(); +}; + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/voicechannel.cc b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/voicechannel.cc new file mode 100644 index 00000000..58e1db60 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/voicechannel.cc @@ -0,0 +1,331 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/session/phone/voicechannel.h" +#include "talk/session/phone/channelmanager.h" +#include "talk/session/phone/phonesessionclient.h" +#include "talk/base/logging.h" +#include +#undef SetPort + +namespace { + +// Delay before quality estimate is meaningful. +uint32 kQualityDelay = 5000; // in ms + +} + +namespace cricket { + +VoiceChannel::VoiceChannel(ChannelManager *manager, Session *session, MediaChannel *channel) { + channel_manager_ = manager; + assert(channel_manager_->worker_thread() == Thread::Current()); + channel_ = channel; + session_ = session; + socket_monitor_ = NULL; + audio_monitor_ = NULL; + socket_ = session_->CreateSocket("rtp"); + socket_->SignalState.connect(this, &VoiceChannel::OnSocketState); + socket_->SignalReadPacket.connect(this, &VoiceChannel::OnSocketRead); + channel->SetInterface(this); + enabled_ = false; + paused_ = false; + socket_writable_ = false; + muted_ = false; + LOG(INFO) << "Created voice channel"; + start_time_ = 0xFFFFFFFF - kQualityDelay; + + session->SignalState.connect(this, &VoiceChannel::OnSessionState); + OnSessionState(session, session->state()); +} + +VoiceChannel::~VoiceChannel() { + assert(channel_manager_->worker_thread() == Thread::Current()); + enabled_ = false; + ChangeState(); + delete socket_monitor_; + delete audio_monitor_; + Thread::Current()->Clear(this); + if (socket_ != NULL) + session_->DestroySocket(socket_); + LOG(INFO) << "Destroyed voice channel"; +} + +void VoiceChannel::OnMessage(Message *pmsg) { + switch (pmsg->message_id) { + case MSG_ENABLE: + EnableMedia_w(); + break; + + case MSG_DISABLE: + DisableMedia_w(); + break; + + case MSG_MUTE: + MuteMedia_w(); + break; + + case MSG_UNMUTE: + UnmuteMedia_w(); + break; + + case MSG_SETSENDCODEC: + SetSendCodec_w(); + break; + } +} + +void VoiceChannel::Enable(bool enable) { + // Can be called from thread other than worker thread + channel_manager_->worker_thread()->Post(this, enable ? MSG_ENABLE : MSG_DISABLE); +} + +void VoiceChannel::Mute(bool mute) { + // Can be called from thread other than worker thread + channel_manager_->worker_thread()->Post(this, mute ? MSG_MUTE : MSG_UNMUTE); +} + +MediaChannel * VoiceChannel::channel() { + return channel_; +} + +void VoiceChannel::OnSessionState(Session* session, Session::State state) { + if ((state == Session::STATE_RECEIVEDACCEPT) || + (state == Session::STATE_RECEIVEDINITIATE)) { + channel_manager_->worker_thread()->Post(this, MSG_SETSENDCODEC); + } +} + +void VoiceChannel::SetSendCodec_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + + const PhoneSessionDescription* desc = + static_cast(session()->remote_description()); + + const char *codec = NULL; + + if (desc->codecs().size() > 0) + PhoneSessionClient::FindMediaCodec(channel_manager_->media_engine(), desc, &codec); + + // The other client should have returned one of the codecs that we offered. + // If they could not, they should have rejected the session. So, if we get + // into this state, we're dealing with a bad client, so we may as well just + // pick the mostt common format there is: payload type zero. + if (codec == NULL) + codec = "PCMU"; + + channel_->SetCodec(codec); +} + +void VoiceChannel::OnSocketState(P2PSocket *socket, P2PSocket::State state) { + switch (state) { + case P2PSocket::STATE_WRITABLE: + SocketWritable_w(); + break; + + default: + SocketNotWritable_w(); + break; + } +} + +void VoiceChannel::OnSocketRead(P2PSocket *socket, const char *data, size_t len) { + assert(channel_manager_->worker_thread() == Thread::Current()); + // OnSocketRead gets called from P2PSocket; now pass data to MediaEngine + channel_->OnPacketReceived(data, (int)len); +} + +void VoiceChannel::SendPacket(const void *data, size_t len) { + // SendPacket gets called from MediaEngine; send to socket + // MediaEngine will call us on a random thread. The Send operation on the socket is + // special in that it can handle this. + socket_->Send(static_cast(data), len); +} + +void VoiceChannel::ChangeState() { + if (paused_ || !enabled_ || !socket_writable_) { + channel_->SetPlayout(false); + channel_->SetSend(false); + } else { + if (muted_) { + channel_->SetSend(false); + channel_->SetPlayout(true); + } else { + channel_->SetSend(true); + channel_->SetPlayout(true); + } + } +} + +void VoiceChannel::PauseMedia_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + assert(!paused_); + + LOG(INFO) << "Voice channel paused"; + paused_ = true; + ChangeState(); +} + +void VoiceChannel::UnpauseMedia_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + assert(paused_); + + LOG(INFO) << "Voice channel unpaused"; + paused_ = false; + ChangeState(); +} + +void VoiceChannel::EnableMedia_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + if (enabled_) + return; + + LOG(INFO) << "Voice channel enabled"; + enabled_ = true; + start_time_ = Time(); + ChangeState(); +} + +void VoiceChannel::DisableMedia_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + if (!enabled_) + return; + + LOG(INFO) << "Voice channel disabled"; + enabled_ = false; + ChangeState(); +} + +void VoiceChannel::MuteMedia_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + if (muted_) + return; + + LOG(INFO) << "Voice channel muted"; + muted_ = true; + ChangeState(); +} + +void VoiceChannel::UnmuteMedia_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + if (!muted_) + return; + + LOG(INFO) << "Voice channel unmuted"; + muted_ = false; + ChangeState(); +} + +void VoiceChannel::SocketWritable_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + if (socket_writable_) + return; + + LOG(INFO) << "Voice channel socket writable"; + socket_writable_ = true; + ChangeState(); +} + +void VoiceChannel::SocketNotWritable_w() { + assert(channel_manager_->worker_thread() == Thread::Current()); + if (!socket_writable_) + return; + + LOG(INFO) << "Voice channel socket not writable"; + socket_writable_ = false; + ChangeState(); +} + +void VoiceChannel::StartConnectionMonitor(int cms) { + delete socket_monitor_; + socket_monitor_ = new SocketMonitor(socket_, Thread::Current()); + socket_monitor_ + ->SignalUpdate.connect(this, &VoiceChannel::OnConnectionMonitorUpdate); + socket_monitor_->Start(cms); +} + +void VoiceChannel::StopConnectionMonitor() { + if (socket_monitor_ != NULL) { + socket_monitor_->Stop(); + socket_monitor_->SignalUpdate.disconnect(this); + delete socket_monitor_; + socket_monitor_ = NULL; + } +} + +void VoiceChannel::OnConnectionMonitorUpdate(SocketMonitor *monitor, + const std::vector &infos) { + SignalConnectionMonitor(this, infos); +} + +void VoiceChannel::StartAudioMonitor(int cms) { + delete audio_monitor_; + audio_monitor_ = new AudioMonitor(this, Thread::Current()); + audio_monitor_ + ->SignalUpdate.connect(this, &VoiceChannel::OnAudioMonitorUpdate); + audio_monitor_->Start(cms); +} + +void VoiceChannel::StopAudioMonitor() { + if (audio_monitor_ != NULL) { + audio_monitor_ ->Stop(); + audio_monitor_ ->SignalUpdate.disconnect(this); + delete audio_monitor_ ; + audio_monitor_ = NULL; + } +} + +void VoiceChannel::OnAudioMonitorUpdate(AudioMonitor *monitor, + const AudioInfo& info) { + SignalAudioMonitor(this, info); +} + +Session *VoiceChannel::session() { + return session_; +} + +bool VoiceChannel::HasQuality() { + return Time() >= start_time_ + kQualityDelay; +} + +float VoiceChannel::GetCurrentQuality() { + return channel_->GetCurrentQuality(); +} + +int VoiceChannel::GetInputLevel_w() { + return channel_manager_->media_engine()->GetInputLevel(); +} + +int VoiceChannel::GetOutputLevel_w() { + return channel_->GetOutputLevel(); +} + +Thread* VoiceChannel::worker_thread() { + return channel_manager_->worker_thread(); +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/voicechannel.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/voicechannel.h new file mode 100644 index 00000000..4cfa0b11 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/phone/voicechannel.h @@ -0,0 +1,129 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _VOICECHANNEL_H_ +#define _VOICECHANNEL_H_ + +#include "talk/base/asyncudpsocket.h" +#include "talk/base/network.h" +#include "talk/base/sigslot.h" +#include "talk/p2p/client/socketmonitor.h" +#include "talk/p2p/base/p2psocket.h" +#include "talk/p2p/base/session.h" +#include "talk/session/phone/audiomonitor.h" +#include "talk/session/phone/mediaengine.h" + +namespace cricket { + +const uint32 MSG_ENABLE = 1; +const uint32 MSG_DISABLE = 2; +const uint32 MSG_MUTE = 3; +const uint32 MSG_UNMUTE = 4; +const uint32 MSG_SETSENDCODEC = 5; + +class ChannelManager; + +class VoiceChannel + : public MessageHandler, public sigslot::has_slots<>, + public NetworkSession, public MediaChannel::NetworkInterface { + public: + VoiceChannel(ChannelManager *manager, Session *session, MediaChannel *channel); + ~VoiceChannel(); + + void Enable(bool enable); + void Mute(bool mute); + MediaChannel *channel(); + Session *session(); + + // Monitoring + + void StartConnectionMonitor(int cms); + void StopConnectionMonitor(); + sigslot::signal2 &> SignalConnectionMonitor; + + void StartAudioMonitor(int cms); + void StopAudioMonitor(); + sigslot::signal2 SignalAudioMonitor; + Thread* worker_thread(); + + // Pausing so that the ChannelManager can change the audio devices. These + // should only be called from the worker thread + void PauseMedia_w(); + void UnpauseMedia_w(); + + int GetInputLevel_w(); + int GetOutputLevel_w(); + + // Gives a quality estimate to the network quality manager. + virtual bool HasQuality(); + virtual float GetCurrentQuality(); + + // MediaEngine calls this + virtual void SendPacket(const void *data, size_t len); + +private: + void ChangeState(); + void EnableMedia_w(); + void DisableMedia_w(); + void MuteMedia_w(); + void UnmuteMedia_w(); + void SocketWritable_w(); + void SocketNotWritable_w(); + + void OnConnectionMonitorUpdate(SocketMonitor *monitor, const std::vector &infos); + void OnAudioMonitorUpdate(AudioMonitor *monitor, const AudioInfo& info); + + // From MessageHandler + + void OnMessage(Message *pmsg); + + // Setting the send codec based on the remote description. + void OnSessionState(Session* session, Session::State state); + void SetSendCodec_w(); + + // From P2PSocket + + void OnSocketState(P2PSocket *socket, P2PSocket::State state); + void OnSocketRead(P2PSocket *socket, const char *data, size_t len); + + + bool enabled_; + bool paused_; + bool socket_writable_; + bool muted_; + MediaChannel *channel_; + Session *session_; + P2PSocket *socket_; + ChannelManager *channel_manager_; + SocketMonitor *socket_monitor_; + AudioMonitor *audio_monitor_; + uint32 start_time_; +}; + +} + +#endif // _VOICECHANNEL_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/receiver.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/receiver.h new file mode 100644 index 00000000..a5326893 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/receiver.h @@ -0,0 +1,72 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _RECEIVER_H_ +#define _RECEIVER_H_ + +#include "talk/xmpp/xmppengine.h" +#include "talk/xmpp/xmpptask.h" +#include "talk/p2p/client/sessionclient.h" + +namespace cricket { + +class Receiver : public buzz::XmppTask { +public: + Receiver(Task *parent, SessionClient *session_client) + : buzz::XmppTask(parent, buzz::XmppEngine::HL_TYPE) { + session_client_ = session_client; + } + + virtual int ProcessStart() { + const buzz::XmlElement *stanza = NextStanza(); + if (stanza == NULL) + return STATE_BLOCKED; + session_client_->OnIncomingStanza(stanza); + + // Respond right away to the sender to let them know that we received + // this IQ + buzz::XmlElement * result = MakeIqResult(stanza); + SendStanza(result); + + return STATE_START; + } + +protected: + virtual bool HandleStanza(const buzz::XmlElement *stanza) { + if (!session_client_->IsClientStanza(stanza)) + return false; + QueueStanza(stanza); + return true; + } + +private: + SessionClient *session_client_; +}; + +} + +#endif // _RECEIVER_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/session/sessionsendtask.h b/kopete/protocols/jabber/jingle/libjingle/talk/session/sessionsendtask.h new file mode 100644 index 00000000..9dc5384c --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/session/sessionsendtask.h @@ -0,0 +1,111 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _CRICKET_PHONE_SESSIONSENDTASK_H_ +#define _CRICKET_PHONE_SESSIONSENDTASK_H_ + +#include "talk/xmpp/xmppengine.h" +#include "talk/xmpp/xmpptask.h" +#include "talk/p2p/client/sessionclient.h" + +namespace cricket { + +// The job of this task is to send an IQ stanza out (after stamping it with +// an ID attribute) and then wait for a response. If not response happens +// within 5 seconds, it will signal failure on a SessionClient. If an error +// happens it will also signal failure. If, however, the send succeeds this +// task will quietly go away. + +// It is safe for this to hold on to the session client. In the case where +// the xmpp client goes away, this task will automatically be aborted. The +// session_client is guaranteed to outlive the xmpp session. +class SessionSendTask : public buzz::XmppTask { +public: + SessionSendTask(Task *parent, SessionClient *session_client) + : buzz::XmppTask(parent, buzz::XmppEngine::HL_SINGLE), + session_client_(session_client), + timed_out_(false) { + } + + void Send(const buzz::XmlElement* stanza) { + assert(stanza_.get() == NULL); + stanza_.reset(new buzz::XmlElement(*stanza)); + stanza_->SetAttr(buzz::QN_ID, task_id()); + } + +protected: + // This gets called by the task runner every 500 msec + virtual void Poll() { + if (ElapsedTime() > (15 * 1000 * 10000)) { // 15 secs + timed_out_ = true; + Wake(); + } + } + + virtual int ProcessStart() { + SendStanza(stanza_.get()); + return STATE_RESPONSE; + } + + virtual int ProcessResponse() { + if (timed_out_) { + session_client_->OnFailedSend(stanza_.get(), NULL); + return STATE_DONE; + } + + const buzz::XmlElement* next = NextStanza(); + if (next == NULL) + return STATE_BLOCKED; + + if (next->Attr(buzz::QN_TYPE) == "result") { + return STATE_DONE; + } else { + session_client_->OnFailedSend(stanza_.get(), next); + return STATE_DONE; + } + } + + virtual bool HandleStanza(const buzz::XmlElement *stanza) { + if (!MatchResponseIq(stanza, buzz::Jid(stanza_->Attr(buzz::QN_TO)), task_id())) + return false; + if (stanza->Attr(buzz::QN_TYPE) == "result" || + stanza->Attr(buzz::QN_TYPE) == "error") { + QueueStanza(stanza); + return true; + } + return false; + } + +private: + SessionClient *session_client_; + buzz::scoped_ptr stanza_; + bool timed_out_; +}; + +} + +#endif // _CRICKET_PHONE_SESSIONSENDTASK_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/Makefile.am new file mode 100644 index 00000000..3186245a --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=mediastreamer diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/Makefile.am new file mode 100644 index 00000000..268a52fe --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/Makefile.am @@ -0,0 +1,92 @@ +EXTRA_DIST=Makefile.ms +noinst_LTLIBRARIES = libmediastreamer.la +libmediastreamer_la_SOURCES=msfilter.c msfilter.h msutils.h waveheader.h\ + mscodec.c mscodec.h \ + mssoundread.c mssoundread.h \ + mssoundwrite.c mssoundwrite.h \ + msbuffer.c msbuffer.h \ + msqueue.c msqueue.h \ + msfifo.c msfifo.h \ + ms.c ms.h\ + mssync.c mssync.h \ + msnosync.c msnosync.h \ + msread.c msread.h \ + mswrite.c mswrite.h \ + mscopy.c mscopy.h \ + msosswrite.c msosswrite.h \ + msossread.c msossread.h \ + msringplayer.c msringplayer.h \ + msrtprecv.c msrtprecv.h \ + msrtpsend.c msrtpsend.h \ + msAlawenc.c msAlawenc.h g711common.h \ + msAlawdec.c msAlawdec.h g711common.h \ + msMUlawenc.c msMUlawenc.h g711common.h \ + msMUlawdec.c msMUlawdec.h g711common.h \ + mstimer.c mstimer.h \ + msqdispatcher.c msqdispatcher.h \ + msfdispatcher.c msfdispatcher.h \ + sndcard.c sndcard.h \ + osscard.c osscard.h\ + hpuxsndcard.c \ + alsacard.c alsacard.h \ + jackcard.c jackcard.h \ + audiostream.c mediastream.h \ + msspeexenc.c msspeexenc.h msspeexdec.c msspeexdec.h \ + msilbcdec.c msilbcdec.h msilbcenc.c msilbcenc.h + +noinst_HEADERS = affine.h \ + msAlawenc.h \ + msfdispatcher.h \ + msilbcdec.h \ + msnosync.h \ + msringplayer.h \ + msspeexdec.h \ + msutils.h \ + waveheader.h \ + alsacard.h \ + msavdecoder.h \ + msfifo.h \ + msilbcenc.h \ + msossread.h \ + msrtprecv.h \ + msspeexenc.h \ + msv4l.h \ + g711common.h \ + msavencoder.h \ + msfilter.h \ + msLPC10decoder.h \ + msosswrite.h \ + msrtpsend.h \ + mssync.h \ + msvideosource.h \ + jackcard.h \ + msbuffer.h \ + msGSMdecoder.h \ + msLPC10encoder.h \ + msqdispatcher.h \ + mssdlout.h \ + mstimer.h \ + mswrite.h \ + mediastream.h \ + mscodec.h \ + msGSMencoder.h \ + msMUlawdec.h \ + msqueue.h \ + mssoundread.h \ + mstruespeechdecoder.h \ + osscard.h \ + msAlawdec.h \ + mscopy.h \ + ms.h \ + msMUlawenc.h \ + msread.h \ + mssoundwrite.h \ + mstruespeechencoder.h \ + sndcard.h + + +libmediastreamer_la_LIBADD= $(GLIB_LIBS) $(ORTP_LIBS) $(SPEEX_LIBS) + +AM_CFLAGS=$(GLIB_CFLAGS) -DG_LOG_DOMAIN=\"MediaStreamer\" $(ORTP_CFLAGS) $(IPV6_CFLAGS) $(ILBC_CFLAGS) $(SPEEX_CFLAGS) + +INCLUDES= -I$(srcdir)/../../.. $(ORTP_CFLAGS) diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/Makefile.ms b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/Makefile.ms new file mode 100644 index 00000000..8b7427c3 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/Makefile.ms @@ -0,0 +1,34 @@ + +OBJEXT=o +AR = ar +RANLIB = ranlib +DEFS= -DG_LOG_DOMAIN=\"MediaStreamer\" +INCLUDES=-I/usr/local/include/glib-2.0 -I/usr/local/lib/glib-2.0/include/ \ + -I../gsmlib/ -I../lpc10-1.5 -I../oRTP +COMPILE= gcc $(DEFS) $(INCLUDES) +LIBTOOL=libtool +LDFLAGS=-L/usr/local/lib/ -lglib-1.3 -lgthread-1.3 -lpthread +LINK = $(LIBTOOL) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ + +libmediastreamer_a_OBJECTS = msfilter.$(OBJEXT) msbuffer.$(OBJEXT) \ +msqueue.$(OBJEXT) msfifo.$(OBJEXT) ms.$(OBJEXT) mssync.$(OBJEXT) \ +msnosync.$(OBJEXT) msread.$(OBJEXT) mswrite.$(OBJEXT) mscopy.$(OBJEXT) \ +msv4lsource.$(OBJEXT) msoss.$(OBJEXT) msosswrite.$(OBJEXT) \ +msossread.$(OBJEXT) msringplayer.$(OBJEXT) msGSMencoder.$(OBJEXT) \ +msGSMdecoder.$(OBJEXT) msLPC10encoder.$(OBJEXT) \ +msLPC10decoder.$(OBJEXT) + +all: libmediastreamer.a mstest + + +.c.o: + $(COMPILE) -c $< + +libmediastreamer.a: $(libmediastreamer_a_OBJECTS) + -rm -f libmediastreamer.a + $(AR) cru libmediastreamer.a $(libmediastreamer_a_OBJECTS) + $(RANLIB) libmediastreamer.a + + +mstest: test.o libmediastreamer.a + gcc -o mstest test.o libmediastreamer.a $(LDFLAGS) -Wl,-rpath /usr/local/lib diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/README b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/README new file mode 100644 index 00000000..1309f534 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/README @@ -0,0 +1,3 @@ +Mediastreamer is the library that handle all media operations: rtp streaming +from file, from soundcard, with codec transcoding, and vice-versa;-). +And also video streaming in the future. diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/affine.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/affine.h new file mode 100644 index 00000000..620fdc9d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/affine.h @@ -0,0 +1,43 @@ +/* + * affine.h -- Affine Transforms for 2d objects + * Copyright (C) 2002 Charles Yates + * Portions Copyright (C) 2003 Dan Dennedy + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 _AFFINE_H +#define _AFFINE_H + +#include + +/** Affine transforms for 2d image manipulation. Current provides shearing and + rotating support. +*/ + +typedef struct { + double matrix[2][2]; +} affine_transform_t; + +void affine_transform_init( affine_transform_t *this ); +void affine_transform_rotate( affine_transform_t *this, double angle ); +void affine_transform_shear( affine_transform_t *this, double shear ); +void affine_transform_scale( affine_transform_t *this, double sx, double sy ); +double affine_transform_mapx( affine_transform_t *this, int x, int y ); +double affine_transform_mapy( affine_transform_t *this, int x, int y ); +void affine_scale( const unsigned char *src, unsigned char *dest, int src_width, int src_height, int dest_width, int dest_height, int bpp ); + +#endif + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/alsacard.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/alsacard.c new file mode 100644 index 00000000..c240aa72 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/alsacard.c @@ -0,0 +1,640 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "alsacard.h" + +#ifdef HAVE_ALSA_ASOUNDLIB_H + +static gchar *over_pcmdev=NULL; + +#include "msossread.h" +#include "msosswrite.h" + +#include + +int __alsa_card_write(AlsaCard *obj,char *buf,int size); + +int alsa_set_params(AlsaCard *obj, int rw, int bits, int stereo, int rate) +{ + snd_pcm_hw_params_t *hwparams=NULL; + snd_pcm_sw_params_t *swparams=NULL; + snd_pcm_t *pcm_handle; + gint dir,exact_value; + gint channels; + gint fsize=0; + gint periods=8; + gint periodsize=256; + gint err; + int format; + + if (rw) { + pcm_handle=obj->write_handle; + } + else pcm_handle=obj->read_handle; + + /* Allocate the snd_pcm_hw_params_t structure on the stack. */ + snd_pcm_hw_params_alloca(&hwparams); + + /* Init hwparams with full configuration space */ + if (snd_pcm_hw_params_any(pcm_handle, hwparams) < 0) { + g_warning("alsa_set_params: Cannot configure this PCM device.\n"); + return(-1); + } + + if (snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { + g_warning("alsa_set_params: Error setting access.\n"); + return(-1); + } + /* Set sample format */ +#ifdef WORDS_BIGENDIAN + format=SND_PCM_FORMAT_S16_BE; +#else + format=SND_PCM_FORMAT_S16_LE; +#endif + if (snd_pcm_hw_params_set_format(pcm_handle, hwparams, format) < 0) { + g_warning("alsa_set_params: Error setting format.\n"); + return(-1); + } + /* Set number of channels */ + if (stereo) channels=2; + else channels=1; + if (snd_pcm_hw_params_set_channels(pcm_handle, hwparams, channels) < 0) { + g_warning("alsa_set_params: Error setting channels.\n"); + return(-1); + } + /* Set sample rate. If the exact rate is not supported */ + /* by the hardware, use nearest possible rate. */ + exact_value=rate; + dir=0; + if ((err=snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &exact_value, &dir))<0){ + g_warning("alsa_set_params: Error setting rate to %i:%s",rate,snd_strerror(err)); + return -1; + } + if (dir != 0) { + g_warning("alsa_set_params: The rate %d Hz is not supported by your hardware.\n " + "==> Using %d Hz instead.\n", rate, exact_value); + } + /* choose greater period size when rate is high */ + periodsize=periodsize*(rate/8000); + + /* Set buffer size (in frames). The resulting latency is given by */ + /* latency = periodsize * periods / (rate * bytes_per_frame) */ + /* + fsize=periodsize * periods; + exact_value=fsize; + if ((err=snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams,&exact_value)) < 0) { + g_warning("alsa_set_params: Error setting buffer size:%s",snd_strerror(err)); + return(-1); + } + if (fsize!= exact_value) { + g_warning("alsa_set_params: The buffer size %d is not supported by your hardware.\n " + "==> Using %d instead.\n", fsize, exact_value); + } + */ + /* set period size */ + exact_value=periodsize; + dir=0; + if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &exact_value, &dir) < 0) { + g_warning("alsa_set_params: Error setting period size.\n"); + return(-1); + } + if (dir != 0) { + g_warning("alsa_set_params: The period size %d is not supported by your hardware.\n " + "==> Using %d instead.\n", periodsize, exact_value); + } + periodsize=exact_value; + /* Set number of periods. Periods used to be called fragments. */ + exact_value=periods; + dir=0; + if (snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &exact_value, &dir) < 0) { + g_warning("alsa_set_params: Error setting periods.\n"); + return(-1); + } + if (dir != 0) { + g_warning("alsa_set_params: The number of periods %d is not supported by your hardware.\n " + "==> Using %d instead.\n", periods, exact_value); + } + /* Apply HW parameter settings to */ + /* PCM device and prepare device */ + if ((err=snd_pcm_hw_params(pcm_handle, hwparams)) < 0) { + g_warning("alsa_set_params: Error setting HW params:%s",snd_strerror(err)); + return(-1); + } + /*prepare sw params */ + if (rw){ + snd_pcm_sw_params_alloca(&swparams); + snd_pcm_sw_params_current(pcm_handle, swparams); + if ((err=snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams,periodsize*2 ))<0){ + g_warning("alsa_set_params: Error setting start threshold:%s",snd_strerror(err)); + return -1; + } + if ((err=snd_pcm_sw_params(pcm_handle, swparams))<0){ + g_warning("alsa_set_params: Error setting SW params:%s",snd_strerror(err)); + return(-1); + } + } + obj->frame_size=channels*(bits/8); + SND_CARD(obj)->bsize=periodsize*obj->frame_size; + /* //SND_CARD(obj)->bsize=4096; */ + obj->frames=periodsize; + g_message("alsa_set_params: blocksize=%i.",SND_CARD(obj)->bsize); + return SND_CARD(obj)->bsize; +} + +int alsa_card_open_r(AlsaCard *obj,int bits,int stereo,int rate) +{ + int bsize; + int err; + snd_pcm_t *pcm_handle; + gchar *pcmdev; + if (over_pcmdev!=NULL) pcmdev=over_pcmdev; + else pcmdev=obj->pcmdev; + + if (snd_pcm_open(&pcm_handle, pcmdev,SND_PCM_STREAM_CAPTURE,SND_PCM_NONBLOCK) < 0) { + g_warning("alsa_card_open_r: Error opening PCM device %s\n",obj->pcmdev ); + return -1; + } + g_return_val_if_fail(pcm_handle!=NULL,-1); + obj->read_handle=pcm_handle; + if ((bsize=alsa_set_params(obj,0,bits,stereo,rate))<0){ + snd_pcm_close(pcm_handle); + obj->read_handle=NULL; + return -1; + } + obj->readbuf=g_malloc0(bsize); + + err=snd_pcm_start(obj->read_handle); + if (err<0){ + g_warning("Cannot start read pcm: %s", snd_strerror(err)); + } + obj->readpos=0; + SND_CARD(obj)->bsize=bsize; + SND_CARD(obj)->flags|=SND_CARD_FLAGS_OPENED; + return 0; +} + +int alsa_card_open_w(AlsaCard *obj,int bits,int stereo,int rate) +{ + int err,bsize; + snd_pcm_t *pcm_handle; + gchar *pcmdev; + if (over_pcmdev!=NULL) pcmdev=over_pcmdev; + else pcmdev=obj->pcmdev; + + if (snd_pcm_open(&pcm_handle, pcmdev,SND_PCM_STREAM_PLAYBACK,SND_PCM_NONBLOCK) < 0) { + g_warning("alsa_card_open_w: Error opening PCM device %s\n", obj->pcmdev); + return -1; + } + obj->write_handle=pcm_handle; + if ((bsize=alsa_set_params(obj,1,bits,stereo,rate))<0){ + snd_pcm_close(pcm_handle); + obj->write_handle=NULL; + return -1; + } + obj->writebuf=g_malloc0(bsize); + + obj->writepos=0; + SND_CARD(obj)->bsize=bsize; + SND_CARD(obj)->flags|=SND_CARD_FLAGS_OPENED; + return 0; +} + + +void alsa_card_set_blocking_mode(AlsaCard *obj, gboolean yesno){ + if (obj->read_handle!=NULL) snd_pcm_nonblock(obj->read_handle,!yesno); + if (obj->write_handle!=NULL) snd_pcm_nonblock(obj->write_handle,!yesno); +} + +void alsa_card_close_r(AlsaCard *obj) +{ + if (obj->read_handle!=NULL){ + snd_pcm_close(obj->read_handle); + obj->read_handle=NULL; + g_free(obj->readbuf); + obj->readbuf=NULL; + } +} + +void alsa_card_close_w(AlsaCard *obj) +{ + if (obj->write_handle!=NULL){ + snd_pcm_close(obj->write_handle); + obj->write_handle=NULL; + g_free(obj->writebuf); + obj->writebuf=NULL; + } +} + +int alsa_card_probe(AlsaCard *obj,int bits,int stereo,int rate) +{ + int ret; + ret=alsa_card_open_w(obj,bits,stereo,rate); + if (ret<0) return -1; + ret=SND_CARD(obj)->bsize; + alsa_card_close_w(obj); + return ret; +} + + +void alsa_card_destroy(AlsaCard *obj) +{ + snd_card_uninit(SND_CARD(obj)); + g_free(obj->pcmdev); + if (obj->readbuf!=0) g_free(obj->readbuf); + if (obj->writebuf!=0) g_free(obj->writebuf); +} + +gboolean alsa_card_can_read(AlsaCard *obj) +{ + int frames; + g_return_val_if_fail(obj->read_handle!=NULL,0); + if (obj->readpos!=0) return TRUE; + if ( frames=snd_pcm_avail_update(obj->read_handle)>=obj->frames) return 1; + /* //g_message("frames=%i",frames); */ + return 0; +} + + + +int __alsa_card_read(AlsaCard *obj,char *buf,int bsize) +{ + int err; + sigset_t set; + sigemptyset(&set); + sigaddset(&set,SIGALRM); + sigprocmask(SIG_BLOCK,&set,NULL); + err=snd_pcm_readi(obj->read_handle,buf,bsize/obj->frame_size); + if (err<0) { + if (err!=-EPIPE){ + g_warning("alsa_card_read: snd_pcm_readi() failed:%s.",snd_strerror(err)); + } + snd_pcm_prepare(obj->read_handle); + err=snd_pcm_readi(obj->read_handle,buf,bsize/obj->frame_size); + if (err<0) g_warning("alsa_card_read: snd_pcm_readi() failed:%s.",snd_strerror(err)); + } + sigprocmask(SIG_UNBLOCK,&set,NULL); + return err*obj->frame_size; +} + +int alsa_card_read(AlsaCard *obj,char *buf,int size) +{ + int err; + gint bsize=SND_CARD(obj)->bsize; + g_return_val_if_fail(obj->read_handle!=NULL,-1); + if (sizereadpos,size); + + if (obj->readpos==0){ + err=__alsa_card_read(obj,obj->readbuf,bsize); + } + + memcpy(buf,&obj->readbuf[obj->readpos],canread); + obj->readpos+=canread; + if (obj->readpos>=bsize) obj->readpos=0; + return canread; + }else{ + err=__alsa_card_read(obj,buf,size); + return err; + } + +} + +int __alsa_card_write(AlsaCard *obj,char *buf,int size) +{ + int err; + sigset_t set; + sigemptyset(&set); + sigaddset(&set,SIGALRM); + sigprocmask(SIG_BLOCK,&set,NULL); + if ((err=snd_pcm_writei(obj->write_handle,buf,size/obj->frame_size))<0){ + if (err!=-EPIPE){ + g_warning("alsa_card_write: snd_pcm_writei() failed:%s.",snd_strerror(err)); + } + snd_pcm_prepare(obj->write_handle); + err=snd_pcm_writei(obj->write_handle,buf,size/obj->frame_size); + if (err<0) g_warning("alsa_card_write: Error writing sound buffer (size=%i):%s",size,snd_strerror(err)); + + } + sigprocmask(SIG_UNBLOCK,&set,NULL); + return err; +} + +int alsa_card_write(AlsaCard *obj,char *buf,int size) +{ + int err; + gint bsize=SND_CARD(obj)->bsize; + g_return_val_if_fail(obj->write_handle!=NULL,-1); + if (sizewritepos,size); + memcpy(&obj->writebuf[obj->writepos],buf,canwrite); + obj->writepos+=canwrite; + if (obj->writepos>=bsize){ + err=__alsa_card_write(obj,obj->writebuf,bsize); + obj->writepos=0; + } + return canwrite; + }else{ + return __alsa_card_write(obj,buf,bsize); + } +} + +snd_mixer_t *alsa_mixer_open(AlsaCard *obj){ + snd_mixer_t *mixer=NULL; + int err; + err=snd_mixer_open(&mixer,0); + if (err<0){ + g_warning("Could not open alsa mixer: %s",snd_strerror(err)); + return NULL; + } + if ((err = snd_mixer_attach (mixer, obj->mixdev)) < 0){ + g_warning("Could not attach mixer to card: %s",snd_strerror(err)); + snd_mixer_close(mixer); + return NULL; + } + if ((err = snd_mixer_selem_register (mixer, NULL, NULL)) < 0){ + g_warning("snd_mixer_selem_register: %s",snd_strerror(err)); + snd_mixer_close(mixer); + return NULL; + } + if ((err = snd_mixer_load (mixer)) < 0){ + g_warning("snd_mixer_load: %s",snd_strerror(err)); + snd_mixer_close(mixer); + return NULL; + } + obj->mixer=mixer; + return mixer; +} + +void alsa_mixer_close(AlsaCard *obj){ + snd_mixer_close(obj->mixer); + obj->mixer=NULL; +} + +typedef enum {CAPTURE, PLAYBACK, CAPTURE_SWITCH, PLAYBACK_SWITCH} MixerAction; + +static gint get_mixer_element(snd_mixer_t *mixer,const char *name, MixerAction action){ + long value=0; + const char *elemname; + snd_mixer_elem_t *elem; + int err; + long sndMixerPMin; + long sndMixerPMax; + long newvol; + elem=snd_mixer_first_elem(mixer); + while (elem!=NULL){ + elemname=snd_mixer_selem_get_name(elem); + /* //g_message("Found alsa mixer element %s.",elemname); */ + if (strcmp(elemname,name)==0){ + switch (action){ + case CAPTURE: + if (snd_mixer_selem_has_capture_volume(elem)){ + snd_mixer_selem_get_playback_volume_range(elem, &sndMixerPMin, &sndMixerPMax); + err=snd_mixer_selem_get_capture_volume(elem,SND_MIXER_SCHN_UNKNOWN,&newvol); + newvol-=sndMixerPMin; + value=(100*newvol)/(sndMixerPMax-sndMixerPMin); + if (err<0) g_warning("Could not get capture volume for %s:%s",name,snd_strerror(err)); + /* //else g_message("Succesfully get capture level for %s.",elemname); */ + break; + } + break; + case PLAYBACK: + if (snd_mixer_selem_has_playback_volume(elem)){ + snd_mixer_selem_get_playback_volume_range(elem, &sndMixerPMin, &sndMixerPMax); + err=snd_mixer_selem_get_playback_volume(elem,SND_MIXER_SCHN_FRONT_LEFT,&newvol); + newvol-=sndMixerPMin; + value=(100*newvol)/(sndMixerPMax-sndMixerPMin); + if (err<0) g_warning("Could not get playback volume for %s:%s",name,snd_strerror(err)); + /* //else g_message("Succesfully get playback level for %s.",elemname); */ + break; + } + break; + case CAPTURE_SWITCH: + + break; + } + } + elem=snd_mixer_elem_next(elem); + } + + return value; +} + + +static void set_mixer_element(snd_mixer_t *mixer,const char *name, gint level,MixerAction action){ + const char *elemname; + snd_mixer_elem_t *elem; + int tmp; + long sndMixerPMin; + long sndMixerPMax; + long newvol; + + elem=snd_mixer_first_elem(mixer); + + while (elem!=NULL){ + elemname=snd_mixer_selem_get_name(elem); + /* //g_message("Found alsa mixer element %s.",elemname); */ + if (strcmp(elemname,name)==0){ + switch(action){ + case CAPTURE: + if (snd_mixer_selem_has_capture_volume(elem)){ + snd_mixer_selem_get_playback_volume_range(elem, &sndMixerPMin, &sndMixerPMax); + newvol=(((sndMixerPMax-sndMixerPMin)*level)/100)+sndMixerPMin; + snd_mixer_selem_set_capture_volume_all(elem,newvol); + /* //g_message("Succesfully set capture level for %s.",elemname); */ + return; + } + break; + case PLAYBACK: + if (snd_mixer_selem_has_playback_volume(elem)){ + snd_mixer_selem_get_playback_volume_range(elem, &sndMixerPMin, &sndMixerPMax); + newvol=(((sndMixerPMax-sndMixerPMin)*level)/100)+sndMixerPMin; + snd_mixer_selem_set_playback_volume_all(elem,newvol); + /* //g_message("Succesfully set playback level for %s.",elemname); */ + return; + } + break; + case CAPTURE_SWITCH: + if (snd_mixer_selem_has_capture_switch(elem)){ + snd_mixer_selem_set_capture_switch_all(elem,level); + /* //g_message("Succesfully set capture switch for %s.",elemname); */ + } + break; + case PLAYBACK_SWITCH: + if (snd_mixer_selem_has_playback_switch(elem)){ + snd_mixer_selem_set_playback_switch_all(elem,level); + /* //g_message("Succesfully set capture switch for %s.",elemname); */ + } + break; + + } + } + elem=snd_mixer_elem_next(elem); + } + + return ; +} + + +void alsa_card_set_level(AlsaCard *obj,gint way,gint a) +{ + snd_mixer_t *mixer; + mixer=alsa_mixer_open(obj); + if (mixer==NULL) return ; + switch(way){ + case SND_CARD_LEVEL_GENERAL: + set_mixer_element(mixer,"Master",a,PLAYBACK); + break; + case SND_CARD_LEVEL_INPUT: + set_mixer_element(mixer,"Capture",a,CAPTURE); + break; + case SND_CARD_LEVEL_OUTPUT: + set_mixer_element(mixer,"PCM",a,PLAYBACK); + break; + default: + g_warning("oss_card_set_level: unsupported command."); + } + alsa_mixer_close(obj); +} + +gint alsa_card_get_level(AlsaCard *obj,gint way) +{ + snd_mixer_t *mixer; + gint value; + mixer=alsa_mixer_open(obj); + if (mixer==NULL) return 0; + switch(way){ + case SND_CARD_LEVEL_GENERAL: + value=get_mixer_element(mixer,"Master",PLAYBACK); + break; + case SND_CARD_LEVEL_INPUT: + value=get_mixer_element(mixer,"Capture",CAPTURE); + break; + case SND_CARD_LEVEL_OUTPUT: + value=get_mixer_element(mixer,"PCM",PLAYBACK); + break; + default: + g_warning("oss_card_set_level: unsupported command."); + } + alsa_mixer_close(obj); + return value; +} + +void alsa_card_set_source(AlsaCard *obj,int source) +{ + snd_mixer_t *mixer; + mixer=alsa_mixer_open(obj); + if (mixer==NULL) return; + switch (source){ + case 'm': + set_mixer_element(mixer,"Mic",1,CAPTURE_SWITCH); + set_mixer_element(mixer,"Capture",1,CAPTURE_SWITCH); + break; + case 'l': + set_mixer_element(mixer,"Line",1,CAPTURE_SWITCH); + set_mixer_element(mixer,"Capture",1,CAPTURE_SWITCH); + break; + } +} + +MSFilter *alsa_card_create_read_filter(AlsaCard *card) +{ + MSFilter *f=ms_oss_read_new(); + ms_oss_read_set_device(MS_OSS_READ(f),SND_CARD(card)->index); + return f; +} + +MSFilter *alsa_card_create_write_filter(AlsaCard *card) +{ + MSFilter *f=ms_oss_write_new(); + ms_oss_write_set_device(MS_OSS_WRITE(f),SND_CARD(card)->index); + return f; +} + + +SndCard * alsa_card_new(gint devid) +{ + AlsaCard * obj; + SndCard *base; + int err; + gchar *name=NULL; + + /* carefull: this is an alsalib call despite its name! */ + err=snd_card_get_name(devid,&name); + if (err<0) { + return NULL; + } + obj= g_new0(AlsaCard,1); + base= SND_CARD(obj); + snd_card_init(base); + + base->card_name=g_strdup_printf("%s (Advanced Linux Sound Architecture)",name); + base->_probe=(SndCardOpenFunc)alsa_card_probe; + base->_open_r=(SndCardOpenFunc)alsa_card_open_r; + base->_open_w=(SndCardOpenFunc)alsa_card_open_w; + base->_can_read=(SndCardPollFunc)alsa_card_can_read; + base->_set_blocking_mode=(SndCardSetBlockingModeFunc)alsa_card_set_blocking_mode; + base->_read=(SndCardIOFunc)alsa_card_read; + base->_write=(SndCardIOFunc)alsa_card_write; + base->_close_r=(SndCardCloseFunc)alsa_card_close_r; + base->_close_w=(SndCardCloseFunc)alsa_card_close_w; + base->_set_rec_source=(SndCardMixerSetRecSourceFunc)alsa_card_set_source; + base->_set_level=(SndCardMixerSetLevelFunc)alsa_card_set_level; + base->_get_level=(SndCardMixerGetLevelFunc)alsa_card_get_level; + base->_destroy=(SndCardDestroyFunc)alsa_card_destroy; + base->_create_read_filter=(SndCardCreateFilterFunc)alsa_card_create_read_filter; + base->_create_write_filter=(SndCardCreateFilterFunc)alsa_card_create_write_filter; + + + obj->pcmdev=g_strdup_printf("plughw:%i,0",devid); + obj->mixdev=g_strdup_printf("hw:%i",devid); + obj->readbuf=NULL; + obj->writebuf=NULL; + return base; +} + + +gint alsa_card_manager_init(SndCardManager *m, gint index) +{ + gint devindex; + gint i; + gint found=0; + gchar *name=NULL; + for(devindex=0;indexcards[index]=alsa_card_new(devindex); + m->cards[index]->index=index; + found++; + index++; + } + } + return found; +} + +void alsa_card_manager_set_default_pcm_device(const gchar *pcmdev){ + if (over_pcmdev!=NULL){ + g_free(over_pcmdev); + } + over_pcmdev=g_strdup(pcmdev); +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/alsacard.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/alsacard.h new file mode 100644 index 00000000..df3372fb --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/alsacard.h @@ -0,0 +1,50 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include + +#ifdef HAVE_ALSA_ASOUNDLIB_H + +#include "sndcard.h" +#define ALSA_PCM_NEW_HW_PARAMS_API +#include +struct _AlsaCard +{ + SndCard parent; + gchar *pcmdev; + gchar *mixdev; + snd_pcm_t *read_handle; + snd_pcm_t *write_handle; + gint frame_size; + gint frames; + gchar *readbuf; + gint readpos; + gchar *writebuf; + gint writepos; + snd_mixer_t *mixer; +}; + +typedef struct _AlsaCard AlsaCard; + +SndCard *alsa_card_new(gint dev_id); +gint alsa_card_manager_init(SndCardManager *m, gint index); +void alsa_card_manager_set_default_pcm_device(const gchar *pcmdev); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/audiostream.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/audiostream.c new file mode 100644 index 00000000..f4ff4867 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/audiostream.c @@ -0,0 +1,343 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "mediastream.h" +#ifdef INET6 + #include + #include + #include +#endif + + +#define MAX_RTP_SIZE 1500 + +/* this code is not part of the library itself, it is part of the mediastream program */ +void audio_stream_free(AudioStream *stream) +{ + RtpSession *s; + RtpSession *destroyed=NULL; + if (stream->rtprecv!=NULL) { + s=ms_rtp_recv_get_session(MS_RTP_RECV(stream->rtprecv)); + if (s!=NULL){ + destroyed=s; + rtp_session_destroy(s); + } + ms_filter_destroy(stream->rtprecv); + } + if (stream->rtpsend!=NULL) { + s=ms_rtp_send_get_session(MS_RTP_SEND(stream->rtpsend)); + if (s!=NULL){ + if (s!=destroyed) + rtp_session_destroy(s); + } + ms_filter_destroy(stream->rtpsend); + } + if (stream->soundread!=NULL) ms_filter_destroy(stream->soundread); + if (stream->soundwrite!=NULL) ms_filter_destroy(stream->soundwrite); + if (stream->encoder!=NULL) ms_filter_destroy(stream->encoder); + if (stream->decoder!=NULL) ms_filter_destroy(stream->decoder); + if (stream->timer!=NULL) ms_sync_destroy(stream->timer); + g_free(stream); +} + +static int dtmf_tab[16]={'0','1','2','3','4','5','6','7','8','9','*','#','A','B','C','D'}; + +static void on_dtmf_received(RtpSession *s,gint dtmf,gpointer user_data) +{ + AudioStream *stream=(AudioStream*)user_data; + if (dtmf>15){ + g_warning("Unsupported telephone-event type."); + return; + } + g_message("Receiving dtmf %c.",dtmf_tab[dtmf]); + if (stream!=NULL){ + if (strcmp(stream->soundwrite->klass->name,"OssWrite")==0) + ms_oss_write_play_dtmf(MS_OSS_WRITE(stream->soundwrite),dtmf_tab[dtmf]); + } +} + +static void on_timestamp_jump(RtpSession *s,guint32* ts, gpointer user_data) +{ + g_warning("The remote sip-phone has send data with a future timestamp: %u," + "resynchronising session.",*ts); + rtp_session_reset(s); +} + +static const char *ip4local="0.0.0.0"; +static const char *ip6local="::"; + +const char *get_local_addr_for(const char *remote) +{ + const char *ret; +#ifdef INET6 + char num[8]; + struct addrinfo hints, *res0; + int err; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + err = getaddrinfo(remote,"8000", &hints, &res0); + if (err!=0) { + g_warning ("get_local_addr_for: %s", gai_strerror(err)); + return ip4local; + } + ret=(res0->ai_addr->sa_family==AF_INET6) ? ip6local : ip4local; + freeaddrinfo(res0); +#else + ret=ip4local; +#endif + return ret; +} + +void create_duplex_rtpsession(RtpProfile *profile, int locport,char *remip,int remport, + int payload,int jitt_comp, + RtpSession **recvsend){ + RtpSession *rtpr; + rtpr=rtp_session_new(RTP_SESSION_SENDRECV); + rtp_session_max_buf_size_set(rtpr,MAX_RTP_SIZE); + rtp_session_set_profile(rtpr,profile); + rtp_session_set_local_addr(rtpr,get_local_addr_for(remip),locport); + if (remport>0) rtp_session_set_remote_addr(rtpr,remip,remport); + rtp_session_set_scheduling_mode(rtpr,0); + rtp_session_set_blocking_mode(rtpr,0); + rtp_session_set_payload_type(rtpr,payload); + rtp_session_set_jitter_compensation(rtpr,jitt_comp); + rtp_session_enable_adaptive_jitter_compensation(rtpr,TRUE); + /*rtp_session_signal_connect(rtpr,"timestamp_jump",(RtpCallback)on_timestamp_jump,NULL);*/ + *recvsend=rtpr; +} + +void create_rtp_sessions(RtpProfile *profile, int locport,char *remip,int remport, + int payload,int jitt_comp, + RtpSession **recv, RtpSession **send){ + RtpSession *rtps,*rtpr; + PayloadType *pt; + /* creates two rtp filters to recv send streams (remote part)*/ + + rtps=rtp_session_new(RTP_SESSION_SENDONLY); + rtp_session_max_buf_size_set(rtps,MAX_RTP_SIZE); + rtp_session_set_profile(rtps,profile); +#ifdef INET6 + rtp_session_set_local_addr(rtps,"::",locport+2); +#else + rtp_session_set_local_addr(rtps,"0.0.0.0",locport+2); +#endif + rtp_session_set_remote_addr(rtps,remip,remport); + rtp_session_set_scheduling_mode(rtps,0); + rtp_session_set_blocking_mode(rtps,0); + rtp_session_set_payload_type(rtps,payload); + rtp_session_set_jitter_compensation(rtps,jitt_comp); + + rtpr=rtp_session_new(RTP_SESSION_RECVONLY); + rtp_session_max_buf_size_set(rtpr,MAX_RTP_SIZE); + rtp_session_set_profile(rtpr,profile); +#ifdef INET6 + rtp_session_set_local_addr(rtpr,"::",locport); +#else + rtp_session_set_local_addr(rtpr,"0.0.0.0",locport); +#endif + rtp_session_set_scheduling_mode(rtpr,0); + rtp_session_set_blocking_mode(rtpr,0); + rtp_session_set_payload_type(rtpr,payload); + rtp_session_set_jitter_compensation(rtpr,jitt_comp); + rtp_session_signal_connect(rtpr,"telephone-event",(RtpCallback)on_dtmf_received,NULL); + rtp_session_signal_connect(rtpr,"timestamp_jump",(RtpCallback)on_timestamp_jump,NULL); + *recv=rtpr; + *send=rtps; + +} + + +AudioStream * audio_stream_start_full(RtpProfile *profile, int locport,char *remip,int remport, + int payload,int jitt_comp, gchar *infile, gchar *outfile, SndCard *playcard, SndCard *captcard) +{ + AudioStream *stream=g_new0(AudioStream,1); + RtpSession *rtps,*rtpr; + PayloadType *pt; + + /* //create_rtp_sessions(profile,locport,remip,remport,payload,jitt_comp,&rtpr,&rtps); */ + + create_duplex_rtpsession(profile,locport,remip,remport,payload,jitt_comp,&rtpr); + rtp_session_signal_connect(rtpr,"telephone-event",(RtpCallback)on_dtmf_received,(gpointer)stream); + rtps=rtpr; + + stream->recv_session = rtpr; + stream->send_session = rtps; + stream->rtpsend=ms_rtp_send_new(); + ms_rtp_send_set_session(MS_RTP_SEND(stream->rtpsend),rtps); + stream->rtprecv=ms_rtp_recv_new(); + ms_rtp_recv_set_session(MS_RTP_RECV(stream->rtprecv),rtpr); + + + /* creates the local part */ + if (infile==NULL) stream->soundread=snd_card_create_read_filter(captcard); + else stream->soundread=ms_read_new(infile); + if (outfile==NULL) stream->soundwrite=snd_card_create_write_filter(playcard); + else stream->soundwrite=ms_write_new(outfile); + + /* creates the couple of encoder/decoder */ + pt=rtp_profile_get_payload(profile,payload); + if (pt==NULL){ + g_error("audiostream.c: undefined payload type."); + return NULL; + } + stream->encoder=ms_encoder_new_with_string_id(pt->mime_type); + stream->decoder=ms_decoder_new_with_string_id(pt->mime_type); + if ((stream->encoder==NULL) || (stream->decoder==NULL)){ + /* big problem: we have not a registered codec for this payload...*/ + audio_stream_free(stream); + g_error("mediastream.c: No decoder available for payload %i.",payload); + return NULL; + } + /* give the sound filters some properties */ + ms_filter_set_property(stream->soundread,MS_FILTER_PROPERTY_FREQ,&pt->clock_rate); + ms_filter_set_property(stream->soundwrite,MS_FILTER_PROPERTY_FREQ,&pt->clock_rate); + + /* give the encoder/decoder some parameters*/ + ms_filter_set_property(stream->encoder,MS_FILTER_PROPERTY_FREQ,&pt->clock_rate); + ms_filter_set_property(stream->encoder,MS_FILTER_PROPERTY_BITRATE,&pt->normal_bitrate); + ms_filter_set_property(stream->decoder,MS_FILTER_PROPERTY_FREQ,&pt->clock_rate); + ms_filter_set_property(stream->decoder,MS_FILTER_PROPERTY_BITRATE,&pt->normal_bitrate); + + ms_filter_set_property(stream->encoder,MS_FILTER_PROPERTY_FMTP, (void*)pt->fmtp); + ms_filter_set_property(stream->decoder,MS_FILTER_PROPERTY_FMTP,(void*)pt->fmtp); + /* create the synchronisation source */ + stream->timer=ms_timer_new(); + + /* and then connect all */ + ms_filter_add_link(stream->soundread,stream->encoder); + ms_filter_add_link(stream->encoder,stream->rtpsend); + ms_filter_add_link(stream->rtprecv,stream->decoder); + ms_filter_add_link(stream->decoder,stream->soundwrite); + + ms_sync_attach(stream->timer,stream->soundread); + ms_sync_attach(stream->timer,stream->rtprecv); + + /* and start */ + ms_start(stream->timer); + + return stream; +} + +static int defcard=0; + +void audio_stream_set_default_card(int cardindex){ + defcard=cardindex; +} + +AudioStream * audio_stream_start_with_files(RtpProfile *prof,int locport,char *remip, + int remport,int profile,int jitt_comp,gchar *infile, gchar*outfile) +{ + return audio_stream_start_full(prof,locport,remip,remport,profile,jitt_comp,infile,outfile,NULL,NULL); +} + +AudioStream * audio_stream_start(RtpProfile *prof,int locport,char *remip,int remport,int profile,int jitt_comp) +{ + SndCard *sndcard; + sndcard=snd_card_manager_get_card(snd_card_manager,defcard); + return audio_stream_start_full(prof,locport,remip,remport,profile,jitt_comp,NULL,NULL,sndcard,sndcard); +} + +AudioStream *audio_stream_start_with_sndcards(RtpProfile *prof,int locport,char *remip,int remport,int profile,int jitt_comp,SndCard *playcard, SndCard *captcard) +{ + g_return_val_if_fail(playcard!=NULL,NULL); + g_return_val_if_fail(captcard!=NULL,NULL); + return audio_stream_start_full(prof,locport,remip,remport,profile,jitt_comp,NULL,NULL,playcard,captcard); +} + +void audio_stream_set_rtcp_information(AudioStream *st, const char *cname){ + if (st->send_session!=NULL){ + rtp_session_set_source_description(st->send_session,cname,NULL,NULL,NULL, NULL,"linphone", + "This is free software (GPL) !"); + } +} + +void audio_stream_stop(AudioStream * stream) +{ + + ms_stop(stream->timer); + ortp_global_stats_display(); + ms_sync_detach(stream->timer,stream->soundread); + ms_sync_detach(stream->timer,stream->rtprecv); + + ms_filter_remove_links(stream->soundread,stream->encoder); + ms_filter_remove_links(stream->encoder,stream->rtpsend); + ms_filter_remove_links(stream->rtprecv,stream->decoder); + ms_filter_remove_links(stream->decoder,stream->soundwrite); + + audio_stream_free(stream); +} + +RingStream * ring_start(gchar *file,gint interval,SndCard *sndcard) +{ + return ring_start_with_cb(file,interval,sndcard,NULL,NULL); +} + +RingStream * ring_start_with_cb(gchar *file,gint interval,SndCard *sndcard, MSFilterNotifyFunc func,gpointer user_data) +{ + RingStream *stream; + int tmp; + g_return_val_if_fail(sndcard!=NULL,NULL); + stream=g_new0(RingStream,1); + stream->source=ms_ring_player_new(file,interval); + if (stream->source==NULL) { + g_warning("Could not create ring player. Probably the ring file (%s) does not exist.",file); + return NULL; + } + if (func!=NULL) ms_filter_set_notify_func(MS_FILTER(stream->source),func,user_data); + stream->sndwrite=snd_card_create_write_filter(sndcard); + ms_filter_get_property(stream->source,MS_FILTER_PROPERTY_FREQ,&tmp); + ms_filter_set_property(stream->sndwrite,MS_FILTER_PROPERTY_FREQ,&tmp); + ms_filter_get_property(stream->source,MS_FILTER_PROPERTY_CHANNELS,&tmp); + ms_filter_set_property(stream->sndwrite,MS_FILTER_PROPERTY_CHANNELS,&tmp); + stream->timer=ms_timer_new(); + ms_filter_add_link(stream->source,stream->sndwrite); + ms_sync_attach(stream->timer,stream->source); + ms_start(stream->timer); + return stream; +} + +void ring_stop(RingStream *stream) +{ + ms_stop(stream->timer); + ms_sync_detach(stream->timer,stream->source); + ms_sync_destroy(stream->timer); + ms_filter_remove_links(stream->source,stream->sndwrite); + ms_filter_destroy(stream->source); + ms_filter_destroy(stream->sndwrite); + g_free(stream); +} + +/* returns the latency in samples if the audio device with id dev_id is openable in full duplex mode, else 0 */ +gint test_audio_dev(int dev_id) +{ + gint err; + SndCard *sndcard=snd_card_manager_get_card(snd_card_manager,dev_id); + if (sndcard==NULL) return -1; + err=snd_card_probe(sndcard,16,0,8000); + return err; /* return latency in number of sample */ +} + +gint audio_stream_send_dtmf(AudioStream *stream, gchar dtmf) +{ + ms_rtp_send_dtmf(MS_RTP_SEND(stream->rtpsend), dtmf); + ms_oss_write_play_dtmf(MS_OSS_WRITE(stream->soundwrite),dtmf); +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/g711common.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/g711common.h new file mode 100644 index 00000000..3f5ad16f --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/g711common.h @@ -0,0 +1,171 @@ +/* + * PCM - A-Law conversion + * Copyright (c) 2000 by Abramo Bagnara + * + * Wrapper for linphone Codec class by Simon Morlat + */ + +static inline int val_seg(int val) +{ + int r = 0; + val >>= 7; + if (val & 0xf0) { + val >>= 4; + r += 4; + } + if (val & 0x0c) { + val >>= 2; + r += 2; + } + if (val & 0x02) + r += 1; + return r; +} + +/* + * s16_to_alaw() - Convert a 16-bit linear PCM value to 8-bit A-law + * + * s16_to_alaw() 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. + */ + +static inline unsigned char s16_to_alaw(int pcm_val) +{ + int mask; + int seg; + unsigned char aval; + + if (pcm_val >= 0) { + mask = 0xD5; + } else { + mask = 0x55; + pcm_val = -pcm_val; + if (pcm_val > 0x7fff) + pcm_val = 0x7fff; + } + + if (pcm_val < 256) + aval = pcm_val >> 4; + else { + /* Convert the scaled magnitude to segment number. */ + seg = val_seg(pcm_val); + aval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0x0f); + } + return aval ^ mask; +} + +/* + * alaw_to_s16() - Convert an A-law value to 16-bit linear PCM + * + */ +static inline int alaw_to_s16(unsigned char a_val) +{ + int t; + int seg; + + a_val ^= 0x55; + t = a_val & 0x7f; + if (t < 16) + t = (t << 4) + 8; + else { + seg = (t >> 4) & 0x07; + t = ((t & 0x0f) << 4) + 0x108; + t <<= seg -1; + } + return ((a_val & 0x80) ? t : -t); +} +/* + * s16_to_ulaw() - 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. + */ + +static inline unsigned char s16_to_ulaw(int pcm_val) /* 2's complement (16-bit range) */ +{ + int mask; + int seg; + unsigned char uval; + + if (pcm_val < 0) { + pcm_val = 0x84 - pcm_val; + mask = 0x7f; + } else { + pcm_val += 0x84; + mask = 0xff; + } + if (pcm_val > 0x7fff) + pcm_val = 0x7fff; + + /* Convert the scaled magnitude to segment number. */ + seg = val_seg(pcm_val); + + /* + * Combine the sign, segment, quantization bits; + * and complement the code word. + */ + uval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0x0f); + return uval ^ mask; +} + +/* + * ulaw_to_s16() - 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. + */ +static inline int ulaw_to_s16(unsigned char u_val) +{ + int 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 & 0x0f) << 3) + 0x84; + t <<= (u_val & 0x70) >> 4; + + return ((u_val & 0x80) ? (0x84 - t) : (t - 0x84)); +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/hpuxsndcard.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/hpuxsndcard.c new file mode 100644 index 00000000..8210e29d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/hpuxsndcard.c @@ -0,0 +1,301 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "sndcard.h" +#include "osscard.h" + +#ifdef HAVE_SYS_AUDIO_H +#include + + +#include "msossread.h" +#include "msosswrite.h" + +#include +#include + + +int hpuxsnd_open(HpuxSndCard *obj, int bits,int stereo, int rate) +{ + int fd; + int p=0,cond=0; + int i=0; + int min_size=0,blocksize=512; + /* do a quick non blocking open to be sure that we are not going to be blocked here + for the eternity */ + fd=open(obj->dev_name,O_RDWR|O_NONBLOCK); + if (fd<0) return -EWOULDBLOCK; + close(fd); + /* open the device */ + fd=open(obj->dev_name,O_RDWR); + + g_return_val_if_fail(fd>0,-errno); + + ioctl(fd,AUDIO_RESET,0); + ioctl(fd,AUDIO_SET_SAMPLE_RATE,rate); + ioctl(fd,AUDIO_SET_CHANNELS,stereo); + p=AUDIO_FORMAT_LINEAR16BIT; + ioctl(fd,AUDIO_SET_DATA_FORMAT,p); + /* ioctl(fd,AUDIO_GET_RXBUFSIZE,&min_size); does not work ? */ + min_size=2048; + + g_message("dsp blocksize is %i.",min_size); + obj->fd=fd; + obj->readpos=0; + obj->writepos=0; + SND_CARD(obj)->bits=bits; + SND_CARD(obj)->stereo=stereo; + SND_CARD(obj)->rate=rate; + SND_CARD(obj)->bsize=min_size; + return fd; +} + +int hpux_snd_card_probe(HpuxSndCard *obj,int bits,int stereo,int rate) +{ + return 2048; +} + + +int hpux_snd_card_open(HpuxSndCard *obj,int bits,int stereo,int rate) +{ + int fd; + obj->ref++; + if (obj->fd==0){ + fd=hpuxsnd_open(obj,bits,stereo,rate); + if (fd<0) { + obj->fd=0; + obj->ref--; + return -1; + } + } + SND_CARD(obj)->flags|=SND_CARD_FLAGS_OPENED; + return 0; +} + +void hpux_snd_card_close(HpuxSndCard *obj) +{ + int i; + obj->ref--; + if (obj->ref==0) { + close(obj->fd); + obj->fd=0; + SND_CARD(obj)->flags&=~SND_CARD_FLAGS_OPENED; + + } +} + +void hpux_snd_card_destroy(HpuxSndCard *obj) +{ + snd_card_uninit(SND_CARD(obj)); + g_free(obj->dev_name); + g_free(obj->mixdev_name); +} + +gboolean hpux_snd_card_can_read(HpuxSndCard *obj) +{ + struct timeval tout={0,0}; + int err; + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(obj->fd,&fdset); + err=select(obj->fd+1,&fdset,NULL,NULL,&tout); + if (err>0) return TRUE; + else return FALSE; +} + +int hpux_snd_card_read(HpuxSndCard *obj,char *buf,int size) +{ + int err; + gint bsize=SND_CARD(obj)->bsize; + if (sizereadpos,size); + if (obj->readbuf==NULL) obj->readbuf=g_malloc0(bsize); + if (obj->readpos==0){ + err=read(obj->fd,obj->readbuf,bsize); + if (err<0) { + g_warning("hpux_snd_card_read: read() failed:%s.",strerror(errno)); + return -1; + } + } + + memcpy(buf,&obj->readbuf[obj->readpos],canread); + obj->readpos+=canread; + if (obj->readpos>=bsize) obj->readpos=0; + return canread; + }else{ + err=read(obj->fd,buf,size); + if (err<0) { + g_warning("hpux_snd_card_read: read-2() failed:%s.",strerror(errno)); + } + return err; + } + +} + +int hpux_snd_card_write(HpuxSndCard *obj,char *buf,int size) +{ + int err; + gint bsize=SND_CARD(obj)->bsize; + if (sizewritepos,size); + if (obj->writebuf==NULL) obj->writebuf=g_malloc0(bsize); + + memcpy(&obj->writebuf[obj->writepos],buf,canwrite); + obj->writepos+=canwrite; + if (obj->writepos>=bsize){ + err=write(obj->fd,obj->writebuf,bsize); + } + return canwrite; + }else{ + return write(obj->fd,buf,bsize); + } +} + +#define SND_CARD_LEVEL_TO_HPUX_LEVEL(a) (((a)*2) - 100) +#define HPUX_LEVEL_TO_SND_CARD_LEVEL(a) (((a)+200)/2) +void hpux_snd_card_set_level(HpuxSndCard *obj,gint way,gint a) +{ + struct audio_gain gain; + int error,mix_fd; + + g_return_if_fail(obj->mixdev_name!=NULL); + memset(&gain,0,sizeof(struct audio_gain)); + switch(way){ + case SND_CARD_LEVEL_GENERAL: + gain.cgain[0].monitor_gain=SND_CARD_LEVEL_TO_HPUX_LEVEL(a); + gain.cgain[1].monitor_gain=SND_CARD_LEVEL_TO_HPUX_LEVEL(a); + break; + case SND_CARD_LEVEL_INPUT: + gain.cgain[0].receive_gain=SND_CARD_LEVEL_TO_HPUX_LEVEL(a); + gain.cgain[1].receive_gain=SND_CARD_LEVEL_TO_HPUX_LEVEL(a); + break; + case SND_CARD_LEVEL_OUTPUT: + gain.cgain[0].transmit_gain=SND_CARD_LEVEL_TO_HPUX_LEVEL(a); + gain.cgain[1].transmit_gain=SND_CARD_LEVEL_TO_HPUX_LEVEL(a); + break; + default: + g_warning("hpux_snd_card_set_level: unsupported command."); + return; + } + gain.channel_mask=AUDIO_CHANNEL_RIGHT|AUDIO_CHANNEL_LEFT; + mix_fd = open(obj->mixdev_name, O_WRONLY); + g_return_if_fail(mix_fd>0); + error=ioctl(mix_fd,AUDIO_SET_GAINS,&gain); + if (error<0){ + g_warning("hpux_snd_card_set_level: Could not set gains: %s",strerror(errno)); + } + close(mix_fd); +} + +gint hpux_snd_card_get_level(HpuxSndCard *obj,gint way) +{ + struct audio_gain gain; + int p=0,mix_fd,error; + g_return_if_fail(obj->mixdev_name!=NULL); + + gain.channel_mask=AUDIO_CHANNEL_RIGHT|AUDIO_CHANNEL_LEFT; + mix_fd = open(obj->mixdev_name, O_RDONLY); + g_return_if_fail(mix_fd>0); + error=ioctl(mix_fd,AUDIO_GET_GAINS,&gain); + if (error<0){ + g_warning("hpux_snd_card_set_level: Could not get gains: %s",strerror(errno)); + } + close(mix_fd); + + switch(way){ + case SND_CARD_LEVEL_GENERAL: + p=gain.cgain[0].monitor_gain; + break; + case SND_CARD_LEVEL_INPUT: + p=gain.cgain[0].receive_gain; + break; + case SND_CARD_LEVEL_OUTPUT: + p=gain.cgain[0].transmit_gain; + break; + default: + g_warning("hpux_snd_card_get_level: unsupported command."); + return -1; + } + return HPUX_LEVEL_TO_SND_CARD_LEVEL(p); +} + +void hpux_snd_card_set_source(HpuxSndCard *obj,int source) +{ + gint p=0; + gint mix_fd; + gint error=0; + g_return_if_fail(obj->mixdev_name!=NULL); + + mix_fd=open("/dev/audio",O_WRONLY); + g_return_if_fail(mix_fd>0); + switch(source){ + case 'm': + error=ioctl(mix_fd,AUDIO_SET_INPUT,AUDIO_IN_MIKE); + break; + case 'l': + error=ioctl(mix_fd,AUDIO_SET_INPUT,AUDIO_IN_LINE); + break; + default: + g_warning("hpux_snd_card_set_source: unsupported source."); + } + close(mix_fd); +} + +MSFilter *hpux_snd_card_create_read_filter(HpuxSndCard *card) +{ + MSFilter *f=ms_oss_read_new(); + ms_oss_read_set_device(MS_OSS_READ(f),SND_CARD(card)->index); + return f; +} + +MSFilter *hpux_snd_card_create_write_filter(HpuxSndCard *card) +{ + MSFilter *f=ms_oss_write_new(); + ms_oss_write_set_device(MS_OSS_WRITE(f),SND_CARD(card)->index); + return f; +} + + +SndCard * hpux_snd_card_new(char *devname, char *mixdev_name) +{ + HpuxSndCard * obj= g_new0(HpuxSndCard,1); + SndCard *base= SND_CARD(obj); + snd_card_init(base); + obj->dev_name=g_strdup(devname); + obj->mixdev_name=g_strdup( mixdev_name); + base->card_name=g_strdup(devname); + base->_probe=(SndCardOpenFunc)hpux_snd_card_probe; + base->_open_r=(SndCardOpenFunc)hpux_snd_card_open; + base->_open_w=(SndCardOpenFunc)hpux_snd_card_open; + base->_can_read=(SndCardPollFunc)hpux_snd_card_can_read; + base->_read=(SndCardIOFunc)hpux_snd_card_read; + base->_write=(SndCardIOFunc)hpux_snd_card_write; + base->_close_r=(SndCardCloseFunc)hpux_snd_card_close; + base->_close_w=(SndCardCloseFunc)hpux_snd_card_close; + base->_set_rec_source=(SndCardMixerSetRecSourceFunc)hpux_snd_card_set_source; + base->_set_level=(SndCardMixerSetLevelFunc)hpux_snd_card_set_level; + base->_get_level=(SndCardMixerGetLevelFunc)hpux_snd_card_get_level; + base->_destroy=(SndCardDestroyFunc)hpux_snd_card_destroy; + base->_create_read_filter=(SndCardCreateFilterFunc)hpux_snd_card_create_read_filter; + base->_create_write_filter=(SndCardCreateFilterFunc)hpux_snd_card_create_write_filter; + return base; +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/jackcard.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/jackcard.c new file mode 100644 index 00000000..b929cce9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/jackcard.c @@ -0,0 +1,574 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + JACK support + Copyright (C) 2004 Tobias Gehrig tobias@gehrig.tk +*/ + +#include "jackcard.h" + +#ifdef __JACK_ENABLED__ + +#include "msossread.h" +#include "msosswrite.h" + +#include + +#define READBUFFERSIZE 524288 +#define WRITEBUFFERSIZE 524288 +#define BSIZE 512 + +/** + * jack_shutdown: + * @arg: + * + * This is the shutdown callback for this JACK application. + * It is called by JACK if the server ever shuts down or + * decides to disconnect the client. + * + */ +void +jack_shutdown (void *arg) +{ + JackCard* obj = (JackCard*) arg; + + obj->jack_running = FALSE; + obj->jack_active = FALSE; + obj->read.port = NULL; + if (obj->read.open) + obj->read.init = TRUE; + obj->write.port = NULL; + if (obj->write.open) + obj->write.init = TRUE; +} + +int samplerate(jack_nframes_t rate, void *arg) +{ + JackCard* obj = (JackCard*) arg; + int error; + + obj->rate = rate; + if (obj->read.open) { + obj->read.data.src_ratio = (double)obj->read.rate / (double)obj->rate; + obj->read.data.input_frames = (long)((double)obj->read.frames/obj->read.data.src_ratio); + g_free(obj->read.data.data_in); + obj->read.data.data_in = malloc(obj->read.data.input_frames*sizeof(float)); + if (obj->read.src_state) + if ((error = src_set_ratio(obj->read.src_state, obj->read.data.src_ratio)) != 0) + g_warning("Error while resetting the write samplerate: %s", src_strerror(error)); + } + if (obj->write.open) { + obj->write.data.src_ratio = (double)obj->rate / (double)obj->write.rate; + obj->write.data.output_frames = (long)((double)obj->write.frames*obj->write.data.src_ratio); + g_free(obj->write.data.data_out); + obj->write.data.data_out = malloc(obj->write.data.output_frames*sizeof(float)); + if (obj->write.src_state) + if ((error = src_set_ratio(obj->write.src_state, obj->write.data.src_ratio)) != 0) + g_warning("Error while resetting the write samplerate: %s", src_strerror(error)); + } + return 0; +} + +/* + * The process callback for this JACK application. + * It is called by JACK at the appropriate times. + * @nframes : + * @arg : + */ +int +process (jack_nframes_t nframes, void *arg) +{ + JackCard* obj = (JackCard*) arg; + sample_t *out; + sample_t *in; + + if (obj->clear && !obj->write.can_process) { + out = (sample_t *) jack_port_get_buffer (obj->write.port, nframes); + memset (out, 0, nframes * sizeof(sample_t)); + obj->clear = FALSE; + } + + if (!obj->can_process) + return 0; + + if(obj->read.can_process) { + in = (sample_t *) jack_port_get_buffer (obj->read.port, nframes); + jack_ringbuffer_write (obj->read.buffer, (void *) in, sizeof(sample_t) * nframes); + } + + if (obj->write.can_process) { + out = (sample_t *) jack_port_get_buffer (obj->write.port, nframes); + memset (out, 0, nframes * sizeof(sample_t)); + if (obj->clear && jack_ringbuffer_read_space(obj->write.buffer) == 0) { + obj->write.can_process = FALSE; + if (!obj->read.open) + obj->can_process = FALSE; + obj->clear = FALSE; + return 0; + } + jack_ringbuffer_read (obj->write.buffer, (void *) out, sizeof(sample_t) * nframes); + } + return 0; +} + +int jack_init(JackCard* obj) +{ + char* client_name; + int error; + + if (!obj->jack_running) { + obj->client = NULL; + client_name = g_strdup_printf("linphone-%u", g_random_int()); + if ((obj->client = jack_client_new (client_name)) == NULL) { + g_warning("cannot create jack client"); + g_free(client_name); + return -1; + } + g_message("Found Jack Daemon"); + g_free(client_name); + + /* tell the JACK server to call `process()' whenever + there is work to be done. + */ + jack_set_process_callback (obj->client, process, obj); + + /* tell the JACK server to call `jack_shutdown()' if + it ever shuts down, either entirely, or if it + just decides to stop calling us. + */ + jack_on_shutdown (obj->client, jack_shutdown, obj); + jack_set_sample_rate_callback (obj->client, samplerate, obj); + obj->rate = jack_get_sample_rate (obj->client); + if (obj->rate == 0) { + g_warning ("rate is 0???"); + if (jack_client_close(obj->client) != 0) + g_warning("could not close client"); + return -1; + } + obj->buffer_size = jack_get_buffer_size(obj->client); + obj->jack_running = TRUE; + } + + if (!obj->jack_active) { + if (jack_activate (obj->client)) { + g_warning("cannot activate jack client"); + return -1; + } else obj->jack_active = TRUE; + } + + if (obj->read.init) { + if (!obj->read.port && (obj->read.port = jack_port_register (obj->client, "input", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0))==NULL) { + g_warning("error while trying to register input port"); + return -1; + } + if (!obj->read.phys_ports && (obj->read.phys_ports = jack_get_ports (obj->client, NULL, NULL, JackPortIsPhysical|JackPortIsOutput)) == NULL) { + g_warning("Cannot find any physical capture ports\n"); + jack_port_unregister(obj->client, obj->read.port); + obj->read.port = NULL; + return -1; + } + if (!jack_port_connected(obj->read.port)) + if ((error = jack_connect (obj->client, obj->read.phys_ports[0], jack_port_name (obj->read.port))) != 0) { + g_warning("cannot connect input ports: %s -> %s\n", jack_port_name (obj->read.port), obj->read.phys_ports[0]); + if (error == EEXIST) g_warning("connection already made"); + else { + jack_port_unregister(obj->client, obj->read.port); + obj->read.port = NULL; + return -1; + } + } + obj->read.init = FALSE; + } + + if (obj->write.init) { + if (!obj->write.port && (obj->write.port = jack_port_register (obj->client, "output", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0))==NULL) { + g_warning("error while trying to register output port"); + return -1; + } + if (!obj->write.phys_ports && (obj->write.phys_ports = jack_get_ports (obj->client, NULL, NULL, JackPortIsPhysical|JackPortIsInput)) == NULL) { + g_warning("Cannot find any physical playback ports\n"); + jack_port_unregister(obj->client, obj->write.port); + obj->write.port = NULL; + return -1; + } + if (!jack_port_connected(obj->write.port)) { + if ((error = jack_connect (obj->client, jack_port_name (obj->write.port), obj->write.phys_ports[0])) != 0) { + g_warning("cannot connect output ports: %s -> %s\n", jack_port_name (obj->write.port), obj->write.phys_ports[0]); + if (error == EEXIST) g_warning("connection already made"); + else { + jack_port_unregister(obj->client, obj->write.port); + obj->write.port = NULL; + return -1; + } + } + if ((error = jack_connect (obj->client, jack_port_name (obj->write.port), obj->write.phys_ports[1])) != 0) { + g_warning("cannot connect output ports: %s -> %s\n", jack_port_name (obj->write.port), obj->write.phys_ports[1]); + if (error == EEXIST) g_warning("connection already made"); + else { + jack_port_unregister(obj->client, obj->write.port); + obj->write.port = NULL; + return -1; + } + } + } + obj->write.init = FALSE; + } + return 0; +} + +int jack_card_open_r(JackCard *obj,int bits,int stereo,int rate) +{ + int channels = stereo + 1, bsize, error; + obj->read.init = TRUE; + if (jack_init(obj) != 0) return -1; + + obj->read.rate = rate; + obj->sample_size = bits / 8; + obj->frame_size = channels * obj->sample_size; + bsize = BSIZE; + obj->read.frames = bsize / 2; + SND_CARD(obj)->bsize = bsize; + SND_CARD(obj)->flags |= SND_CARD_FLAGS_OPENED; + obj->read.channels = channels; + if ((obj->read.src_state = src_new (SRC_SINC_FASTEST, channels, &error)) == NULL) + g_warning("Error while initializing the samplerate converter: %s", src_strerror(error)); + obj->read.data.src_ratio = (double)rate / (double)obj->rate; + obj->read.data.input_frames = (long)((double)obj->read.frames/obj->read.data.src_ratio); + obj->read.data.data_in = malloc(obj->read.data.input_frames*sizeof(float)); + obj->read.data.data_out = malloc(obj->read.frames*sizeof(float)); + obj->read.data.end_of_input = 0; + if (!obj->read.buffer) + obj->read.buffer = jack_ringbuffer_create(READBUFFERSIZE); + obj->read.can_process = TRUE; + obj->can_process = TRUE; + obj->read.open = TRUE; + obj->read.init = FALSE; + return 0; +} + +int jack_card_open_w(JackCard *obj,int bits,int stereo,int rate) +{ + int channels = stereo + 1, bsize, err; + obj->write.init = TRUE; + if (jack_init(obj) != 0) return -1; + + obj->write.rate = rate; + obj->sample_size = bits / 8; + obj->frame_size = channels * obj->sample_size; + bsize = BSIZE; + obj->write.frames = bsize / 2; + SND_CARD(obj)->bsize = bsize; + SND_CARD(obj)->flags |= SND_CARD_FLAGS_OPENED; + obj->write.channels = channels; + if ((obj->write.src_state = src_new (SRC_SINC_FASTEST, channels, &err)) == NULL) + g_warning("Error while initializing the samplerate converter: %s", src_strerror(err)); + obj->write.data.src_ratio = (double)obj->rate / (double)rate; + obj->write.data.data_in = malloc(obj->write.frames*sizeof(float)); + obj->write.data.end_of_input = 0; + obj->write.data.output_frames = (long)((double)obj->write.frames*obj->write.data.src_ratio); + obj->write.data.data_out = malloc(obj->write.data.output_frames*sizeof(float)); + if (!obj->write.buffer) + obj->write.buffer = jack_ringbuffer_create(WRITEBUFFERSIZE); + obj->write.can_process = TRUE; + obj->can_process = TRUE; + obj->write.open = TRUE; + obj->write.init = FALSE; + return 0; +} + +void jack_card_set_blocking_mode(JackCard *obj, gboolean yesno) +{ +} + +void jack_card_close_r(JackCard *obj) +{ + obj->read.open = FALSE; + obj->read.init = FALSE; + obj->read.can_process = FALSE; + if (!obj->write.open) + obj->can_process = FALSE; + if (obj->read.src_state) + obj->read.src_state = src_delete (obj->read.src_state); + g_free(obj->read.data.data_in); + g_free(obj->read.data.data_out); +} + +void jack_card_close_w(JackCard *obj) +{ + obj->write.open = FALSE; + obj->write.init = FALSE; + obj->clear = TRUE; + if (!obj->jack_running) { + obj->write.can_process = FALSE; + obj->can_process = FALSE; + } + if (obj->write.src_state) + obj->write.src_state = src_delete (obj->write.src_state); + g_free(obj->write.data.data_in); + g_free(obj->write.data.data_out); +} + +int jack_card_probe(JackCard *obj,int bits,int stereo,int rate) +{ + if (obj->jack_running) return BSIZE; + else if (jack_init(obj) == 0) return BSIZE; + else return -1; +} + +void jack_card_destroy(JackCard *obj) +{ + if (obj->jack_running) jack_client_close (obj->client); + snd_card_uninit(SND_CARD(obj)); + if (obj->read.buffer) { + jack_ringbuffer_free(obj->read.buffer); + obj->read.buffer = NULL; + } + if (obj->write.buffer) { + jack_ringbuffer_free(obj->write.buffer); + obj->write.buffer = NULL; + } + if (obj->read.phys_ports) { + g_free(obj->read.phys_ports); + obj->read.phys_ports = NULL; + } + if (obj->write.phys_ports) { + g_free(obj->write.phys_ports); + obj->write.phys_ports = NULL; + } +} + +gboolean jack_card_can_read(JackCard *obj) +{ + g_return_val_if_fail(obj->read.buffer!=NULL,0); + if (jack_ringbuffer_read_space(obj->read.buffer)>=(long)((double)obj->read.frames/obj->read.data.src_ratio)*sizeof(sample_t)) return TRUE; + else return FALSE; +} + +int jack_card_read(JackCard *obj,char *buf,int size) +{ + size_t bytes, can_read, i; + int error; + float norm, value; + + g_return_val_if_fail((obj->read.buffer!=NULL)&&(obj->read.src_state!=NULL),-1); + if (jack_init(obj) != 0) return -1; + size /= 2; + can_read = MIN(size, obj->read.frames); + // can_read = MIN(((long)((double)can_read / obj->read.data.src_ratio))*sizeof(sample_t), jack_ringbuffer_read_space(obj->read.buffer)); + can_read = ((long)((double)can_read / obj->read.data.src_ratio))*sizeof(sample_t); + obj->read.can_process = FALSE; + bytes = jack_ringbuffer_read (obj->read.buffer, (void *)obj->read.data.data_in, can_read); + obj->read.can_process = TRUE; + obj->read.data.input_frames = bytes / sizeof(sample_t); + can_read = MIN(size, obj->read.frames); + obj->read.data.output_frames = can_read; + if ((error = src_process(obj->read.src_state, &(obj->read.data))) != 0) + g_warning("error while samplerate conversion. error: %s", src_strerror(error)); + norm = obj->read.level*obj->level*(float)0x8000; + for (i=0; i < obj->read.data.output_frames_gen; i++) { + value = obj->read.data.data_out[i]*norm; + if (value >= 32767.0) + ((short*)buf)[i] = 32767; + else if (value <= -32768.0) + ((short*)buf)[i] = -32768; + else + ((short*)buf)[i] = (short)value; + } + bytes = obj->read.data.output_frames_gen * 2; + return bytes; +} + +int jack_card_write(JackCard *obj,char *buf,int size) +{ + size_t bytes, can_write, i; + int error; + float norm; + + g_return_val_if_fail((obj->write.buffer!=NULL)&&(obj->write.src_state!=NULL),-1); + if (jack_init(obj) != 0) return -1; + size /= 2; + can_write = MIN(size, obj->write.frames); + norm = obj->write.level*obj->level/(float)0x8000; + for (i=0; iwrite.data.data_in[i] = (float)((short*)buf)[i]*norm; + } + obj->write.data.input_frames = can_write; + if ((error = src_process(obj->write.src_state, &(obj->write.data))) != 0) + g_warning("error while samplerate conversion. error: %s", src_strerror(error)); + obj->write.can_process = FALSE; + bytes = jack_ringbuffer_write (obj->write.buffer, (void *) obj->write.data.data_out, sizeof(sample_t)*obj->write.data.output_frames_gen); + obj->write.can_process = TRUE; + return bytes; +} + +void jack_card_set_level(JackCard *obj,gint way,gint a) +{ + switch(way){ + case SND_CARD_LEVEL_GENERAL: + obj->level = (float)a / 100.0; + break; + case SND_CARD_LEVEL_INPUT: + obj->read.level = (float)a / 100.0; + break; + case SND_CARD_LEVEL_OUTPUT: + obj->write.level = (float)a / 100.0; + break; + default: + g_warning("jack_card_set_level: unsupported command."); + } +} + +gint jack_card_get_level(JackCard *obj,gint way) +{ + gint value = 0; + + switch(way){ + case SND_CARD_LEVEL_GENERAL: + value = (gint)(obj->level*100.0); + break; + case SND_CARD_LEVEL_INPUT: + value = (gint)(obj->read.level*100.0); + break; + case SND_CARD_LEVEL_OUTPUT: + value = (gint)(obj->write.level*100.0); + break; + default: + g_warning("jack_card_get_level: unsupported command."); + } + return value; +} + +void jack_card_set_source(JackCard *obj,int source) +{ +} + +MSFilter *jack_card_create_read_filter(JackCard *card) +{ + MSFilter *f=ms_oss_read_new(); + ms_oss_read_set_device(MS_OSS_READ(f),SND_CARD(card)->index); + return f; +} + +MSFilter *jack_card_create_write_filter(JackCard *card) +{ + MSFilter *f=ms_oss_write_new(); + ms_oss_write_set_device(MS_OSS_WRITE(f),SND_CARD(card)->index); + return f; +} +SndCard * jack_card_new(jack_client_t *client) +{ + JackCard * obj; + SndCard *base; + + obj= g_new0(JackCard,1); + + if (!client) return NULL; + obj->client = client; + obj->jack_running = TRUE; + obj->jack_active = FALSE; + obj->can_process = FALSE; + obj->clear = TRUE; + obj->write.can_process = FALSE; + obj->write.open = FALSE; + obj->write.init = TRUE; + obj->write.port = NULL; + obj->write.phys_ports = NULL; + obj->write.buffer = NULL; + obj->read.can_process = FALSE; + obj->read.open = FALSE; + obj->read.init = TRUE; + obj->read.port = NULL; + obj->read.phys_ports = NULL; + obj->read.buffer = NULL; + + /* tell the JACK server to call `process()' whenever + there is work to be done. + */ + jack_set_process_callback (client, process, obj); + + /* tell the JACK server to call `jack_shutdown()' if + it ever shuts down, either entirely, or if it + just decides to stop calling us. + */ + jack_on_shutdown (client, jack_shutdown, obj); + + jack_set_sample_rate_callback (client, samplerate, obj); + + obj->rate = jack_get_sample_rate (client); + obj->buffer_size = jack_get_buffer_size(obj->client); + + jack_init(obj); + + base= SND_CARD(obj); + snd_card_init(base); + +#ifdef HAVE_GLIB + base->card_name=g_strdup_printf("JACK client"); +#else + base->card_name=malloc(100); + snprintf(base->card_name, 100, "JACK client"); +#endif + + base->_probe=(SndCardOpenFunc)jack_card_probe; + base->_open_r=(SndCardOpenFunc)jack_card_open_r; + base->_open_w=(SndCardOpenFunc)jack_card_open_w; + base->_can_read=(SndCardPollFunc)jack_card_can_read; + base->_set_blocking_mode=(SndCardSetBlockingModeFunc)jack_card_set_blocking_mode; + base->_read=(SndCardIOFunc)jack_card_read; + base->_write=(SndCardIOFunc)jack_card_write; + base->_close_r=(SndCardCloseFunc)jack_card_close_r; + base->_close_w=(SndCardCloseFunc)jack_card_close_w; + base->_set_rec_source=(SndCardMixerSetRecSourceFunc)jack_card_set_source; + base->_set_level=(SndCardMixerSetLevelFunc)jack_card_set_level; + base->_get_level=(SndCardMixerGetLevelFunc)jack_card_get_level; + base->_destroy=(SndCardDestroyFunc)jack_card_destroy; + base->_create_read_filter=(SndCardCreateFilterFunc)jack_card_create_read_filter; + base->_create_write_filter=(SndCardCreateFilterFunc)jack_card_create_write_filter; + + obj->read.buffer=NULL; + obj->write.buffer=NULL; + obj->buffer_size = 0; + obj->level = 1.0; + obj->write.level = 1.0; + obj->read.level = 1.0; + + return base; +} + + +gint jack_card_manager_init(SndCardManager *m, gint index) +{ + jack_client_t *client = NULL; + char* client_name; + + client_name=g_strdup_printf("linphone-%u", g_random_int()); + if ((client = jack_client_new (client_name))!= NULL) + { + g_message("Found Jack Daemon"); + g_free(client_name); + m->cards[index]=jack_card_new(client); + m->cards[index]->index=index; + return 1; + } else { + g_free(client_name); + return 0; + } +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/jackcard.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/jackcard.h new file mode 100644 index 00000000..33ec46dc --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/jackcard.h @@ -0,0 +1,81 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + JACK support + Copyright (C) 2004 Tobias Gehrig tobias@gehrig.tk +*/ + +#ifndef JACK_CARD_H +#define JACK_CARD_H + +#include + +#ifdef __JACK_ENABLED__ + +#include "sndcard.h" + +#include +#include + +#include + +typedef jack_default_audio_sample_t sample_t; + +typedef struct { + jack_port_t *port; + const char **phys_ports; + float level; + jack_ringbuffer_t *buffer; + gint channels; + gint rate; + SRC_STATE* src_state; + SRC_DATA data; + size_t frames; + gboolean can_process; + gboolean open; + gboolean init; +} jackcard_mode_t; + +struct _JackCard +{ + SndCard parent; + + jack_client_t *client; + gboolean jack_running; + gboolean jack_active; + float level; + jack_nframes_t buffer_size; + gint sample_size; + gint frame_size; + gint rate; + gboolean can_process; + gboolean clear; + + jackcard_mode_t read, write; +}; + +typedef struct _JackCard JackCard; + +SndCard * jack_card_new(jack_client_t *client); + +gint jack_card_manager_init(SndCardManager *m, gint index); + +#endif + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mediastream.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mediastream.h new file mode 100644 index 00000000..3ccbab69 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mediastream.h @@ -0,0 +1,130 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MEDIASTREAM_H +#define MEDIASTREAM_H + +#include "msrtprecv.h" +#include "msrtpsend.h" +#include "ms.h" +#include "msosswrite.h" +#include "msossread.h" +#include "msread.h" +#include "mswrite.h" +#include "mstimer.h" +#include "mscodec.h" +#ifdef HAVE_SPEEX +#include "msspeexdec.h" +#endif +#include "msringplayer.h" + + +struct _AudioStream +{ + MSSync *timer; + RtpSession *send_session; + RtpSession *recv_session; + MSFilter *soundread; + MSFilter *soundwrite; + MSFilter *encoder; + MSFilter *decoder; + MSFilter *rtprecv; + MSFilter *rtpsend; +}; + + +typedef struct _AudioStream AudioStream; + +struct _RingStream +{ + MSSync *timer; + MSFilter *source; + MSFilter *sndwrite; +}; + +typedef struct _RingStream RingStream; + +/* start a thread that does sampling->encoding->rtp_sending|rtp_receiving->decoding->playing */ +AudioStream *audio_stream_start (RtpProfile * prof, int locport, char *remip, + int remport, int profile, int jitt_comp); + +AudioStream *audio_stream_start_with_sndcards(RtpProfile * prof, int locport, char *remip4, + int remport, int profile, int jitt_comp, SndCard *playcard, SndCard *captcard); + +AudioStream *audio_stream_start_with_files (RtpProfile * prof, int locport, + char *remip4, int remport, + int profile, int jitt_comp, + gchar * infile, gchar * outfile); +void audio_stream_set_rtcp_information(AudioStream *st, const char *cname); + + +/* stop the above process*/ +void audio_stream_stop (AudioStream * stream); + +RingStream *ring_start (gchar * file, gint interval, SndCard *sndcard); +RingStream *ring_start_with_cb(gchar * file, gint interval, SndCard *sndcard, MSFilterNotifyFunc func,gpointer user_data); +void ring_stop (RingStream * stream); + +/* returns the latency in samples if the audio device with id dev_id is openable in full duplex mode, else 0 */ +gint test_audio_dev (int dev_id); + +/* send a dtmf */ +gint audio_stream_send_dtmf (AudioStream * stream, gchar dtmf); + +void audio_stream_set_default_card(int cardindex); + + +#ifdef VIDEO_ENABLED + +/***************** + Video Support + *****************/ + + + +struct _VideoStream +{ + MSSync *timer; + RtpSession *send_session; + RtpSession *recv_session; + MSFilter *source; + MSFilter *output; + MSFilter *encoder; + MSFilter *decoder; + MSFilter *rtprecv; + MSFilter *rtpsend; + gboolean show_local; +}; + + +typedef struct _VideoStream VideoStream; + +VideoStream *video_stream_start(RtpProfile *profile, int locport, char *remip4, int remport, + int payload, int jitt_comp, gboolean show_local, const gchar *source, const gchar *device); +void video_stream_set_rtcp_information(VideoStream *st, const char *cname); +void video_stream_stop (VideoStream * stream); + +VideoStream * video_preview_start(const gchar *source, const gchar *device); +void video_preview_stop(VideoStream *stream); + +#endif + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/ms.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/ms.c new file mode 100644 index 00000000..cfcafa33 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/ms.c @@ -0,0 +1,342 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "ms.h" +#include "sndcard.h" +#include "mscodec.h" + +#include +#include +#include +#include +#include +#include + +#ifdef VIDEO_ENABLED +extern void ms_video_source_register_all(); +#endif +#ifdef HAVE_ILBC +extern void ms_ilbc_codec_init(); +#endif + +/** + * ms_init: + * + * + * Initialize the mediastreamer. This must be the first function called in a program + * using the mediastreamer library. + * + * + */ +void ms_init() +{ + if (!g_thread_supported()) g_thread_init (NULL); +#ifdef HAVE_GLIB + if (!g_module_supported()){ + g_error("GModule is not supported."); + } +#endif + /* initialize the oss subsystem */ + snd_card_manager_init(snd_card_manager); + /* register the statically linked codecs */ + ms_codec_register_all(); +#ifdef VIDEO_ENABLED + ms_video_source_register_all(); +#endif +#ifdef HAVE_ILBC + ms_ilbc_codec_init(); +#endif +} + + +static gint compare(gconstpointer a, gconstpointer b) +{ + MSFilter *f1=(MSFilter*)a,*f2=(MSFilter*)b; + if (f1->klassklass) return -1; + if (f1->klass==f2->klass) return 0; + /* if f1->klass>f2->klass ....*/ + return 1; +} + +static GList *g_list_append_if_new(GList *l,gpointer data) +{ + GList *res=l; + if (g_list_find(res,data)==NULL) + res=g_list_append(res,data); + return(res); +} + +static GList *get_nexts(MSFilter *f,GList *l) +{ + int i; + MSFifo *fifo; + MSQueue *q; + GList *res=l; + + /* check fifos*/ + for (i=0;i klass->max_foutputs;i++) + { + fifo=f->outfifos[i]; + if (fifo!=NULL) res=g_list_append_if_new(res,(gpointer)fifo->next_data); + } + /* check queues*/ + for (i=0;i klass->max_qoutputs;i++) + { + q=f->outqueues[i]; + if (q!=NULL) res=g_list_append_if_new(res,(gpointer)q->next_data); + } + return(res); +} + +/* compile graphs attached to a sync source*/ +int ms_compile(MSSync *sync) +{ + int i; + GList *list1=NULL,*list2=NULL,*elem; + GList *proc_chain=NULL; + MSFilter *f; + + /* first free the old list if we are just updating*/ + if (sync->execution_list!=NULL) g_list_free(sync->execution_list); + /* get the list of filters attached to this sync*/ + for (i=0;ifilters;i++) + { + /* //printf("found filter !\n"); */ + list1=g_list_append(list1,sync->attached_filters[i]); + } + /* find the processing chain */ + while (list1!=NULL) + { + list2=NULL; + /* sort the list by types of filter*/ + list1=g_list_sort(list1,compare); + /* save into the processing chain list*/ + /* //printf("list1 :%i elements\n",g_list_length(list1)); */ + proc_chain=g_list_concat(proc_chain,list1); + /* get all following filters. They are appended to list2*/ + elem=list1; + while (elem!=NULL) + { + f=(MSFilter*)(elem->data); + /* check if filter 's status */ + if (f->klass->attributes & FILTER_CAN_SYNC) + { + sync->samples_per_tick=0; + } + list2=get_nexts(f,list2); + elem=g_list_next(elem); + } + list1=list2; + } + sync->execution_list=proc_chain; + sync->flags&=~MS_SYNC_NEED_UPDATE; + ms_trace("%i filters successfully compiled in a processing chain.",g_list_length(sync->execution_list)); + return 0; +} + +/*execute the processing chain attached to a sync source. It is called as a thread by ms_main()*/ +void *ms_thread_run(void *sync_ptr) +{ + MSSync *sync=(MSSync*) sync_ptr; + GList *filter; + MSFilter *f; + + + ms_sync_lock(sync); + while(sync->run) + { + /* //g_message("sync->run=%i",sync->run); */ + if (sync->samples_per_tick==0) ms_sync_suspend(sync); + if (sync->flags & MS_SYNC_NEED_UPDATE){ + ms_compile(sync); + ms_sync_setup(sync); + } + filter=sync->execution_list; + ms_sync_unlock(sync); + /* //ms_trace("Calling synchronisation"); */ + ms_sync_synchronize(sync); + while(filter!=NULL) + { + f=(MSFilter*)filter->data; + if (MS_FILTER_GET_CLASS(f)->attributes & FILTER_IS_SOURCE) + { + /* execute it once */ + ms_trace("Running source filter %s.",f->klass->name); + ms_filter_process(f); + } + else + { + /* make the filter process its input data until it has no more */ + while ( ms_filter_fifos_have_data(f) || ms_filter_queues_have_data(f) ) + { + ms_trace("Running filter %s.",f->klass->name); + ms_filter_process(f); + } + } + filter=g_list_next(filter); + } + ms_sync_lock(sync); + } + g_cond_signal(sync->stop_cond); /* signal that the sync thread has finished */ + ms_sync_unlock(sync); + g_message("Mediastreamer processing thread is exiting."); + return NULL; +} + +/* stop the processing chain attached to a sync source.*/ +void ms_thread_stop(MSSync *sync) +{ + if (sync->thread!=NULL) + { + if (sync->samples_per_tick==0) + { + /* to wakeup the thread */ + /* //g_cond_signal(sync->thread_cond); */ + } + g_mutex_lock(sync->lock); + sync->run=0; + sync->thread=NULL; + g_cond_wait(sync->stop_cond,sync->lock); + g_mutex_unlock(sync->lock); + } + /* //g_message("ms_thread_stop() finished."); */ +} + +/** + * ms_start: + * @sync: A synchronisation source to be started. + * + * Starts a thread that will shedule all processing chains attached to the synchronisation source @sync. + * + * + */ +void ms_start(MSSync *sync) +{ + if (sync->run==1) return; /*already running*/ + ms_compile(sync); + ms_sync_setup(sync); + /* this is to avoid race conditions, for example: + ms_start(sync); + ms_oss_write_start(ossw); + here tge ossw filter need to be compiled to run ms_oss_write_start() + */ + ms_trace("ms_start: creating new thread."); + sync->run=1; + sync->thread=g_thread_create((GThreadFunc)ms_thread_run,(gpointer)sync,TRUE,NULL); + if (sync->thread==NULL){ + g_warning("Could not create thread !"); + } +} + +/** + * ms_stop: + * @sync: A synchronisation source to be stopped. + * + * Stop the thread that was sheduling the processing chains attached to the synchronisation source @sync. + * The processing chains are kept unchanged, no object is freed. The synchronisation source can be restarted using ms_start(). + * + * + */ +void ms_stop(MSSync *sync) +{ + ms_thread_stop(sync); + ms_sync_unsetup(sync); +} + + +gint ms_load_plugin(gchar *path) +{ +#ifdef HAVE_GLIB + g_module_open(path,0); +#endif + return 0; +} + +gchar * ms_proc_get_param(gchar *parameter) +{ + gchar *file; + int fd; + int err,len; + gchar *p,*begin,*end; + gchar *ret; + fd=open("/proc/cpuinfo",O_RDONLY); + if (fd<0){ + g_warning("Could not open /proc/cpuinfo."); + return NULL; + } + file=g_malloc(1024); + err=read(fd,file,1024); + file[err-1]='\0'; + /* find the parameter */ + p=strstr(file,parameter); + if (p==NULL){ + /* parameter not found */ + g_free(file); + return NULL; + } + /* find the following ':' */ + p=strchr(p,':'); + if (p==NULL){ + g_free(file); + return NULL; + } + /* find the value*/ + begin=p+2; + end=strchr(begin,'\n'); + if (end==NULL) end=strchr(begin,'\0'); + len=end-begin+1; + ret=g_malloc(len+1); + snprintf(ret,len,"%s",begin); + /* //printf("%s=%s\n",parameter,ret); */ + g_free(file); + return ret; +} + +gint ms_proc_get_type() +{ + static int proc_type=0; + gchar *value; + if (proc_type==0){ + value=ms_proc_get_param("cpu family"); + if (value!=NULL) { + proc_type=atoi(value); + g_free(value); + }else return -1; + } + return proc_type; +} + +gint ms_proc_get_speed() +{ + char *value; + static int proc_speed=0; + if (proc_speed==0){ + value=ms_proc_get_param("cpu MHz"); + if (value!=NULL){ + proc_speed=atoi(value); + g_free(value); + }else return -1; + } + /* //printf("proc_speed=%i\n",proc_speed); */ + return proc_speed; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/ms.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/ms.h new file mode 100644 index 00000000..51c69b96 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/ms.h @@ -0,0 +1,81 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + + +#ifndef MS_H +#define MS_H +#include "msfilter.h" +#include "mssync.h" + + +void ms_init(); + +/* compile graphs attached to a sync source*/ +int ms_compile(MSSync *source); + + +/* stop the processing chain attached to a sync source.*/ +void ms_thread_stop(MSSync *sync); + + +/** + * function_name:ms_thread_run + * @sync: The synchronization source for all the set of graphs to run. + * + * Execute the processing chain attached to a sync source. This function loops indefinitely. + * The media streamer programmer can choose to execute this function directly, or to call ms_start(), + * that will start a thread for the synchronisation source. + * + * Returns: no return value. + */ +void *ms_thread_run(void *sync); + + +/** + * function_name:ms_start + * @sync: A synchronisation source to be started. + * + * Starts a thread that will shedule all processing chains attached to the synchronisation source @sync. + * + * Returns: no return value. + */ +void ms_start(MSSync *sync); + + +/** + * function_name:ms_stop + * @sync: A synchronisation source to be stopped. + * + * Stop the thread that was sheduling the processing chains attached to the synchronisation source @sync. + * The processing chains are kept unchanged, no object is freed. The synchronisation source can be restarted using ms_start(). + * + * Returns: no return value. + */ +void ms_stop(MSSync *sync); + + +gchar * ms_proc_get_param(gchar *parameter); +gint ms_proc_get_type(); +gint ms_proc_get_speed(); + + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msAlawdec.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msAlawdec.c new file mode 100644 index 00000000..70cc906e --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msAlawdec.c @@ -0,0 +1,132 @@ + /* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include +#include + +extern MSFilter * ms_ALAWencoder_new(void); + +MSCodecInfo ALAWinfo={ + { + "ALAW codec", + 0, + MS_FILTER_AUDIO_CODEC, + ms_ALAWencoder_new, + "This is the classic A-law codec. Good quality, but only usable with high speed network connections." + }, + ms_ALAWencoder_new, + ms_ALAWdecoder_new, + 320, + 160, + 64000, + 8000, + 8, + "PCMA", + 1, + 1, +}; + +static MSALAWDecoderClass *ms_ALAWdecoder_class=NULL; + +MSFilter * ms_ALAWdecoder_new(void) +{ + MSALAWDecoder *r; + + r=g_new(MSALAWDecoder,1); + ms_ALAWdecoder_init(r); + if (ms_ALAWdecoder_class==NULL) + { + ms_ALAWdecoder_class=g_new(MSALAWDecoderClass,1); + ms_ALAWdecoder_class_init(ms_ALAWdecoder_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_ALAWdecoder_class); + return(MS_FILTER(r)); +} + + +/* FOR INTERNAL USE*/ +void ms_ALAWdecoder_init(MSALAWDecoder *r) +{ + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->infifos=r->f_inputs; + MS_FILTER(r)->outfifos=r->f_outputs; + MS_FILTER(r)->r_mingran=ALAW_DECODER_RMAXGRAN; + memset(r->f_inputs,0,sizeof(MSFifo*)*MSALAWDECODER_MAX_INPUTS); + memset(r->f_outputs,0,sizeof(MSFifo*)*MSALAWDECODER_MAX_INPUTS); + +} + +void ms_ALAWdecoder_class_init(MSALAWDecoderClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"ALAWDecoder"); + MS_FILTER_CLASS(klass)->info=(MSFilterInfo*)&ALAWinfo; + MS_FILTER_CLASS(klass)->max_finputs=MSALAWDECODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->max_foutputs=MSALAWDECODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->r_maxgran=ALAW_DECODER_RMAXGRAN; + MS_FILTER_CLASS(klass)->w_maxgran=ALAW_DECODER_WMAXGRAN; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_ALAWdecoder_destroy; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_ALAWdecoder_process; +} + +void ms_ALAWdecoder_process(MSALAWDecoder *r) +{ + MSFifo *fi,*fo; + int inlen,outlen; + gchar *s,*d; + int i; + /* process output fifos, but there is only one for this class of filter*/ + + /* this is the simplest process function design: + the filter declares a r_mingran of ALAW_DECODER_RMAXGRAN, so the mediastreamer's + scheduler will call the process function each time there is ALAW_DECODER_RMAXGRAN + bytes to read in the input fifo. If there is more, then it will call it several + time in order to the fifo to be completetly processed. + This is very simple, but not very efficient because of the multiple call function + of MSFilterProcessFunc that may happen. + The MSAlawEncoder implements another design; see it. + */ + + fi=r->f_inputs[0]; + fo=r->f_outputs[0]; + g_return_if_fail(fi!=NULL); + g_return_if_fail(fo!=NULL); + + inlen=ms_fifo_get_read_ptr(fi,ALAW_DECODER_RMAXGRAN,(void**)&s); + if (s==NULL) return; + outlen=ms_fifo_get_write_ptr(fo,ALAW_DECODER_WMAXGRAN,(void**)&d); + if (d!=NULL) + { + for(i=0;i +#include + +/*this is the class that implements a ALAWdecoder filter*/ + +#define MSALAWDECODER_MAX_INPUTS 1 /* max output per filter*/ + + +typedef struct _MSALAWDecoder +{ + /* the MSALAWDecoder derivates from MSFilter, so the MSFilter object MUST be the first of the MSALAWDecoder object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MSALAWDECODER_MAX_INPUTS]; + MSFifo *f_outputs[MSALAWDECODER_MAX_INPUTS]; +} MSALAWDecoder; + +typedef struct _MSALAWDecoderClass +{ + /* the MSALAWDecoder derivates from MSFilter, so the MSFilter class MUST be the first of the MSALAWDecoder class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSALAWDecoderClass; + +/* PUBLIC */ +#define MS_ALAWDECODER(filter) ((MSALAWDecoder*)(filter)) +#define MS_ALAWDECODER_CLASS(klass) ((MSALAWDecoderClass*)(klass)) +MSFilter * ms_ALAWdecoder_new(void); + +/* FOR INTERNAL USE*/ +void ms_ALAWdecoder_init(MSALAWDecoder *r); +void ms_ALAWdecoder_class_init(MSALAWDecoderClass *klass); +void ms_ALAWdecoder_destroy( MSALAWDecoder *obj); +void ms_ALAWdecoder_process(MSALAWDecoder *r); + +/* tuning parameters :*/ +#define ALAW_DECODER_WMAXGRAN 320 +#define ALAW_DECODER_RMAXGRAN 160 + +extern MSCodecInfo ALAWinfo; + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msAlawenc.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msAlawenc.c new file mode 100644 index 00000000..fd1f9abe --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msAlawenc.c @@ -0,0 +1,124 @@ + /* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "msAlawenc.h" +#include "g711common.h" + +extern MSCodecInfo ALAWinfo; + +static MSALAWEncoderClass *ms_ALAWencoder_class=NULL; + +MSFilter * ms_ALAWencoder_new(void) +{ + MSALAWEncoder *r; + + r=g_new(MSALAWEncoder,1); + ms_ALAWencoder_init(r); + if (ms_ALAWencoder_class==NULL) + { + ms_ALAWencoder_class=g_new(MSALAWEncoderClass,1); + ms_ALAWencoder_class_init(ms_ALAWencoder_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_ALAWencoder_class); + return(MS_FILTER(r)); +} + + +/* FOR INTERNAL USE*/ +void ms_ALAWencoder_init(MSALAWEncoder *r) +{ + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->infifos=r->f_inputs; + MS_FILTER(r)->outfifos=r->f_outputs; + MS_FILTER(r)->r_mingran=ALAW_ENCODER_RMAXGRAN; /* the filter can be called as soon as there is + something to process */ + memset(r->f_inputs,0,sizeof(MSFifo*)*MSALAWENCODER_MAX_INPUTS); + memset(r->f_outputs,0,sizeof(MSFifo*)*MSALAWENCODER_MAX_INPUTS); + +} + +void ms_ALAWencoder_class_init(MSALAWEncoderClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"ALAWEncoder"); + MS_FILTER_CLASS(klass)->info=(MSFilterInfo*)&ALAWinfo; + MS_FILTER_CLASS(klass)->max_finputs=MSALAWENCODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->max_foutputs=MSALAWENCODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->r_maxgran=ALAW_ENCODER_RMAXGRAN; + MS_FILTER_CLASS(klass)->w_maxgran=ALAW_ENCODER_WMAXGRAN; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_ALAWencoder_destroy; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_ALAWencoder_process; +} + +void ms_ALAWencoder_process(MSALAWEncoder *r) +{ + MSFifo *fi,*fo; + int inlen,outlen; + gchar *s,*d; + int i; + /* process output fifos, but there is only one for this class of filter*/ + + /* this is the sophisticated design of the process function: + Here the filter declares that it can be called as soon as there is something + to read on the input fifo by setting r_mingran=0. + Then it ask for the fifo to get as many data as possible by calling: + inlen=ms_fifo_get_read_ptr(fi,0,(void**)&s); + This avoid multiple call to the process function to process all data available + on the input fifo... but the writing of the process function is a bit + more difficult, because althoug ms_fifo_get_read_ptr() returns N bytes, + we cannot ask ms_fifo_get_write_ptr to return N bytes if + N>MS_FILTER_CLASS(klass)->w_maxgran. This is forbidden by the MSFifo + mechanism. + This is an open issue. + For the moment what is done here is that ms_fifo_get_write_ptr() is called + several time with its maximum granularity in order to try to write the output. + ... + One solution: + -create a new function ms_fifo_get_rw_ptr(fifo1,p1, fifo2,p2) to + return the number of bytes able to being processed according to the input + and output fifo, and their respective data pointers + */ + + + fi=r->f_inputs[0]; + fo=r->f_outputs[0]; + + inlen=ms_fifo_get_read_ptr(fi,ALAW_ENCODER_RMAXGRAN,(void**)&s); + if (s==NULL) return; + outlen=ms_fifo_get_write_ptr(fo,ALAW_ENCODER_WMAXGRAN,(void**)&d); + if (d!=NULL) + { + for(i=0;i +#include +#include + +/*this is the class that implements a GSMdecoder filter*/ + +#define MSGSMDECODER_MAX_INPUTS 1 /* max output per filter*/ + + +typedef struct _MSGSMDecoder +{ + /* the MSGSMDecoder derivates from MSFilter, so the MSFilter object MUST be the first of the MSGSMDecoder object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MSGSMDECODER_MAX_INPUTS]; + MSFifo *f_outputs[MSGSMDECODER_MAX_INPUTS]; + gsm gsm_handle; +} MSGSMDecoder; + +typedef struct _MSGSMDecoderClass +{ + /* the MSGSMDecoder derivates from MSFilter, so the MSFilter class MUST be the first of the MSGSMDecoder class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSGSMDecoderClass; + +/* PUBLIC */ +#define MS_GSMDECODER(filter) ((MSGSMDecoder*)(filter)) +#define MS_GSMDECODER_CLASS(klass) ((MSGSMDecoderClass*)(klass)) +MSFilter * ms_GSMdecoder_new(void); + +/* FOR INTERNAL USE*/ +void ms_GSMdecoder_init(MSGSMDecoder *r); +void ms_GSMdecoder_class_init(MSGSMDecoderClass *klass); +void ms_GSMdecoder_destroy( MSGSMDecoder *obj); +void ms_GSMdecoder_process(MSGSMDecoder *r); + +extern MSCodecInfo GSMinfo; + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msGSMencoder.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msGSMencoder.h new file mode 100644 index 00000000..2deae387 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msGSMencoder.h @@ -0,0 +1,61 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSGSMENCODER_H +#define MSGSMENCODER_H + +#include "msfilter.h" +#include + +/*this is the class that implements a GSMencoder filter*/ + +#define MSGSMENCODER_MAX_INPUTS 1 /* max output per filter*/ + + +typedef struct _MSGSMEncoder +{ + /* the MSGSMEncoder derivates from MSFilter, so the MSFilter object MUST be the first of the MSGSMEncoder object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MSGSMENCODER_MAX_INPUTS]; + MSFifo *f_outputs[MSGSMENCODER_MAX_INPUTS]; + gsm gsm_handle; +} MSGSMEncoder; + +typedef struct _MSGSMEncoderClass +{ + /* the MSGSMEncoder derivates from MSFilter, so the MSFilter class MUST be the first of the MSGSMEncoder class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSGSMEncoderClass; + +/* PUBLIC */ +#define MS_GSMENCODER(filter) ((MSGSMEncoder*)(filter)) +#define MS_GSMENCODER_CLASS(klass) ((MSGSMEncoderClass*)(klass)) +MSFilter * ms_GSMencoder_new(void); + +/* FOR INTERNAL USE*/ +void ms_GSMencoder_init(MSGSMEncoder *r); +void ms_GSMencoder_class_init(MSGSMEncoderClass *klass); +void ms_GSMencoder_destroy( MSGSMEncoder *obj); +void ms_GSMencoder_process(MSGSMEncoder *r); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msLPC10decoder.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msLPC10decoder.h new file mode 100644 index 00000000..59d9deca --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msLPC10decoder.h @@ -0,0 +1,64 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSLPC10DECODER_H +#define MSLPC10DECODER_H + +#include +#include +#include + +/*this is the class that implements a LPC10decoder filter*/ + +#define MSLPC10DECODER_MAX_INPUTS 1 /* max output per filter*/ + + +typedef struct _MSLPC10Decoder +{ + /* the MSLPC10Decoder derivates from MSFilter, so the MSFilter object MUST be the first of the MSLPC10Decoder object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MSLPC10DECODER_MAX_INPUTS]; + MSFifo *f_outputs[MSLPC10DECODER_MAX_INPUTS]; + struct lpc10_decoder_state *lpc10_dec; +} MSLPC10Decoder; + +typedef struct _MSLPC10DecoderClass +{ + /* the MSLPC10Decoder derivates from MSFilter, so the MSFilter class MUST be the first of the MSLPC10Decoder class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSLPC10DecoderClass; + +/* PUBLIC */ +#define MS_LPC10DECODER(filter) ((MSLPC10Decoder*)(filter)) +#define MS_LPC10DECODER_CLASS(klass) ((MSLPC10DecoderClass*)(klass)) +MSFilter * ms_LPC10decoder_new(void); + +/* FOR INTERNAL USE*/ +void ms_LPC10decoder_init(MSLPC10Decoder *r); +void ms_LPC10decoder_class_init(MSLPC10DecoderClass *klass); +void ms_LPC10decoder_destroy( MSLPC10Decoder *obj); +void ms_LPC10decoder_process(MSLPC10Decoder *r); + +extern MSCodecInfo LPC10info; + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msLPC10encoder.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msLPC10encoder.h new file mode 100644 index 00000000..4db16436 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msLPC10encoder.h @@ -0,0 +1,74 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSLPC10ENCODER_H +#define MSLPC10ENCODER_H + +#include "mscodec.h" + + +int +read_16bit_samples(gint16 int16samples[], float speech[], int n); + +int +write_16bit_samples(gint16 int16samples[], float speech[], int n); + +void +write_bits(unsigned char *data, gint32 *bits, int len); + +int +read_bits(unsigned char *data, gint32 *bits, int len); + + +/*this is the class that implements a LPC10encoder filter*/ + +#define MSLPC10ENCODER_MAX_INPUTS 1 /* max output per filter*/ + + +typedef struct _MSLPC10Encoder +{ + /* the MSLPC10Encoder derivates from MSFilter, so the MSFilter object MUST be the first of the MSLPC10Encoder object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MSLPC10ENCODER_MAX_INPUTS]; + MSFifo *f_outputs[MSLPC10ENCODER_MAX_INPUTS]; + struct lpc10_encoder_state *lpc10_enc; +} MSLPC10Encoder; + +typedef struct _MSLPC10EncoderClass +{ + /* the MSLPC10Encoder derivates from MSFilter, so the MSFilter class MUST be the first of the MSLPC10Encoder class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSLPC10EncoderClass; + +/* PUBLIC */ +#define MS_LPC10ENCODER(filter) ((MSLPC10Encoder*)(filter)) +#define MS_LPC10ENCODER_CLASS(klass) ((MSLPC10EncoderClass*)(klass)) +MSFilter * ms_LPC10encoder_new(void); + +/* FOR INTERNAL USE*/ +void ms_LPC10encoder_init(MSLPC10Encoder *r); +void ms_LPC10encoder_class_init(MSLPC10EncoderClass *klass); +void ms_LPC10encoder_destroy( MSLPC10Encoder *obj); +void ms_LPC10encoder_process(MSLPC10Encoder *r); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msMUlawdec.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msMUlawdec.c new file mode 100644 index 00000000..500f2389 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msMUlawdec.c @@ -0,0 +1,130 @@ + /* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include +#include + +extern MSFilter * ms_MULAWencoder_new(void); + +MSCodecInfo MULAWinfo={ + { + "MULAW codec", + 0, + MS_FILTER_AUDIO_CODEC, + ms_MULAWencoder_new, + "This is the classic Mu-law codec. Good quality, but only usable with high speed network connections." + }, + ms_MULAWencoder_new, + ms_MULAWdecoder_new, + 320, + 160, + 64000, + 8000, + 0, + "PCMU", + 1, + 1 +}; + +static MSMULAWDecoderClass *ms_MULAWdecoder_class=NULL; + +MSFilter * ms_MULAWdecoder_new(void) +{ + MSMULAWDecoder *r; + + r=g_new(MSMULAWDecoder,1); + ms_MULAWdecoder_init(r); + if (ms_MULAWdecoder_class==NULL) + { + ms_MULAWdecoder_class=g_new(MSMULAWDecoderClass,1); + ms_MULAWdecoder_class_init(ms_MULAWdecoder_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_MULAWdecoder_class); + return(MS_FILTER(r)); +} + + +/* FOR INTERNAL USE*/ +void ms_MULAWdecoder_init(MSMULAWDecoder *r) +{ + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->infifos=r->f_inputs; + MS_FILTER(r)->outfifos=r->f_outputs; + MS_FILTER(r)->r_mingran=MULAW_DECODER_RMAXGRAN; + memset(r->f_inputs,0,sizeof(MSFifo*)*MSMULAWDECODER_MAX_INPUTS); + memset(r->f_outputs,0,sizeof(MSFifo*)*MSMULAWDECODER_MAX_INPUTS); + +} + +void ms_MULAWdecoder_class_init(MSMULAWDecoderClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"MULAWDecoder"); + MS_FILTER_CLASS(klass)->info=(MSFilterInfo*)&MULAWinfo; + MS_FILTER_CLASS(klass)->max_finputs=MSMULAWDECODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->max_foutputs=MSMULAWDECODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->r_maxgran=MULAW_DECODER_RMAXGRAN; + MS_FILTER_CLASS(klass)->w_maxgran=MULAW_DECODER_WMAXGRAN; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_MULAWdecoder_destroy; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_MULAWdecoder_process; +} + +void ms_MULAWdecoder_process(MSMULAWDecoder *r) +{ + MSFifo *fi,*fo; + int inlen,outlen; + gchar *s,*d; + int i; + /* process output fifos, but there is only one for this class of filter*/ + + /* this is the simplest process function design: + the filter declares a r_mingran of MULAW_DECODER_RMAXGRAN, so the mediastreamer's + scheduler will call the process function each time there is MULAW_DECODER_RMAXGRAN + bytes to read in the input fifo. If there is more, then it will call it several + time in order to the fifo to be completetly processed. + This is very simple, but not very efficient because of the multiple call function + of MSFilterProcessFunc that may happen. + The MSAlawEncoder implements another design; see it. + */ + + fi=r->f_inputs[0]; + fo=r->f_outputs[0]; + + inlen=ms_fifo_get_read_ptr(fi,MULAW_DECODER_RMAXGRAN,(void**)&s); + if (s==NULL) g_error("ms_MULAWdecoder_process: internal error."); + outlen=ms_fifo_get_write_ptr(fo,MULAW_DECODER_WMAXGRAN,(void**)&d); + if (d!=NULL) + { + for(i=0;i +#include + +/*this is the class that implements a MULAWdecoder filter*/ + +#define MSMULAWDECODER_MAX_INPUTS 1 /* max output per filter*/ + + +typedef struct _MSMULAWDecoder +{ + /* the MSMULAWDecoder derivates from MSFilter, so the MSFilter object MUST be the first of the MSMULAWDecoder object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MSMULAWDECODER_MAX_INPUTS]; + MSFifo *f_outputs[MSMULAWDECODER_MAX_INPUTS]; +} MSMULAWDecoder; + +typedef struct _MSMULAWDecoderClass +{ + /* the MSMULAWDecoder derivates from MSFilter, so the MSFilter class MUST be the first of the MSMULAWDecoder class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSMULAWDecoderClass; + +/* PUBLIC */ +#define MS_MULAWDECODER(filter) ((MSMULAWDecoder*)(filter)) +#define MS_MULAWDECODER_CLASS(klass) ((MSMULAWDecoderClass*)(klass)) +MSFilter * ms_MULAWdecoder_new(void); + +/* FOR INTERNAL USE*/ +void ms_MULAWdecoder_init(MSMULAWDecoder *r); +void ms_MULAWdecoder_class_init(MSMULAWDecoderClass *klass); +void ms_MULAWdecoder_destroy( MSMULAWDecoder *obj); +void ms_MULAWdecoder_process(MSMULAWDecoder *r); + +/* tuning parameters :*/ +#define MULAW_DECODER_WMAXGRAN 320 +#define MULAW_DECODER_RMAXGRAN 160 + +extern MSCodecInfo MULAWinfo; + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msMUlawenc.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msMUlawenc.c new file mode 100644 index 00000000..2f740d89 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msMUlawenc.c @@ -0,0 +1,99 @@ + /* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "msMUlawenc.h" +#include "g711common.h" + +extern MSCodecInfo MULAWinfo; + +static MSMULAWEncoderClass *ms_MULAWencoder_class=NULL; + +MSFilter * ms_MULAWencoder_new(void) +{ + MSMULAWEncoder *r; + + r=g_new(MSMULAWEncoder,1); + ms_MULAWencoder_init(r); + if (ms_MULAWencoder_class==NULL) + { + ms_MULAWencoder_class=g_new(MSMULAWEncoderClass,1); + ms_MULAWencoder_class_init(ms_MULAWencoder_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_MULAWencoder_class); + return(MS_FILTER(r)); +} + + +/* FOR INTERNAL USE*/ +void ms_MULAWencoder_init(MSMULAWEncoder *r) +{ + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->infifos=r->f_inputs; + MS_FILTER(r)->outfifos=r->f_outputs; + MS_FILTER(r)->r_mingran=MULAW_ENCODER_RMAXGRAN; /* the filter can be called as soon as there is + something to process */ + memset(r->f_inputs,0,sizeof(MSFifo*)*MSMULAWENCODER_MAX_INPUTS); + memset(r->f_outputs,0,sizeof(MSFifo*)*MSMULAWENCODER_MAX_INPUTS); + +} + +void ms_MULAWencoder_class_init(MSMULAWEncoderClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"MULAWEncoder"); + MS_FILTER_CLASS(klass)->info=(MSFilterInfo*)&MULAWinfo; + MS_FILTER_CLASS(klass)->max_finputs=MSMULAWENCODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->max_foutputs=MSMULAWENCODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->r_maxgran=MULAW_ENCODER_RMAXGRAN; + MS_FILTER_CLASS(klass)->w_maxgran=MULAW_ENCODER_WMAXGRAN; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_MULAWencoder_destroy; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_MULAWencoder_process; +} + +void ms_MULAWencoder_process(MSMULAWEncoder *r) +{ + MSFifo *fi,*fo; + int inlen,outlen; + gchar *s,*d; + int i; + /* process output fifos, but there is only one for this class of filter*/ + + fi=r->f_inputs[0]; + fo=r->f_outputs[0]; + inlen=ms_fifo_get_read_ptr(fi,MULAW_ENCODER_RMAXGRAN,(void**)&s); + outlen=ms_fifo_get_write_ptr(fo,MULAW_ENCODER_WMAXGRAN,(void**)&d); + if (d!=NULL) + { + for(i=0;i + +/*this is the class that implements a AVdecoder filter*/ + +#define MSAVDECODER_MAX_INPUTS 1 /* max output per filter*/ + + +struct _MSAVDecoder +{ + /* the MSAVDecoder derivates from MSFilter, so the MSFilter object MUST be the first of the MSAVDecoder object + in order to the object mechanism to work*/ + MSFilter filter; + MSQueue *q_inputs[MSAVDECODER_MAX_INPUTS]; + MSQueue *q_outputs[MSAVDECODER_MAX_INPUTS]; + AVCodec *av_codec; /*the AVCodec from which this MSFilter is related */ + AVCodecContext av_context; /* the context of the AVCodec */ + gint av_opened; + int output_pix_fmt; + int width; + int height; + int skip_gob; + unsigned char buf_compressed[100000]; + int buf_size; + MSBuffer *obufwrap; /* alternate buffer, when format change is needed*/ +}; + +typedef struct _MSAVDecoder MSAVDecoder; + +struct _MSAVDecoderClass +{ + /* the MSAVDecoder derivates from MSFilter, so the MSFilter class MUST be the first of the MSAVDecoder class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +}; + +typedef struct _MSAVDecoderClass MSAVDecoderClass; + +/* PUBLIC */ +#define MS_AVDECODER(filter) ((MSAVDecoder*)(filter)) +#define MS_AVDECODER_CLASS(klass) ((MSAVDecoderClass*)(klass)) + +MSFilter *ms_h263_decoder_new(); +MSFilter *ms_mpeg_decoder_new(); +MSFilter *ms_mpeg4_decoder_new(); +MSFilter * ms_AVdecoder_new_with_codec(enum CodecID codec_id); + +gint ms_AVdecoder_set_format(MSAVDecoder *dec, gchar *fmt); +void ms_AVdecoder_set_width(MSAVDecoder *av,gint w); +void ms_AVdecoder_set_height(MSAVDecoder *av,gint h); + +/* FOR INTERNAL USE*/ +void ms_AVdecoder_init(MSAVDecoder *r, AVCodec *codec); +void ms_AVdecoder_uninit(MSAVDecoder *enc); +void ms_AVdecoder_class_init(MSAVDecoderClass *klass); +void ms_AVdecoder_destroy( MSAVDecoder *obj); +void ms_AVdecoder_process(MSAVDecoder *r); + +void ms_AVCodec_init(); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msavencoder.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msavencoder.h new file mode 100644 index 00000000..6fe5cad4 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msavencoder.h @@ -0,0 +1,90 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSAVENCODER_H +#define MSAVENCODER_H + +#include "msfilter.h" +#include "mscodec.h" +#include + +/*this is the class that implements a AVencoder filter*/ + +#define MSAVENCODER_MAX_INPUTS 1 /* max output per filter*/ +#define MSAVENCODER_MAX_OUTPUTS 2 + +struct _MSAVEncoder +{ + /* the MSAVEncoder derivates from MSFilter, so the MSFilter object MUST be the first of the MSAVEncoder object + in order to the object mechanism to work*/ + MSFilter filter; + MSQueue *q_inputs[MSAVENCODER_MAX_INPUTS]; + MSQueue *q_outputs[MSAVENCODER_MAX_OUTPUTS]; + AVCodec *av_codec; /*the AVCodec from which this MSFilter is related */ + AVCodecContext av_context; /* the context of the AVCodec */ + gint input_pix_fmt; + gint av_opened; + MSBuffer *comp_buf; + MSBuffer *yuv_buf; +}; + +typedef struct _MSAVEncoder MSAVEncoder; +/* MSAVEncoder always outputs planar YUV and accept any incoming format you should setup using + ms_AVencoder_set_format() +q_outputs[0] is the compressed video stream output +q_outputs[1] is a YUV planar buffer of the image it receives in input. +*/ + + +struct _MSAVEncoderClass +{ + /* the MSAVEncoder derivates from MSFilter, so the MSFilter class MUST be the first of the MSAVEncoder class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +}; + +typedef struct _MSAVEncoderClass MSAVEncoderClass; + +/* PUBLIC */ +#define MS_AVENCODER(filter) ((MSAVEncoder*)(filter)) +#define MS_AVENCODER_CLASS(klass) ((MSAVEncoderClass*)(klass)) + +MSFilter *ms_h263_encoder_new(); +MSFilter *ms_mpeg_encoder_new(); +MSFilter *ms_mpeg4_encoder_new(); +MSFilter * ms_AVencoder_new_with_codec(enum CodecID codec_id, MSCodecInfo *info); + +gint ms_AVencoder_set_format(MSAVEncoder *enc, gchar *fmt); + +#define ms_AVencoder_set_width(av,w) (av)->av_context.width=(w) +#define ms_AVencoder_set_height(av,h) (av)->av_context.height=(h) +#define ms_AVencoder_set_bit_rate(av,r) (av)->av_context.bit_rate=(r) + +void ms_AVencoder_set_frame_rate(MSAVEncoder *enc, gint frame_rate, gint frame_rate_base); + +/* FOR INTERNAL USE*/ +void ms_AVencoder_init(MSAVEncoder *r, AVCodec *codec); +void ms_AVencoder_uninit(MSAVEncoder *enc); +void ms_AVencoder_class_init(MSAVEncoderClass *klass); +void ms_AVencoder_destroy( MSAVEncoder *obj); +void ms_AVencoder_process(MSAVEncoder *r); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msbuffer.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msbuffer.c new file mode 100644 index 00000000..4ca3c925 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msbuffer.c @@ -0,0 +1,94 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "msbuffer.h" +#include "msutils.h" +#include + + + +MSBuffer * ms_buffer_new(guint32 size) +{ + MSBuffer *buf; + buf=(MSBuffer*)g_malloc(sizeof(MSBuffer)+size); + buf->ref_count=0; + buf->size=size; + ms_trace("ms_buffer_new: Allocating buffer of %i bytes.",size); + /* allocate the data buffer: there is a lot of optmisation that can be done by using a pool of cached buffers*/ + buf->buffer=((char*)(buf))+sizeof(MSBuffer); /* to avoid to do two allocations, + buffer info and buffer are contigous.*/ + buf->flags=MS_BUFFER_CONTIGUOUS; + return(buf); +} + +MSBuffer *ms_buffer_alloc(gint flags) +{ + MSBuffer *buf; + buf=(MSBuffer*)g_malloc(sizeof(MSBuffer)); + buf->ref_count=0; + buf->size=0; + buf->buffer=NULL; + buf->flags=0; + return(buf); +} + + +void ms_buffer_destroy(MSBuffer *buf) +{ + if (buf->flags & MS_BUFFER_CONTIGUOUS){ + g_free(buf); + } + else { + g_free(buf->buffer); + g_free(buf); + } +} + +MSMessage *ms_message_alloc() +{ + MSMessage *m=g_malloc(sizeof(MSMessage)); + memset(m,0,sizeof(MSMessage)); + return m; +} + +MSMessage *ms_message_new(gint size) +{ + MSMessage *m=ms_message_alloc(); + MSBuffer *buf=ms_buffer_new(size); + ms_message_set_buf(m,buf); + return m; +} + +void ms_message_destroy(MSMessage *m) +{ + /* the buffer is freed if its ref_count goes to zero */ + if (m->buffer!=NULL){ + m->buffer->ref_count--; + if (m->buffer->ref_count==0) ms_buffer_destroy(m->buffer); + } + g_free(m); +} + +MSMessage * ms_message_dup(MSMessage *m) +{ + MSMessage *msg=ms_message_alloc(); + ms_message_set_buf(msg,m->buffer); + return msg; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msbuffer.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msbuffer.h new file mode 100644 index 00000000..f96b35a7 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msbuffer.h @@ -0,0 +1,75 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSBUFFER_H +#define MSBUFFER_H +#include + +#ifdef HAVE_GLIB +#include +#else +#include +#endif + + +#define MS_BUFFER_LARGE 4092 + + +typedef struct _MSBuffer +{ + gchar *buffer; + guint32 size; + guint16 ref_count; + guint16 flags; +#define MS_BUFFER_CONTIGUOUS (1) +}MSBuffer; + +MSBuffer * ms_buffer_new(guint32 size); +void ms_buffer_destroy(MSBuffer *buf); + +struct _MSMessage +{ + MSBuffer *buffer; /* points to a MSBuffer */ + void *data; /*points to buffer->buffer */ + guint32 size; /* the size of the buffer to read in data. It may not be the + physical size (I mean buffer->buffer->size */ + struct _MSMessage *next; + struct _MSMessage *prev; /* MSMessage are queued into MSQueues */ +}; + +typedef struct _MSMessage MSMessage; + + +MSBuffer *ms_buffer_alloc(gint flags); +MSMessage *ms_message_new(gint size); + +#define ms_message_set_buf(m,b) do { (b)->ref_count++; (m)->buffer=(b); (m)->data=(b)->buffer; (m)->size=(b)->size; }while(0) +#define ms_message_unset_buf(m) do { (m)->buffer->ref_count--; (m)->buffer=NULL; (m)->size=0; (m)->data=NULL; } while(0) + +#define ms_message_size(m) (m)->size +void ms_message_destroy(MSMessage *m); + +MSMessage * ms_message_dup(MSMessage *m); + +/* allocate a single message without buffer */ +MSMessage *ms_message_alloc(); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mscodec.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mscodec.c new file mode 100644 index 00000000..dafa1e87 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mscodec.c @@ -0,0 +1,250 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "mscodec.h" +#include "msMUlawdec.h" + +#ifdef TRUESPEECH +extern MSCodecInfo TrueSpeechinfo; +#endif + +#ifdef VIDEO_ENABLED +extern void ms_AVCodec_init(); +#endif + +#define UDP_HDR_SZ 8 +#define RTP_HDR_SZ 12 +#define IP4_HDR_SZ 20 /*20 is the minimum, but there may be some options*/ + + + + +/* register all statically linked codecs */ +void ms_codec_register_all() +{ +/*// ms_filter_register(MS_FILTER_INFO(&GSMinfo)); + // ms_filter_register(MS_FILTER_INFO(&LPC10info));*/ + ms_filter_register(MS_FILTER_INFO(&MULAWinfo)); +#ifdef TRUESPEECH + ms_filter_register(MS_FILTER_INFO(&TrueSpeechinfo)); +#endif +#ifdef VIDEO_ENABLED + ms_AVCodec_init(); +#endif + +} + +/* returns a list of MSCodecInfo */ +GList * ms_codec_get_all_audio() +{ + GList *audio_codecs=NULL; + GList *elem=filter_list; + MSFilterInfo *info; + while (elem!=NULL) + { + info=(MSFilterInfo *)elem->data; + if (info->type==MS_FILTER_AUDIO_CODEC){ + audio_codecs=g_list_append(audio_codecs,info); + } + elem=g_list_next(elem); + } + return audio_codecs; +} + + +MSCodecInfo * ms_audio_codec_info_get(gchar *name) +{ + GList *elem=filter_list; + MSFilterInfo *info; + while (elem!=NULL) + { + info=(MSFilterInfo *)elem->data; + if ( (info->type==MS_FILTER_AUDIO_CODEC) ){ + MSCodecInfo *codinfo=(MSCodecInfo *)info; + if (strcmp(codinfo->description,name)==0){ + return MS_CODEC_INFO(info); + } + } + elem=g_list_next(elem); + } + return NULL; +} + +MSCodecInfo * ms_video_codec_info_get(gchar *name) +{ + GList *elem=filter_list; + MSFilterInfo *info; + while (elem!=NULL) + { + info=(MSFilterInfo *)elem->data; + if ( (info->type==MS_FILTER_VIDEO_CODEC) ){ + MSCodecInfo *codinfo=(MSCodecInfo *)info; + if (strcmp(codinfo->description,name)==0){ + return MS_CODEC_INFO(info); + } + } + elem=g_list_next(elem); + } + return NULL; +} + +/* returns a list of MSCodecInfo */ +GList * ms_codec_get_all_video() +{ + GList *video_codecs=NULL; + GList *elem=filter_list; + MSFilterInfo *info; + while (elem!=NULL) + { + info=(MSFilterInfo *)elem->data; + if (info->type==MS_FILTER_VIDEO_CODEC){ + video_codecs=g_list_append(video_codecs,info); + } + elem=g_list_next(elem); + } + return video_codecs; +} + +MSFilter * ms_encoder_new(gchar *name) +{ + GList *elem=filter_list; + MSFilterInfo *info; + while (elem!=NULL) + { + info=(MSFilterInfo *)elem->data; + if ((info->type==MS_FILTER_AUDIO_CODEC) || (info->type==MS_FILTER_VIDEO_CODEC)){ + MSCodecInfo *codinfo=(MSCodecInfo *)elem->data; + if (strcmp(info->name,name)==0){ + return codinfo->encoder(); + } + } + elem=g_list_next(elem); + } + return NULL; +} + +MSFilter * ms_decoder_new(gchar *name) +{ + GList *elem=filter_list; + MSFilterInfo *info; + while (elem!=NULL) + { + info=(MSFilterInfo *)elem->data; + if ((info->type==MS_FILTER_AUDIO_CODEC) || (info->type==MS_FILTER_VIDEO_CODEC)){ + MSCodecInfo *codinfo=(MSCodecInfo *)elem->data; + if (strcmp(info->name,name)==0){ + return codinfo->decoder(); + } + } + elem=g_list_next(elem); + } + return NULL; +} + +MSFilter * ms_encoder_new_with_pt(gint pt) +{ + GList *elem=filter_list; + MSFilterInfo *info; + while (elem!=NULL) + { + info=(MSFilterInfo *)elem->data; + if ((info->type==MS_FILTER_AUDIO_CODEC) || (info->type==MS_FILTER_VIDEO_CODEC)){ + MSCodecInfo *codinfo=(MSCodecInfo *)elem->data; + if (codinfo->pt==pt){ + return codinfo->encoder(); + } + } + elem=g_list_next(elem); + } + return NULL; +} + +MSFilter * ms_decoder_new_with_pt(gint pt) +{ + GList *elem=filter_list; + MSFilterInfo *info; + while (elem!=NULL) + { + info=(MSFilterInfo *)elem->data; + if ((info->type==MS_FILTER_AUDIO_CODEC) || (info->type==MS_FILTER_VIDEO_CODEC)){ + MSCodecInfo *codinfo=(MSCodecInfo *)elem->data; + if (codinfo->pt==pt){ + return codinfo->decoder(); + } + } + elem=g_list_next(elem); + } + return NULL; +} + +MSFilter * ms_decoder_new_with_string_id(gchar *id) +{ + GList *elem=filter_list; + MSFilterInfo *info; + while (elem!=NULL) + { + info=(MSFilterInfo *)elem->data; + if ((info->type==MS_FILTER_AUDIO_CODEC) || (info->type==MS_FILTER_VIDEO_CODEC)){ + MSCodecInfo *codinfo=(MSCodecInfo *)elem->data; + if (strcasecmp(codinfo->description,id)==0){ + return codinfo->decoder(); + } + } + elem=g_list_next(elem); + } + return NULL; +} + +MSFilter * ms_encoder_new_with_string_id(gchar *id) +{ + GList *elem=filter_list; + MSFilterInfo *info; + while (elem!=NULL) + { + info=(MSFilterInfo *)elem->data; + if ((info->type==MS_FILTER_AUDIO_CODEC) || (info->type==MS_FILTER_VIDEO_CODEC)){ + MSCodecInfo *codinfo=(MSCodecInfo *)elem->data; + if (strcasecmp(codinfo->description,id)==0){ + return codinfo->encoder(); + } + } + elem=g_list_next(elem); + } + return NULL; +} +/* return 0 if codec can be used with bandwidth, -1 else*/ +int ms_codec_is_usable(MSCodecInfo *codec,double bandwidth) +{ + double codec_band; + double npacket; + double packet_size; + + if (((MSFilterInfo*)codec)->type==MS_FILTER_AUDIO_CODEC) + { + /* calculate the total bandwdith needed by codec (including headers for rtp, udp, ip)*/ + /* number of packet per second*/ + npacket=2.0*(double)(codec->rate)/(double)(codec->fr_size); + packet_size=(double)(codec->dt_size)+UDP_HDR_SZ+RTP_HDR_SZ+IP4_HDR_SZ; + codec_band=packet_size*8.0*npacket; + } + else return -1; + return(codec_band +#include +#include +#include +#include +#include + +static MSCopyClass *ms_copy_class=NULL; + +MSFilter * ms_copy_new(void) +{ + MSCopy *r; + + r=g_new(MSCopy,1); + ms_copy_init(r); + if (ms_copy_class==NULL) + { + ms_copy_class=g_new(MSCopyClass,1); + ms_copy_class_init(ms_copy_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_copy_class); + return(MS_FILTER(r)); +} + + +/* FOR INTERNAL USE*/ +void ms_copy_init(MSCopy *r) +{ + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->infifos=r->f_inputs; + MS_FILTER(r)->outfifos=r->f_outputs; + MS_FILTER(r)->r_mingran=MSCOPY_DEF_GRAN; + memset(r->f_inputs,0,sizeof(MSFifo*)*MSCOPY_MAX_INPUTS); + memset(r->f_outputs,0,sizeof(MSFifo*)*MSCOPY_MAX_INPUTS); +} + +void ms_copy_class_init(MSCopyClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"fifocopier"); + MS_FILTER_CLASS(klass)->max_finputs=MSCOPY_MAX_INPUTS; + MS_FILTER_CLASS(klass)->max_foutputs=MSCOPY_MAX_INPUTS; + MS_FILTER_CLASS(klass)->r_maxgran=MSCOPY_DEF_GRAN; + MS_FILTER_CLASS(klass)->w_maxgran=MSCOPY_DEF_GRAN; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_copy_destroy; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_copy_process; +} + +void ms_copy_process(MSCopy *r) +{ + MSFifo *fi,*fo; + int err1; + gint gran=MS_FILTER(r)->klass->r_maxgran; + void *s,*d; + + /* process output fifos, but there is only one for this class of filter*/ + + fi=r->f_inputs[0]; + fo=r->f_outputs[0]; + if (fi!=NULL) + { + err1=ms_fifo_get_read_ptr(fi,gran,&s); + if (err1>0) err1=ms_fifo_get_write_ptr(fo,gran,&d); + if (err1>0) + { + memcpy(d,s,gran); + } + } +} + +void ms_copy_destroy( MSCopy *obj) +{ + g_free(obj); +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mscopy.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mscopy.h new file mode 100644 index 00000000..2b5749b9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mscopy.h @@ -0,0 +1,61 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSCOPY_H +#define MSCOPY_H + +#include "msfilter.h" + + +/*this is the class that implements a copy filter*/ + +#define MSCOPY_MAX_INPUTS 1 /* max output per filter*/ + +#define MSCOPY_DEF_GRAN 64 /* the default granularity*/ + +typedef struct _MSCopy +{ + /* the MSCopy derivates from MSFilter, so the MSFilter object MUST be the first of the MSCopy object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MSCOPY_MAX_INPUTS]; + MSFifo *f_outputs[MSCOPY_MAX_INPUTS]; +} MSCopy; + +typedef struct _MSCopyClass +{ + /* the MSCopy derivates from MSFilter, so the MSFilter class MUST be the first of the MSCopy class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSCopyClass; + +/* PUBLIC */ +#define MS_COPY(filter) ((MSCopy*)(filter)) +#define MS_COPY_CLASS(klass) ((MSCopyClass*)(klass)) +MSFilter * ms_copy_new(void); + +/* FOR INTERNAL USE*/ +void ms_copy_init(MSCopy *r); +void ms_copy_class_init(MSCopyClass *klass); +void ms_copy_destroy( MSCopy *obj); +void ms_copy_process(MSCopy *r); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfdispatcher.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfdispatcher.c new file mode 100644 index 00000000..692bbb7b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfdispatcher.c @@ -0,0 +1,94 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a dispatcher of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "msfdispatcher.h" + +static MSFdispatcherClass *ms_fdispatcher_class=NULL; + +MSFilter * ms_fdispatcher_new(void) +{ + MSFdispatcher *obj; + obj=g_malloc(sizeof(MSFdispatcher)); + if (ms_fdispatcher_class==NULL){ + ms_fdispatcher_class=g_malloc(sizeof(MSFdispatcherClass)); + ms_fdispatcher_class_init(ms_fdispatcher_class); + } + MS_FILTER(obj)->klass=MS_FILTER_CLASS(ms_fdispatcher_class); + ms_fdispatcher_init(obj); + return MS_FILTER(obj); +} + + +void ms_fdispatcher_init(MSFdispatcher *obj) +{ + ms_filter_init(MS_FILTER(obj)); + MS_FILTER(obj)->infifos=obj->f_inputs; + MS_FILTER(obj)->outfifos=obj->f_outputs; + MS_FILTER(obj)->r_mingran=MS_FDISPATCHER_DEF_GRAN; + memset(obj->f_inputs,0,sizeof(MSFifo*)*MS_FDISPATCHER_MAX_INPUTS); + memset(obj->f_outputs,0,sizeof(MSFifo*)*MS_FDISPATCHER_MAX_OUTPUTS); +} + + + +void ms_fdispatcher_class_init(MSFdispatcherClass *klass) +{ + MSFilterClass *parent_class=MS_FILTER_CLASS(klass); + ms_filter_class_init(parent_class); + ms_filter_class_set_name(parent_class,"fdispatcher"); + parent_class->max_finputs=MS_FDISPATCHER_MAX_INPUTS; + parent_class->max_foutputs=MS_FDISPATCHER_MAX_OUTPUTS; + parent_class->r_maxgran=MS_FDISPATCHER_DEF_GRAN; + parent_class->w_maxgran=MS_FDISPATCHER_DEF_GRAN; + parent_class->destroy=(MSFilterDestroyFunc)ms_fdispatcher_destroy; + parent_class->process=(MSFilterProcessFunc)ms_fdispatcher_process; +} + + +void ms_fdispatcher_destroy( MSFdispatcher *obj) +{ + g_free(obj); +} + +void ms_fdispatcher_process(MSFdispatcher *obj) +{ + gint i; + MSFifo *inf=obj->f_inputs[0]; + + + if (inf!=NULL){ + void *s,*d; + /* dispatch fifos */ + while ( ms_fifo_get_read_ptr(inf,MS_FDISPATCHER_DEF_GRAN,&s) >0 ){ + for (i=0;if_outputs[i]; + + if (outf!=NULL) + { + ms_fifo_get_write_ptr(outf,MS_FDISPATCHER_DEF_GRAN,&d); + if (d!=NULL) memcpy(d,s,MS_FDISPATCHER_DEF_GRAN); + } + } + } + } +} + + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfdispatcher.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfdispatcher.h new file mode 100644 index 00000000..b1b457df --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfdispatcher.h @@ -0,0 +1,61 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a dispatcher of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSFDISPATCHER_H +#define MSFDISPATCHER_H + +#include "msfilter.h" + + +/*this is the class that implements a fdispatcher filter*/ + +#define MS_FDISPATCHER_MAX_INPUTS 1 +#define MS_FDISPATCHER_MAX_OUTPUTS 5 +#define MS_FDISPATCHER_DEF_GRAN 64 /* the default granularity*/ + +typedef struct _MSFdispatcher +{ + /* the MSFdispatcher derivates from MSFilter, so the MSFilter object MUST be the first of the MSFdispatcher object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MS_FDISPATCHER_MAX_INPUTS]; + MSFifo *f_outputs[MS_FDISPATCHER_MAX_OUTPUTS]; +} MSFdispatcher; + +typedef struct _MSFdispatcherClass +{ + /* the MSFdispatcher derivates from MSFilter, so the MSFilter class MUST be the first of the MSFdispatcher class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSFdispatcherClass; + +/* PUBLIC */ +#define MS_FDISPATCHER(filter) ((MSFdispatcher*)(filter)) +#define MS_FDISPATCHER_CLASS(klass) ((MSFdispatcherClass*)(klass)) +MSFilter * ms_fdispatcher_new(void); + +/* FOR INTERNAL USE*/ +void ms_fdispatcher_init(MSFdispatcher *r); +void ms_fdispatcher_class_init(MSFdispatcherClass *klass); +void ms_fdispatcher_destroy( MSFdispatcher *obj); +void ms_fdispatcher_process(MSFdispatcher *r); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfifo.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfifo.c new file mode 100644 index 00000000..7e783c24 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfifo.c @@ -0,0 +1,168 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include "msutils.h" +#include "msfifo.h" + +MSFifo * ms_fifo_new(MSBuffer *buf, gint r_gran, gint w_gran, gint r_offset, gint w_offset) +{ + MSFifo *fifo; + gint saved_offset=MAX(r_gran+r_offset,w_offset); + + g_return_val_if_fail(saved_offset<=(buf->size),NULL); + fifo=g_malloc(sizeof(MSFifo)); + fifo->buffer=buf; + fifo->r_gran=r_gran; + fifo->w_gran=w_gran; + fifo->begin=fifo->wr_ptr=fifo->rd_ptr=buf->buffer+saved_offset; + fifo->readsize=0; + fifo->size=fifo->writesize=buf->size-saved_offset; + fifo->saved_offset= saved_offset; + fifo->r_end=fifo->w_end=buf->buffer+buf->size; + fifo->pre_end=fifo->w_end-saved_offset; + buf->ref_count++; + fifo->prev_data=NULL; + fifo->next_data=NULL; + ms_trace("fifo base=%x, begin=%x, end=%x, saved_offset=%i, size=%i" + ,fifo->buffer->buffer,fifo->begin,fifo->w_end,fifo->saved_offset,fifo->size); + return(fifo); +} + +MSFifo * ms_fifo_new_with_buffer(gint r_gran, gint w_gran, gint r_offset, gint w_offset, + gint min_fifo_size) +{ + MSFifo *fifo; + MSBuffer *buf; + gint saved_offset=MAX(r_gran+r_offset,w_offset); + gint fifo_size; + gint tmp; + if (min_fifo_size==0) min_fifo_size=w_gran; + + /* we must allocate a fifo with a size multiple of min_fifo_size, + with a saved_offset */ + if (min_fifo_size>MS_BUFFER_LARGE) + fifo_size=(min_fifo_size) + saved_offset; + else fifo_size=(6*min_fifo_size) + saved_offset; + buf=ms_buffer_new(fifo_size); + fifo=ms_fifo_new(buf,r_gran,w_gran,r_offset,w_offset); + ms_trace("fifo_size=%i",fifo_size); + return(fifo); +} + +void ms_fifo_destroy( MSFifo *fifo) +{ + g_free(fifo); +} + +void ms_fifo_destroy_with_buffer(MSFifo *fifo) +{ + ms_buffer_destroy(fifo->buffer); + ms_fifo_destroy(fifo); +} + +gint ms_fifo_get_read_ptr(MSFifo *fifo, gint bsize, void **ret_ptr) +{ + gchar *rnext; + + *ret_ptr=NULL; + /* //ms_trace("ms_fifo_get_read_ptr: entering.");*/ + g_return_val_if_fail(bsize<=fifo->r_gran,-EINVAL); + + if (bsize>fifo->readsize) + { + ms_trace("Not enough data: bsize=%i, readsize=%i",bsize,fifo->readsize); + return (-ENODATA); + } + + rnext=fifo->rd_ptr+bsize; + if (rnext<=fifo->r_end){ + + *ret_ptr=fifo->rd_ptr; + fifo->rd_ptr=rnext; + }else{ + int unread=fifo->r_end-fifo->rd_ptr; + *ret_ptr=fifo->begin-unread; + memcpy(fifo->buffer->buffer,fifo->r_end-fifo->saved_offset,fifo->saved_offset); + fifo->rd_ptr=(char*)(*ret_ptr) + bsize; + fifo->r_end=fifo->w_end; /* this is important ! */ + ms_trace("moving read ptr to %x",fifo->rd_ptr); + + } + /* update write size*/ + fifo->writesize+=bsize; + fifo->readsize-=bsize; + return bsize; +} + + +void ms_fifo_update_write_ptr(MSFifo *fifo, gint written){ + gint reserved=fifo->wr_ptr-fifo->prev_wr_ptr; + gint unwritten; + g_return_if_fail(reserved>=0); + unwritten=reserved-written; + g_return_if_fail(unwritten>=0); + /* fix readsize and writesize */ + fifo->readsize-=unwritten; + fifo->writesize+=unwritten; + fifo->wr_ptr+=written; +} + +gint ms_fifo_get_write_ptr(MSFifo *fifo, gint bsize, void **ret_ptr) +{ + gchar *wnext; + + *ret_ptr=NULL; + /* //ms_trace("ms_fifo_get_write_ptr: Entering.");*/ + g_return_val_if_fail(bsize<=fifo->w_gran,-EINVAL); + if (bsize>fifo->writesize) + { + ms_trace("Not enough space: bsize=%i, writesize=%i",bsize,fifo->writesize); + *ret_ptr=NULL; + return(-ENODATA); + } + wnext=fifo->wr_ptr+bsize; + if (wnext<=fifo->w_end){ + *ret_ptr=fifo->wr_ptr; + fifo->wr_ptr=wnext; + }else{ + *ret_ptr=fifo->begin; + fifo->r_end=fifo->wr_ptr; + fifo->wr_ptr=fifo->begin+bsize; + ms_trace("moving write ptr to %x",fifo->wr_ptr); + } + fifo->prev_wr_ptr=*ret_ptr; + /* update readsize*/ + fifo->readsize+=bsize; + fifo->writesize-=bsize; + /* //ms_trace("ms_fifo_get_write_ptr: readsize=%i, writesize=%i",fifo->readsize,fifo->writesize);*/ + return bsize; +} + +gint ms_fifo_get_rw_ptr(MSFifo *f1,void **p1,gint minsize1, + MSFifo *f2,void **p2,gint minsize2) +{ + gint rbsize,wbsize; + + rbsize=MIN(f1->readsize,(f1->pre_end-f1->rd_ptr)); + wbsize=MIN(f2->writesize,(f2->w_end-f2->wr_ptr)); + return 0; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfifo.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfifo.h new file mode 100644 index 00000000..fde1bece --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfifo.h @@ -0,0 +1,73 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifdef HAVE_GLIB +#include +#else +#include "glist.h" +#endif +#include "msbuffer.h" + +typedef struct _MSFifo +{ + gint r_gran; /*maximum granularity for reading*/ + gint w_gran; /*maximum granularity for writing*/ + gchar * rd_ptr; /* read pointer on the position where there is something to read on the MSBuffer */ + guint32 readsize; + gchar * wr_ptr; + gchar * prev_wr_ptr; + guint32 writesize; /* write pointer on the position where it is possible to write on the MSBuffer */ + gchar * begin; /* rd_ptr et wr_ptr must all be >=begin*/ + guint32 size; /* the length of the fifo, but this may not be equal to buffer->size*/ + guint32 saved_offset; + gchar * pre_end; /* the end of the buffer that is copied at the begginning when we wrap around*/ + gchar * w_end; /* when a wr ptr is expected to exceed end_offset, + it must be wrapped around to go at the beginning of the buffer. This is the end of the buffer*/ + gchar * r_end; /* this is the last position written at the end of the fifo. If a read ptr is expected to + exceed this pointer, it must be put at the begginning of the buffer */ + void *prev_data; /*user data, usually the writing MSFilter*/ + void *next_data; /* user data, usually the reading MSFilter */ + MSBuffer *buffer; +} MSFifo; + +/* constructor*/ +/* r_gran: max granularity for reading (in number of bytes)*/ +/* w_gran: max granularity for writing (in number of bytes)*/ +/* r_offset: number of bytes that are kept available behind read pointer (for recursive filters)*/ +/* w_offset: number of bytes that are kept available behind write pointer (for recursive filters)*/ +/* buf is a MSBuffer that should be compatible with the above parameter*/ +MSFifo * ms_fifo_new(MSBuffer *buf, gint r_gran, gint w_gran, gint r_offset, gint w_offset); + +/*does the same that ms_fifo_new(), but also allocate a compatible buffer automatically*/ +MSFifo * ms_fifo_new_with_buffer(gint r_gran, gint w_gran, gint r_offset, gint w_offset, gint min_buffer_size); + +void ms_fifo_destroy( MSFifo *fifo); + +void ms_fifo_destroy_with_buffer(MSFifo *fifo); + +/* get data to read */ +gint ms_fifo_get_read_ptr(MSFifo *fifo, gint bsize, void **ret_ptr); + +/* get a buffer to write*/ +gint ms_fifo_get_write_ptr(MSFifo *fifo, gint bsize, void **ret_ptr); + +/* in case the buffer got by ms_fifo_get_write_ptr() could not be filled completely, you must +tell it by using this function */ +void ms_fifo_update_write_ptr(MSFifo *fifo, gint written); diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfilter.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfilter.c new file mode 100644 index 00000000..c67e9f0e --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msfilter.c @@ -0,0 +1,537 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include +#include "msfilter.h" + + + +void ms_filter_init(MSFilter *filter) +{ + filter->finputs=0; + filter->foutputs=0; + filter->qinputs=0; + filter->qoutputs=0; + filter->infifos=NULL; + filter->outfifos=NULL; + filter->inqueues=NULL; + filter->outqueues=NULL; + filter->lock=g_mutex_new(); + filter->min_fifo_size=0x7fff; + filter->notify_event=NULL; + filter->userdata=NULL; +} + +void ms_filter_uninit(MSFilter *filter) +{ + g_mutex_free(filter->lock); +} + +void ms_filter_class_init(MSFilterClass *filterclass) +{ + filterclass->name=NULL; + filterclass->max_finputs=0; + filterclass->max_foutputs=0; + filterclass->max_qinputs=0; + filterclass->max_qoutputs=0; + filterclass->r_maxgran=0; + filterclass->w_maxgran=0; + filterclass->r_offset=0; + filterclass->w_offset=0; + filterclass->set_property=NULL; + filterclass->get_property=NULL; + filterclass->setup=NULL; + filterclass->unsetup=NULL; + filterclass->process=NULL; + filterclass->destroy=NULL; + filterclass->attributes=0; + filterclass->ref_count=0; +} + +/* find output queue */ +gint find_oq(MSFilter *m1,MSQueue *oq) +{ + gint i; + + for (i=0;imax_qoutputs;i++){ + if (m1->outqueues[i]==oq) return i; + } + + return -1; +} + +/* find input queue */ +gint find_iq(MSFilter *m1,MSQueue *iq) +{ + gint i; + for (i=0;imax_qinputs;i++){ + if (m1->inqueues[i]==iq) return i; + } + return -1; +} + +/* find output fifo */ +gint find_of(MSFilter *m1,MSFifo *of) +{ + gint i; + for (i=0;imax_foutputs;i++){ + if (m1->outfifos[i]==of) return i; + } + + return -1; +} + +/* find input fifo */ +gint find_if(MSFilter *m1,MSFifo *inf) +{ + gint i; + + for (i=0;imax_finputs;i++){ + if (m1->infifos[i]==inf) return i; + } + + return -1; +} + +#define find_free_iq(_m1) find_iq(_m1,NULL) +#define find_free_oq(_m1) find_oq(_m1,NULL) +#define find_free_if(_m1) find_if(_m1,NULL) +#define find_free_of(_m1) find_of(_m1,NULL) + +int ms_filter_add_link(MSFilter *m1, MSFilter *m2) +{ + gint m1_q=-1; + gint m1_f=-1; + gint m2_q=-1; + gint m2_f=-1; + /* determine the type of link we can add */ + m1_q=find_free_oq(m1); + m1_f=find_free_of(m1); + m2_q=find_free_iq(m2); + m2_f=find_free_if(m2); + if ((m1_q!=-1) && (m2_q!=-1)){ + /* link with queues */ + ms_trace("m1_q=%i , m2_q=%i",m1_q,m2_q); + return ms_filter_link(m1,m1_q,m2,m2_q,LINK_QUEUE); + } + if ((m1_f!=-1) && (m2_f!=-1)){ + /* link with queues */ + ms_trace("m1_f=%i , m2_f=%i",m1_f,m2_f); + return ms_filter_link(m1,m1_f,m2,m2_f,LINK_FIFO); + } + g_warning("ms_filter_add_link: could not link."); + return -1; +} +/** + * ms_filter_link: + * @m1: A #MSFilter object. + * @pin1: The pin number on @m1. + * @m2: A #MSFilter object. + * @pin2: The pin number on @m2. + * @linktype: Type of connection, it may be #LINK_QUEUE, #LINK_FIFOS. + * + * This function links two MSFilter object between them. It must be used to make chains of filters. + * All data outgoing from pin1 of m1 will go to the input pin2 of m2. + * The way to communicate can be fifos or queues, depending of the nature of the filters. Filters can have + * multiple queue pins and multiple fifo pins, but most of them have only one queue input/output or only one + * fifo input/output. Fifos are usally used by filters doing audio processing, while queues are used by filters doing + * video processing. + * + * Returns: 0 if successfull, a negative value reprensenting the errno.h error. + */ +int ms_filter_link(MSFilter *m1, gint pin1, MSFilter *m2,gint pin2, int linktype) +{ + MSQueue *q; + MSFifo *fifo; + + g_message("ms_filter_add_link: %s,%i -> %s,%i",m1->klass->name,pin1,m2->klass->name,pin2); + switch(linktype) + { + case LINK_QUEUE: + /* Are filter m1 and m2 able to accept more queues connections ?*/ + g_return_val_if_fail(m1->qoutputsmax_qoutputs,-EMLINK); + g_return_val_if_fail(m2->qinputsmax_qinputs,-EMLINK); + /* Are filter m1 and m2 valid with their inputs and outputs ?*/ + g_return_val_if_fail(m1->outqueues!=NULL,-EFAULT); + g_return_val_if_fail(m2->inqueues!=NULL,-EFAULT); + /* are the requested pins exists ?*/ + g_return_val_if_fail(pin1max_qoutputs,-EINVAL); + g_return_val_if_fail(pin2max_qinputs,-EINVAL); + /* are the requested pins free ?*/ + g_return_val_if_fail(m1->outqueues[pin1]==NULL,-EBUSY); + g_return_val_if_fail(m2->inqueues[pin2]==NULL,-EBUSY); + + q=ms_queue_new(); + m1->outqueues[pin1]=m2->inqueues[pin2]=q; + m1->qoutputs++; + m2->qinputs++; + q->prev_data=(void*)m1; + q->next_data=(void*)m2; + break; + case LINK_FIFO: + /* Are filter m1 and m2 able to accept more fifo connections ?*/ + g_return_val_if_fail(m1->foutputsmax_foutputs,-EMLINK); + g_return_val_if_fail(m2->finputsmax_finputs,-EMLINK); + /* Are filter m1 and m2 valid with their inputs and outputs ?*/ + g_return_val_if_fail(m1->outfifos!=NULL,-EFAULT); + g_return_val_if_fail(m2->infifos!=NULL,-EFAULT); + /* are the requested pins exists ?*/ + g_return_val_if_fail(pin1max_foutputs,-EINVAL); + g_return_val_if_fail(pin2max_finputs,-EINVAL); + /* are the requested pins free ?*/ + g_return_val_if_fail(m1->outfifos[pin1]==NULL,-EBUSY); + g_return_val_if_fail(m2->infifos[pin2]==NULL,-EBUSY); + + if (MS_FILTER_GET_CLASS(m1)->attributes & FILTER_IS_SOURCE) + { + /* configure min_fifo_size */ + fifo=ms_fifo_new_with_buffer(MS_FILTER_GET_CLASS(m2)->r_maxgran, + MS_FILTER_GET_CLASS(m1)->w_maxgran, + MS_FILTER_GET_CLASS(m2)->r_offset, + MS_FILTER_GET_CLASS(m1)->w_offset, + MS_FILTER_GET_CLASS(m1)->w_maxgran); + m2->min_fifo_size=MS_FILTER_GET_CLASS(m1)->w_maxgran; + } + else + { + gint next_size; + ms_trace("ms_filter_add_link: min_fifo_size=%i",m1->min_fifo_size); + fifo=ms_fifo_new_with_buffer(MS_FILTER_GET_CLASS(m2)->r_maxgran, + MS_FILTER_GET_CLASS(m1)->w_maxgran, + MS_FILTER_GET_CLASS(m2)->r_offset, + MS_FILTER_GET_CLASS(m1)->w_offset, + m1->min_fifo_size); + if (MS_FILTER_GET_CLASS(m2)->r_maxgran>0){ + next_size=(m1->min_fifo_size* + (MS_FILTER_GET_CLASS(m2)->w_maxgran)) / + (MS_FILTER_GET_CLASS(m2)->r_maxgran); + }else next_size=m1->min_fifo_size; + ms_trace("ms_filter_add_link: next_size=%i",next_size); + m2->min_fifo_size=next_size; + } + + + m1->outfifos[pin1]=m2->infifos[pin2]=fifo; + m1->foutputs++; + m2->finputs++; + fifo->prev_data=(void*)m1; + fifo->next_data=(void*)m2; + break; + } + return 0; +} +/** + * ms_filter_unlink: + * @m1: A #MSFilter object. + * @pin1: The pin number on @m1. + * @m2: A #MSFilter object. + * @pin2: The pin number on @m2. + * @linktype: Type of connection, it may be #LINK_QUEUE, #LINK_FIFOS. + * + * Unlink @pin1 of filter @m1 from @pin2 of filter @m2. @linktype specifies what type of connection is removed. + * + * Returns: 0 if successfull, a negative value reprensenting the errno.h error. + */ +int ms_filter_unlink(MSFilter *m1, gint pin1, MSFilter *m2,gint pin2,gint linktype) +{ + switch(linktype) + { + case LINK_QUEUE: + /* Are filter m1 and m2 valid with their inputs and outputs ?*/ + g_return_val_if_fail(m1->outqueues!=NULL,-EFAULT); + g_return_val_if_fail(m2->inqueues!=NULL,-EFAULT); + /* are the requested pins exists ?*/ + g_return_val_if_fail(pin1max_qoutputs,-EINVAL); + g_return_val_if_fail(pin2max_qinputs,-EINVAL); + /* are the requested pins busy ?*/ + g_return_val_if_fail(m1->outqueues[pin1]!=NULL,-ENOENT); + g_return_val_if_fail(m2->inqueues[pin2]!=NULL,-ENOENT); + /* are the two pins connected together ?*/ + g_return_val_if_fail(m1->outqueues[pin1]==m2->inqueues[pin2],-EINVAL); + + ms_queue_destroy(m1->outqueues[pin1]); + m1->outqueues[pin1]=m2->inqueues[pin2]=NULL; + m1->qoutputs--; + m2->qinputs--; + + break; + case LINK_FIFO: + /* Are filter m1 and m2 valid with their inputs and outputs ?*/ + g_return_val_if_fail(m1->outfifos!=NULL,-EFAULT); + g_return_val_if_fail(m2->infifos!=NULL,-EFAULT); + /* are the requested pins exists ?*/ + g_return_val_if_fail(pin1max_foutputs,-EINVAL); + g_return_val_if_fail(pin2max_finputs,-EINVAL); + /* are the requested pins busy ?*/ + g_return_val_if_fail(m1->outfifos[pin1]!=NULL,-ENOENT); + g_return_val_if_fail(m2->infifos[pin2]!=NULL,-ENOENT); + /* are the two pins connected together ?*/ + g_return_val_if_fail(m1->outfifos[pin1]==m2->infifos[pin2],-EINVAL); + ms_fifo_destroy_with_buffer(m1->outfifos[pin1]); + m1->outfifos[pin1]=m2->infifos[pin2]=NULL; + m1->foutputs--; + m2->finputs--; + break; + } + return 0; +} + +/** + *ms_filter_remove_links: + *@m1: a filter + *@m2: another filter. + * + * Removes all links between m1 and m2. + * + *Returns: 0 if one more link have been removed, -1 if not. +**/ +gint ms_filter_remove_links(MSFilter *m1, MSFilter *m2) +{ + int i,j; + int removed=-1; + MSQueue *qo; + MSFifo *fo; + /* takes all outputs of m1, and removes the one that goes to m2 */ + if (m1->outqueues!=NULL){ + for (i=0;imax_qoutputs;i++) + { + qo=m1->outqueues[i]; + if (qo!=NULL){ + MSFilter *rmf; + /* test if the queue connects to m2 */ + rmf=(MSFilter*)qo->next_data; + if (rmf==m2){ + j=find_iq(rmf,qo); + if (j==-1) g_error("Could not find input queue: impossible case."); + ms_filter_unlink(m1,i,m2,j,LINK_QUEUE); + removed=0; + } + } + } + } + if (m1->outfifos!=NULL){ + for (i=0;imax_foutputs;i++) + { + fo=m1->outfifos[i]; + if (fo!=NULL){ + MSFilter *rmf; + /* test if the queue connects to m2 */ + rmf=(MSFilter*)fo->next_data; + if (rmf==m2){ + j=find_if(rmf,fo); + if (j==-1) g_error("Could not find input fifo: impossible case."); + ms_filter_unlink(m1,i,m2,j,LINK_FIFO); + removed=0; + } + } + } + } + return removed; +} + +/** + * ms_filter_fifos_have_data: + * @f: a #MSFilter object. + * + * Tells if the filter has enough data in its input fifos in order to be executed succesfully. + * + * Returns: 1 if it can be executed, 0 else. + */ +gint ms_filter_fifos_have_data(MSFilter *f) +{ + gint i,j; + gint max_inputs=f->klass->max_finputs; + gint con_inputs=f->finputs; + MSFifo *fifo; + /* test fifos */ + for(i=0,j=0; (iinfifos[i]; + if (fifo!=NULL) + { + j++; + if (fifo->readsize==0) return 0; + if (fifo->readsize>=f->r_mingran) return 1; + } + } + return 0; +} + +/** + * ms_filter_queues_have_data: + * @f: a #MSFilter object. + * + * Tells if the filter has enough data in its input queues in order to be executed succesfully. + * + * Returns: 1 if it can be executed, 0 else. + */ +gint ms_filter_queues_have_data(MSFilter *f) +{ + gint i,j; + gint max_inputs=f->klass->max_qinputs; + gint con_inputs=f->qinputs; + MSQueue *q; + /* test queues */ + for(i=0,j=0; (iinqueues[i]; + if (q!=NULL) + { + j++; + if (ms_queue_can_get(q)) return 1; + } + } + return 0; +} + + + +void ms_filter_destroy(MSFilter *f) +{ + /* first check if the filter is disconnected from any others */ + g_return_if_fail(f->finputs==0); + g_return_if_fail(f->foutputs==0); + g_return_if_fail(f->qinputs==0); + g_return_if_fail(f->qoutputs==0); + f->klass->destroy(f); +} + +GList *filter_list=NULL; + +void ms_filter_register(MSFilterInfo *info) +{ + gpointer tmp; + tmp=g_list_find(filter_list,info); + if (tmp==NULL) filter_list=g_list_append(filter_list,(gpointer)info); +} + +void ms_filter_unregister(MSFilterInfo *info) +{ + filter_list=g_list_remove(filter_list,(gpointer)info); +} + +static gint compare_names(gpointer info, gpointer name) +{ + MSFilterInfo *i=(MSFilterInfo*) info; + return (strcmp(i->name,name)); +} + +MSFilterInfo * ms_filter_get_by_name(const gchar *name) +{ + GList *elem=g_list_find_custom(filter_list, + (gpointer)name,(GCompareFunc)compare_names); + if (elem!=NULL){ + return (MSFilterInfo*)elem->data; + } + return NULL; +} + + + +MSFilter * ms_filter_new_with_name(const gchar *name) +{ + MSFilterInfo *info=ms_filter_get_by_name(name); + if (info!=NULL) return info->constructor(); + g_warning("ms_filter_new_with_name: no filter named %s found.",name); + return NULL; +} + + +/* find the first codec in the left part of the stream */ +MSFilter * ms_filter_search_upstream_by_type(MSFilter *f,MSFilterType type) +{ + MSFilter *tmp=f; + MSFilterInfo *info; + + if ((tmp->infifos!=NULL) && (tmp->infifos[0]!=NULL)){ + tmp=(MSFilter*) tmp->infifos[0]->prev_data; + while(1){ + info=MS_FILTER_GET_CLASS(tmp)->info; + if (info!=NULL){ + if ( (info->type==type) ){ + return tmp; + } + } + if ((tmp->infifos!=NULL) && (tmp->infifos[0]!=NULL)) + tmp=(MSFilter*) tmp->infifos[0]->prev_data; + else break; + } + } + tmp=f; + if ((tmp->inqueues!=NULL) && (tmp->inqueues[0]!=NULL)){ + tmp=(MSFilter*) tmp->inqueues[0]->prev_data; + while(1){ + + info=MS_FILTER_GET_CLASS(tmp)->info; + if (info!=NULL){ + if ( (info->type==type)){ + return tmp; + } + }else g_warning("ms_filter_search_upstream_by_type: filter %s has no info." + ,MS_FILTER_GET_CLASS(tmp)->name); + if ((tmp->inqueues!=NULL) && (tmp->inqueues[0]!=NULL)) + tmp=(MSFilter*) tmp->inqueues[0]->prev_data; + else break; + } + } + return NULL; +} + + +int ms_filter_set_property(MSFilter *f, MSFilterProperty prop,void *value) +{ + if (f->klass->set_property!=NULL){ + return f->klass->set_property(f,prop,value); + } + return 0; +} + +int ms_filter_get_property(MSFilter *f, MSFilterProperty prop,void *value) +{ + if (f->klass->get_property!=NULL){ + return f->klass->get_property(f,prop,value); + } + return -1; +} + +void ms_filter_set_notify_func(MSFilter* filter,MSFilterNotifyFunc func, gpointer userdata) +{ + filter->notify_event=func; + filter->userdata=userdata; +} + +void ms_filter_notify_event(MSFilter *filter,gint event, gpointer arg) +{ + if (filter->notify_event!=NULL){ + filter->notify_event(filter,event,arg,filter->userdata); + } +} + +void swap_buffer(gchar *buffer, gint len) +{ + int i; + gchar tmp; + for (i=0;i + +#ifdef HAVE_GLIB +#include +#include +#else +#undef VERSION +#undef PACKAGE +#include +#endif + +#include +#include "msutils.h" +#include "msfifo.h" +#include "msqueue.h" + +struct _MSFilter; +/*this is the abstract object and class for all filter types*/ +typedef gint (*MSFilterNotifyFunc)(struct _MSFilter*, gint event, gpointer arg, gpointer userdata); + +struct _MSFilter +{ + struct _MSFilterClass *klass; + GMutex *lock; + guchar finputs; /* number of connected fifo inputs*/ + guchar foutputs; /* number of connected fifo outputs*/ + guchar qinputs; /* number of connected queue inputs*/ + guchar qoutputs; /* number of connected queue outputs*/ + gint min_fifo_size; /* set when linking*/ + gint r_mingran; /* read minimum granularity (for fifos). + It can be zero so that the filter can accept any size of reading data*/ + MSFifo **infifos; /*pointer to a table of pointer to input fifos*/ + MSFifo **outfifos; /*pointer to a table of pointer to output fifos*/ + MSQueue **inqueues; /*pointer to a table of pointer to input queues*/ + MSQueue **outqueues; /*pointer to a table of pointer to output queues*/ + MSFilterNotifyFunc notify_event; + gpointer userdata; +}; + +typedef struct _MSFilter MSFilter; + +typedef enum{ + MS_FILTER_PROPERTY_FREQ, /* value is int */ + MS_FILTER_PROPERTY_BITRATE, /*value is int */ + MS_FILTER_PROPERTY_CHANNELS,/*value is int */ + MS_FILTER_PROPERTY_FMTP /* value is string */ +}MSFilterProperty; + +#define MS_FILTER_PROPERTY_STRING_MAX_SIZE 256 + +typedef MSFilter * (*MSFilterNewFunc)(void); +typedef void (*MSFilterProcessFunc)(MSFilter *); +typedef void (*MSFilterDestroyFunc)(MSFilter *); +typedef int (*MSFilterPropertyFunc)(MSFilter *,int ,void*); +typedef void (*MSFilterSetupFunc)(MSFilter *, void *); /*2nd arg is the sync */ + +typedef struct _MSFilterClass +{ + struct _MSFilterInfo *info; /*pointer to a filter_info */ + gchar *name; + guchar max_finputs; /* maximum number of fifo inputs*/ + guchar max_foutputs; /* maximum number of fifo outputs*/ + guchar max_qinputs; /* maximum number of queue inputs*/ + guchar max_qoutputs; /* maximum number of queue outputs*/ + gint r_maxgran; /* read maximum granularity (for fifos)*/ + gint w_maxgran; /* write maximum granularity (for fifos)*/ + gint r_offset; /* size of kept samples behind read pointer (for fifos)*/ + gint w_offset; /* size of kept samples behind write pointer (for fifos)*/ + MSFilterPropertyFunc set_property; + MSFilterPropertyFunc get_property; + MSFilterSetupFunc setup; /* called when attaching to sync */ + void (*process)(MSFilter *filter); + MSFilterSetupFunc unsetup; /* called when detaching from sync */ + void (*destroy)(MSFilter *filter); + guint attributes; +#define FILTER_HAS_FIFOS (0x0001) +#define FILTER_HAS_QUEUES (0x0001<<1) +#define FILTER_IS_SOURCE (0x0001<<2) +#define FILTER_IS_SINK (0x0001<<3) +#define FILTER_CAN_SYNC (0x0001<<4) + guint ref_count; /*number of object using the class*/ +} MSFilterClass; + + + +#define MS_FILTER(obj) ((MSFilter*)obj) +#define MS_FILTER_CLASS(klass) ((MSFilterClass*)klass) +#define MS_FILTER_GET_CLASS(obj) ((MSFilterClass*)((MS_FILTER(obj)->klass))) + +void ms_filter_class_init(MSFilterClass *filterclass); +void ms_filter_init(MSFilter *filter); + +#define ms_filter_class_set_attr(filter,flag) ((filter)->attributes|=(flag)) +#define ms_filter_class_unset_attr(filter,flag) ((filter)->attributes&=~(flag)) + +#define ms_filter_class_set_name(__klass,__name) (__klass)->name=g_strdup((__name)) +#define ms_filter_class_set_info(_klass,_info) (_klass)->info=(_info) +/* public*/ + +#define ms_filter_process(filter) ((filter)->klass->process((filter))) + +#define ms_filter_lock(filter) g_mutex_lock((filter)->lock) +#define ms_filter_unlock(filter) g_mutex_unlock((filter)->lock) +/* low level connect functions */ +int ms_filter_link(MSFilter *m1, gint pin1, MSFilter *m2,gint pin2, gint linktype); +int ms_filter_unlink(MSFilter *m1, gint pin1, MSFilter *m2,gint pin2,gint linktype); + +/* high level connect functions */ +int ms_filter_add_link(MSFilter *m1, MSFilter *m2); +int ms_filter_remove_links(MSFilter *m1, MSFilter *m2); + +void ms_filter_set_notify_func(MSFilter* filter,MSFilterNotifyFunc func, gpointer userdata); +void ms_filter_notify_event(MSFilter *filter,gint event, gpointer arg); + +int ms_filter_set_property(MSFilter *f,MSFilterProperty property, void *value); +int ms_filter_get_property(MSFilter *f,MSFilterProperty property, void *value); + + +gint ms_filter_fifos_have_data(MSFilter *f); +gint ms_filter_queues_have_data(MSFilter *f); + +void ms_filter_uninit(MSFilter *obj); +void ms_filter_destroy(MSFilter *f); + +#define ms_filter_get_mingran(f) ((f)->r_mingran) +#define ms_filter_set_mingran(f,gran) ((f)->r_mingran=(gran)) + +#define LINK_DEFAULT 0 +#define LINK_FIFO 1 +#define LINK_QUEUE 2 + + +#define MSFILTER_VERSION(a,b,c) (((a)<<2)|((b)<<1)|(c)) + +enum _MSFilterType +{ + MS_FILTER_DISK_IO, + MS_FILTER_AUDIO_CODEC, + MS_FILTER_VIDEO_CODEC, + MS_FILTER_NET_IO, + MS_FILTER_VIDEO_IO, + MS_FILTER_AUDIO_IO, + MS_FILTER_OTHER +}; + +typedef enum _MSFilterType MSFilterType; + + +/* find the first codec in the left part of the stream */ +MSFilter * ms_filter_search_upstream_by_type(MSFilter *f,MSFilterType type); + +struct _MSFilterInfo +{ + gchar *name; + gint version; + MSFilterType type; + MSFilterNewFunc constructor; + char *description; /*some textual information*/ +}; + +typedef struct _MSFilterInfo MSFilterInfo; + +void ms_filter_register(MSFilterInfo *finfo); +void ms_filter_unregister(MSFilterInfo *finfo); +MSFilterInfo * ms_filter_get_by_name(const gchar *name); + +MSFilter * ms_filter_new_with_name(const gchar *name); + + + +extern GList *filter_list; +#define MS_FILTER_INFO(obj) ((MSFilterInfo*)obj) + +void swap_buffer(gchar *buffer, gint len); + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcdec.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcdec.c new file mode 100644 index 00000000..b2dfff98 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcdec.c @@ -0,0 +1,194 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include + +#ifdef HAVE_ILBC + + +#include "msilbcdec.h" +#include "msilbcenc.h" +#include "mscodec.h" +#include +#include + + + +extern MSFilter * ms_ilbc_encoder_new(void); + +MSCodecInfo ilbc_info={ + { + "iLBC codec", + 0, + MS_FILTER_AUDIO_CODEC, + ms_ilbc_encoder_new, + "A speech codec suitable for robust voice communication over IP" + }, + ms_ilbc_encoder_new, + ms_ilbc_decoder_new, + 0, /* not applicable, 2 modes */ + 0, /* not applicable, 2 modes */ + 15200, + 8000, + 97, + "iLBC", + 1, + 1, +}; + + +void ms_ilbc_codec_init() +{ + ms_filter_register(MS_FILTER_INFO(&ilbc_info)); +} + + + +static MSILBCDecoderClass *ms_ilbc_decoder_class=NULL; + +MSFilter * ms_ilbc_decoder_new(void) +{ + MSILBCDecoder *r; + + r=g_new(MSILBCDecoder,1); + ms_ilbc_decoder_init(r); + if (ms_ilbc_decoder_class==NULL) + { + ms_ilbc_decoder_class=g_new(MSILBCDecoderClass,1); + ms_ilbc_decoder_class_init(ms_ilbc_decoder_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_ilbc_decoder_class); + return(MS_FILTER(r)); +} + + +int ms_ilbc_decoder_set_property(MSILBCDecoder *obj, MSFilterProperty prop, char *value) +{ + switch(prop){ + case MS_FILTER_PROPERTY_FMTP: + if (value == NULL) return 0; + if (strstr(value,"ptime=20")!=NULL) obj->ms_per_frame=20; + else if (strstr(value,"ptime=30")!=NULL) obj->ms_per_frame=30; + else g_warning("Unrecognized fmtp parameter for ilbc encoder!"); + break; + } + return 0; +} +int ms_ilbc_decoder_get_property(MSILBCDecoder *obj, MSFilterProperty prop, char *value) +{ + switch(prop){ + case MS_FILTER_PROPERTY_FMTP: + if (obj->ms_per_frame==20) strncpy(value,"ptime=20",MS_FILTER_PROPERTY_STRING_MAX_SIZE); + if (obj->ms_per_frame==30) strncpy(value,"ptime=30",MS_FILTER_PROPERTY_STRING_MAX_SIZE); + break; + } + return 0; +} + +void ms_ilbc_decoder_setup(MSILBCDecoder *r) +{ + MSFilterClass *klass = NULL; + switch (r->ms_per_frame) { + case 20: + r->samples_per_frame = BLOCKL_20MS; + r->bytes_per_compressed_frame = NO_OF_BYTES_20MS; + break; + case 30: + r->samples_per_frame = BLOCKL_30MS; + r->bytes_per_compressed_frame = NO_OF_BYTES_30MS; + break; + default: + g_error("ms_ilbc_decoder_setup: Bad value for ptime (%i)",r->ms_per_frame); + } + g_message("Using ilbc decoder with %i ms frames mode.",r->ms_per_frame); + initDecode(&r->ilbc_dec, r->ms_per_frame /* ms frames */, /* user enhancer */ 0); +} + + +/* FOR INTERNAL USE*/ +void ms_ilbc_decoder_init(MSILBCDecoder *r) +{ + /* default bitrate */ + r->bitrate = 15200; + r->ms_per_frame = 30; + r->samples_per_frame = BLOCKL_20MS; + r->bytes_per_compressed_frame = NO_OF_BYTES_20MS; + + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->inqueues=r->q_inputs; + MS_FILTER(r)->outfifos=r->f_outputs; + memset(r->q_inputs,0,sizeof(MSFifo*)*MSILBCDECODER_MAX_INPUTS); + memset(r->f_outputs,0,sizeof(MSFifo*)*MSILBCDECODER_MAX_INPUTS); +} + +void ms_ilbc_decoder_class_init(MSILBCDecoderClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"ILBCDec"); + MS_FILTER_CLASS(klass)->max_qinputs=MSILBCDECODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->max_foutputs=MSILBCDECODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->w_maxgran= ILBC_MAX_SAMPLES_PER_FRAME*2; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_ilbc_decoder_destroy; + MS_FILTER_CLASS(klass)->set_property=(MSFilterPropertyFunc)ms_ilbc_decoder_set_property; + MS_FILTER_CLASS(klass)->get_property=(MSFilterPropertyFunc)ms_ilbc_decoder_get_property; + MS_FILTER_CLASS(klass)->setup=(MSFilterSetupFunc)ms_ilbc_decoder_setup; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_ilbc_decoder_process; + MS_FILTER_CLASS(klass)->info=(MSFilterInfo*)&ilbc_info; +} + +void ms_ilbc_decoder_process(MSILBCDecoder *r) +{ + MSFifo *fo; + MSQueue *qi; + int err1; + void *dst=NULL; + float speech[ILBC_MAX_SAMPLES_PER_FRAME]; + MSMessage *m; + + qi=r->q_inputs[0]; + fo=r->f_outputs[0]; + m=ms_queue_get(qi); + + ms_fifo_get_write_ptr(fo, r->samples_per_frame*2, &dst); + if (dst!=NULL){ + if (m->data!=NULL){ + if (m->sizebytes_per_compressed_frame) { + g_warning("Invalid ilbc frame ?"); + } + iLBC_decode(speech, m->data, &r->ilbc_dec, /* mode */1); + }else{ + iLBC_decode(speech,NULL, &r->ilbc_dec,0); + } + ilbc_write_16bit_samples((gint16*)dst, speech, r->samples_per_frame); + } + ms_message_destroy(m); +} + +void ms_ilbc_decoder_uninit(MSILBCDecoder *obj) +{ +} + +void ms_ilbc_decoder_destroy( MSILBCDecoder *obj) +{ + ms_ilbc_decoder_uninit(obj); + g_free(obj); +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcdec.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcdec.h new file mode 100644 index 00000000..c219aabe --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcdec.h @@ -0,0 +1,72 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSILBCDECODER_H +#define MSILBCDECODER_H + +#include +#include +#include + +/*this is the class that implements a ILBCdecoder filter*/ + +#define MSILBCDECODER_MAX_INPUTS 1 /* max output per filter*/ + + +typedef struct _MSILBCDecoder +{ + /* the MSILBCDecoder derivates from MSFilter, so the MSFilter object MUST be the first of the MSILBCDecoder object + in order to the object mechanism to work*/ + MSFilter filter; + MSQueue *q_inputs[MSILBCDECODER_MAX_INPUTS]; + MSFifo *f_outputs[MSILBCDECODER_MAX_INPUTS]; + iLBC_Dec_Inst_t ilbc_dec; + int bitrate; + int ms_per_frame; + int samples_per_frame; + int bytes_per_compressed_frame; +} MSILBCDecoder; + +typedef struct _MSILBCDecoderClass +{ + /* the MSILBCDecoder derivates from MSFilter, so the MSFilter class MUST be the first of the MSILBCDecoder class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSILBCDecoderClass; + +/* PUBLIC */ + +/* call this before if don't load the plugin dynamically */ +void ms_ilbc_codec_init(); + +#define MS_ILBCDECODER(filter) ((MSILBCDecoder*)(filter)) +#define MS_ILBCDECODER_CLASS(klass) ((MSILBCDecoderClass*)(klass)) +MSFilter * ms_ilbc_decoder_new(void); + +/* FOR INTERNAL USE*/ +void ms_ilbc_decoder_init(MSILBCDecoder *r); +void ms_ilbc_decoder_class_init(MSILBCDecoderClass *klass); +void ms_ilbc_decoder_destroy( MSILBCDecoder *obj); +void ms_ilbc_decoder_process(MSILBCDecoder *r); + +extern MSCodecInfo ilbc_info; + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcenc.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcenc.c new file mode 100644 index 00000000..76d8b648 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcenc.c @@ -0,0 +1,244 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include + +#ifdef HAVE_ILBC + +#include +#include +#include "msilbcenc.h" + + +extern MSCodecInfo ilbc_info; + +/* The return value of each of these calls is the same as that + returned by fread/fwrite, which should be the number of samples + successfully read/written, not the number of bytes. */ + +int +ilbc_read_16bit_samples(gint16 int16samples[], float speech[], int n) +{ + int i; + + /* Convert 16 bit integer samples to floating point values in the + range [-1,+1]. */ + + for (i = 0; i < n; i++) { + speech[i] = int16samples[i]; + } + + return (n); +} + + + +int +ilbc_write_16bit_samples(gint16 int16samples[], float speech[], int n) +{ + int i; + float real_sample; + + /* Convert floating point samples in range [-1,+1] to 16 bit + integers. */ + for (i = 0; i < n; i++) { + float dtmp=speech[i]; + if (dtmpMAX_SAMPLE) + dtmp=MAX_SAMPLE; + int16samples[i] = (short) dtmp; + } + return (n); +} + +/* + +Write the bits in bits[0] through bits[len-1] to file f, in "packed" +format. + +bits is expected to be an array of len integer values, where each +integer is 0 to represent a 0 bit, and any other value represents a 1 +bit. This bit string is written to the file f in the form of several +8 bit characters. If len is not a multiple of 8, then the last +character is padded with 0 bits -- the padding is in the least +significant bits of the last byte. The 8 bit characters are "filled" +in order from most significant bit to least significant. + +*/ + +void +ilbc_write_bits(unsigned char *data, unsigned char *bits, int nbytes) +{ + memcpy(data, bits, nbytes); +} + + + +/* + +Read bits from file f into bits[0] through bits[len-1], in "packed" +format. + +*/ + +int +ilbc_read_bits(unsigned char *data, unsigned char *bits, int nbytes) +{ + + memcpy(bits, data, nbytes); + + return (nbytes); +} + + + + +static MSILBCEncoderClass *ms_ilbc_encoder_class=NULL; + +MSFilter * ms_ilbc_encoder_new(void) +{ + MSILBCEncoder *r; + + r=g_new(MSILBCEncoder,1); + ms_ilbc_encoder_init(r); + if (ms_ilbc_encoder_class==NULL) + { + ms_ilbc_encoder_class=g_new(MSILBCEncoderClass,1); + ms_ilbc_encoder_class_init(ms_ilbc_encoder_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_ilbc_encoder_class); + return(MS_FILTER(r)); +} + + +int ms_ilbc_encoder_set_property(MSILBCEncoder *obj, MSFilterProperty prop, char *value) +{ + switch(prop){ + case MS_FILTER_PROPERTY_FMTP: + if (value == NULL) return 0; + if (strstr(value,"ptime=20")!=NULL) obj->ms_per_frame=20; + else if (strstr(value,"ptime=30")!=NULL) obj->ms_per_frame=30; + else g_warning("Unrecognized fmtp parameter for ilbc encoder!"); + break; + } + return 0; +} + + +int ms_ilbc_encoder_get_property(MSILBCEncoder *obj, MSFilterProperty prop, char *value) +{ + switch(prop){ + case MS_FILTER_PROPERTY_FMTP: + if (obj->ms_per_frame==20) strncpy(value,"ptime=20",MS_FILTER_PROPERTY_STRING_MAX_SIZE); + if (obj->ms_per_frame==30) strncpy(value,"ptime=30",MS_FILTER_PROPERTY_STRING_MAX_SIZE); + break; + } + return 0; +} + +void ms_ilbc_encoder_setup(MSILBCEncoder *r) +{ + MSFilterClass *klass = NULL; + switch (r->ms_per_frame) { + case 20: + r->samples_per_frame = BLOCKL_20MS; + r->bytes_per_compressed_frame = NO_OF_BYTES_20MS; + break; + case 30: + r->samples_per_frame = BLOCKL_30MS; + r->bytes_per_compressed_frame = NO_OF_BYTES_30MS; + break; + default: + g_error("Bad bitrate value (%i) for ilbc encoder!", r->ms_per_frame); + break; + } + MS_FILTER(r)->r_mingran= (r->samples_per_frame * 2); + g_message("Using ilbc encoder with %i ms frames mode.",r->ms_per_frame); + initEncode(&r->ilbc_enc, r->ms_per_frame /* ms frames */); +} + +/* FOR INTERNAL USE*/ +void ms_ilbc_encoder_init(MSILBCEncoder *r) +{ + /* default bitrate */ + r->bitrate = 15200; + r->ms_per_frame = 20; + r->samples_per_frame = BLOCKL_20MS; + r->bytes_per_compressed_frame = NO_OF_BYTES_20MS; + + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->infifos=r->f_inputs; + MS_FILTER(r)->outqueues=r->q_outputs; + MS_FILTER(r)->r_mingran= (r->samples_per_frame * 2); + memset(r->f_inputs,0,sizeof(MSFifo*)*MSILBCENCODER_MAX_INPUTS); + memset(r->q_outputs,0,sizeof(MSFifo*)*MSILBCENCODER_MAX_INPUTS); +} + +void ms_ilbc_encoder_class_init(MSILBCEncoderClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"ILBCEnc"); + MS_FILTER_CLASS(klass)->max_finputs=MSILBCENCODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->max_qoutputs=MSILBCENCODER_MAX_INPUTS; + MS_FILTER_CLASS(klass)->r_maxgran=ILBC_MAX_SAMPLES_PER_FRAME*2; + MS_FILTER_CLASS(klass)->set_property=(MSFilterPropertyFunc)ms_ilbc_encoder_set_property; + MS_FILTER_CLASS(klass)->get_property=(MSFilterPropertyFunc)ms_ilbc_encoder_get_property; + MS_FILTER_CLASS(klass)->setup=(MSFilterSetupFunc)ms_ilbc_encoder_setup; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_ilbc_encoder_destroy; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_ilbc_encoder_process; + MS_FILTER_CLASS(klass)->info=(MSFilterInfo*)&ilbc_info; +} + +void ms_ilbc_encoder_process(MSILBCEncoder *r) +{ + MSFifo *fi; + MSQueue *qo; + MSMessage *m; + void *src=NULL; + float speech[ILBC_MAX_SAMPLES_PER_FRAME]; + + /* process output fifos, but there is only one for this class of filter*/ + + qo=r->q_outputs[0]; + fi=r->f_inputs[0]; + ms_fifo_get_read_ptr(fi,r->samples_per_frame*2,&src); + if (src==NULL) { + g_warning( "src=%p\n", src); + return; + } + m=ms_message_new(r->bytes_per_compressed_frame); + + ilbc_read_16bit_samples((gint16*)src, speech, r->samples_per_frame); + iLBC_encode((unsigned char *)m->data, speech, &r->ilbc_enc); + ms_queue_put(qo,m); +} + +void ms_ilbc_encoder_uninit(MSILBCEncoder *obj) +{ +} + +void ms_ilbc_encoder_destroy( MSILBCEncoder *obj) +{ + ms_ilbc_encoder_uninit(obj); + g_free(obj); +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcenc.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcenc.h new file mode 100644 index 00000000..bd8f3bf5 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msilbcenc.h @@ -0,0 +1,84 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSILBCENCODER_H +#define MSILBCENCODER_H + +#include "mscodec.h" +#include + +#define ILBC_BITS_IN_COMPRESSED_FRAME 400 + +int +ilbc_read_16bit_samples(gint16 int16samples[], float speech[], int n); + +int +ilbc_write_16bit_samples(gint16 int16samples[], float speech[], int n); + +void +ilbc_write_bits(unsigned char *data, unsigned char *bits, int nbytes); + +int +ilbc_read_bits(unsigned char *data, unsigned char *bits, int nbytes); + + +/*this is the class that implements a ILBCencoder filter*/ + +#define MSILBCENCODER_MAX_INPUTS 1 /* max output per filter*/ + + +typedef struct _MSILBCEncoder +{ + /* the MSILBCEncoder derivates from MSFilter, so the MSFilter object MUST be the first of the MSILBCEncoder object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MSILBCENCODER_MAX_INPUTS]; + MSQueue *q_outputs[MSILBCENCODER_MAX_INPUTS]; + iLBC_Enc_Inst_t ilbc_enc; + int ilbc_encoded_bytes; + int bitrate; + int ms_per_frame; + int samples_per_frame; + int bytes_per_compressed_frame; +} MSILBCEncoder; + +typedef struct _MSILBCEncoderClass +{ + /* the MSILBCEncoder derivates from MSFilter, so the MSFilter class MUST be the first of the MSILBCEncoder class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSILBCEncoderClass; + +/* PUBLIC */ +#define MS_ILBCENCODER(filter) ((MSILBCEncoder*)(filter)) +#define MS_ILBCENCODER_CLASS(klass) ((MSILBCEncoderClass*)(klass)) +MSFilter * ms_ilbc_encoder_new(void); + +/* FOR INTERNAL USE*/ +void ms_ilbc_encoder_init(MSILBCEncoder *r); +void ms_ilbc_encoder_class_init(MSILBCEncoderClass *klass); +void ms_ilbc_encoder_destroy( MSILBCEncoder *obj); +void ms_ilbc_encoder_process(MSILBCEncoder *r); + +#define ILBC_MAX_BYTES_PER_COMPRESSED_FRAME NO_OF_BYTES_30MS +#define ILBC_MAX_SAMPLES_PER_FRAME BLOCKL_30MS + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msnosync.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msnosync.c new file mode 100644 index 00000000..af5141c0 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msnosync.c @@ -0,0 +1,82 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "msnosync.h" + +static MSNoSyncClass *ms_nosync_class=NULL; + +void ms_nosync_init(MSNoSync *sync) +{ + ms_sync_init(MS_SYNC(sync)); + MS_SYNC(sync)->attached_filters=sync->filters; + memset(sync->filters,0,MSNOSYNC_MAX_FILTERS*sizeof(MSFilter*)); + MS_SYNC(sync)->samples_per_tick=160; + sync->started=0; +} + +void ms_nosync_class_init(MSNoSyncClass *klass) +{ + ms_sync_class_init(MS_SYNC_CLASS(klass)); + MS_SYNC_CLASS(klass)->max_filters=MSNOSYNC_MAX_FILTERS; + MS_SYNC_CLASS(klass)->synchronize=(MSSyncSyncFunc)ms_nosync_synchronize; + MS_SYNC_CLASS(klass)->destroy=(MSSyncDestroyFunc)ms_nosync_destroy; + /* no need to overload these function*/ + MS_SYNC_CLASS(klass)->attach=ms_sync_attach_generic; + MS_SYNC_CLASS(klass)->detach=ms_sync_detach_generic; +} + +void ms_nosync_destroy(MSNoSync *nosync) +{ + g_free(nosync); +} + +/* the synchronization function that does nothing*/ +void ms_nosync_synchronize(MSNoSync *nosync) +{ + gint32 time; + if (nosync->started==0){ + gettimeofday(&nosync->start,NULL); + nosync->started=1; + } + gettimeofday(&nosync->current,NULL); + MS_SYNC(nosync)->ticks++; + /* update the time, we are supposed to work at 8000 Hz */ + time=((nosync->current.tv_sec-nosync->start.tv_sec)*1000) + + ((nosync->current.tv_usec-nosync->start.tv_usec)/1000); + MS_SYNC(nosync)->time=time; + return; +} + + +MSSync *ms_nosync_new() +{ + MSNoSync *nosync; + + nosync=g_malloc(sizeof(MSNoSync)); + ms_nosync_init(nosync); + if (ms_nosync_class==NULL) + { + ms_nosync_class=g_new(MSNoSyncClass,1); + ms_nosync_class_init(ms_nosync_class); + } + MS_SYNC(nosync)->klass=MS_SYNC_CLASS(ms_nosync_class); + return(MS_SYNC(nosync)); +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msnosync.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msnosync.h new file mode 100644 index 00000000..eef52d45 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msnosync.h @@ -0,0 +1,60 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "mssync.h" + +#include +#define MSNOSYNC_MAX_FILTERS 10 + +/* MSNoSync derivates from MSSync base class*/ + +typedef struct _MSNoSync +{ + /* the MSSync must be the first field of the object in order to the object mechanism to work*/ + MSSync sync; + MSFilter *filters[MSNOSYNC_MAX_FILTERS]; + int started; + struct timeval start,current; +} MSNoSync; + + +typedef struct _MSNoSyncClass +{ + /* the MSSyncClass must be the first field of the class in order to the class mechanism to work*/ + MSSyncClass parent_class; +} MSNoSyncClass; + + +/*private*/ + +void ms_nosync_init(MSNoSync *sync); +void ms_nosync_class_init(MSNoSyncClass *sync); + +void ms_nosync_destroy(MSNoSync *nosync); +void ms_nosync_synchronize(MSNoSync *nosync); + +/*public*/ + +/* casts a MSSync object into a MSNoSync */ +#define MS_NOSYNC(sync) ((MSNoSync*)(sync)) +/* casts a MSSync class into a MSNoSync class */ +#define MS_NOSYNC_CLASS(klass) ((MSNoSyncClass*)(klass)) + +MSSync *ms_nosync_new(); diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msossread.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msossread.c new file mode 100644 index 00000000..2486c736 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msossread.c @@ -0,0 +1,148 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "msossread.h" +#include "mssync.h" +#include +#include +#include +#include + +MSFilterInfo oss_read_info={ + "OSS read", + 0, + MS_FILTER_AUDIO_IO, + ms_oss_read_new, + NULL +}; + +static MSOssReadClass *msossreadclass=NULL; + +MSFilter * ms_oss_read_new() +{ + MSOssRead *w; + + if (msossreadclass==NULL) + { + msossreadclass=g_new(MSOssReadClass,1); + ms_oss_read_class_init( msossreadclass ); + } + + w=g_new(MSOssRead,1); + MS_FILTER(w)->klass=MS_FILTER_CLASS(msossreadclass); + ms_oss_read_init(w); + + return(MS_FILTER(w)); +} + +/* FOR INTERNAL USE*/ +void ms_oss_read_init(MSOssRead *w) +{ + ms_sound_read_init(MS_SOUND_READ(w)); + MS_FILTER(w)->outfifos=w->f_outputs; + MS_FILTER(w)->outfifos[0]=NULL; + w->devid=0; + w->sndcard=NULL; + w->freq=8000; +} + +gint ms_oss_read_set_property(MSOssRead *f,MSFilterProperty prop, void *value) +{ + switch(prop){ + case MS_FILTER_PROPERTY_FREQ: + f->freq=((gint*)value)[0]; + break; + } + return 0; +} +void ms_oss_read_class_init(MSOssReadClass *klass) +{ + ms_sound_read_class_init(MS_SOUND_READ_CLASS(klass)); + MS_FILTER_CLASS(klass)->max_foutputs=1; /* one fifo output only */ + MS_FILTER_CLASS(klass)->setup=(MSFilterSetupFunc)ms_oss_read_setup; + MS_FILTER_CLASS(klass)->unsetup=(MSFilterSetupFunc)ms_oss_read_stop; + MS_FILTER_CLASS(klass)->process= (MSFilterProcessFunc)ms_oss_read_process; + MS_FILTER_CLASS(klass)->set_property=(MSFilterPropertyFunc)ms_oss_read_set_property; + MS_FILTER_CLASS(klass)->destroy= (MSFilterDestroyFunc)ms_oss_read_destroy; + MS_FILTER_CLASS(klass)->w_maxgran=MS_OSS_READ_MAX_GRAN; + MS_FILTER_CLASS(klass)->info=&oss_read_info; + MS_SOUND_READ_CLASS(klass)->set_device=(gint (*)(MSSoundRead*,gint))ms_oss_read_set_device; + MS_SOUND_READ_CLASS(klass)->start=(void (*)(MSSoundRead*))ms_oss_read_start; + MS_SOUND_READ_CLASS(klass)->stop=(void (*)(MSSoundRead*))ms_oss_read_stop; + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"OssRead"); + /* //ms_filter_class_set_attr( MS_FILTER_CLASS(klass),FILTER_CAN_SYNC|FILTER_IS_SOURCE); */ +} + +void ms_oss_read_destroy( MSOssRead *obj) +{ + g_free(obj); +} + +void ms_oss_read_process(MSOssRead *f) +{ + MSFifo *fifo; + char *p; + fifo=f->f_outputs[0]; + + g_return_if_fail(f->sndcard!=NULL); + g_return_if_fail(f->gran>0); + + if (snd_card_can_read(f->sndcard)){ + int got; + ms_fifo_get_write_ptr(fifo,f->gran,(void**)&p); + g_return_if_fail(p!=NULL); + got=snd_card_read(f->sndcard,p,f->gran); + if (got>=0 && got!=f->gran) ms_fifo_update_write_ptr(fifo,got); + } +} + + +void ms_oss_read_start(MSOssRead *r) +{ + g_return_if_fail(r->devid!=-1); + r->sndcard=snd_card_manager_get_card(snd_card_manager,r->devid); + g_return_if_fail(r->sndcard!=NULL); + /* open the device for an audio telephony signal with minimum latency */ + snd_card_open_r(r->sndcard,16,0,r->freq); + r->gran=(512*r->freq)/8000; + +} + +void ms_oss_read_stop(MSOssRead *w) +{ + g_return_if_fail(w->devid!=-1); + g_return_if_fail(w->sndcard!=NULL); + snd_card_close_r(w->sndcard); + w->sndcard=NULL; +} + + +void ms_oss_read_setup(MSOssRead *f, MSSync *sync) +{ + f->sync=sync; + ms_oss_read_start(f); +} + + +gint ms_oss_read_set_device(MSOssRead *r,gint devid) +{ + r->devid=devid; + return 0; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msossread.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msossread.h new file mode 100644 index 00000000..89d5a40b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msossread.h @@ -0,0 +1,77 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#ifndef MSOSSREAD_H +#define MSOSSREAD_H + +#include "mssoundread.h" +#include "sndcard.h" +#include "mssync.h" + + +/*this is the class that implements oss writing sink filter*/ + +#define MS_OSS_READ_MAX_INPUTS 1 /* max output per filter*/ + +#define MS_OSS_READ_MAX_GRAN (512*2) /* the maximum granularity*/ + +struct _MSOssRead +{ + /* the MSOssRead derivates from MSSoundRead so the MSSoundRead object MUST be the first of the MSOssRead object + in order to the object mechanism to work*/ + MSSoundRead filter; + MSFifo *f_outputs[MS_OSS_READ_MAX_INPUTS]; + MSSync *sync; + SndCard *sndcard; + gint freq; + gint devid; /* the sound device id it depends on*/ + gint gran; + gint flags; +#define START_REQUESTED 1 +#define STOP_REQUESTED 2 +}; + +typedef struct _MSOssRead MSOssRead; + +struct _MSOssReadClass +{ + /* the MSOssRead derivates from MSSoundRead, so the MSSoundRead class MUST be the first of the MSOssRead class + in order to the class mechanism to work*/ + MSSoundReadClass parent_class; +}; + +typedef struct _MSOssReadClass MSOssReadClass; + +/* PUBLIC */ +#define MS_OSS_READ(filter) ((MSOssRead*)(filter)) +#define MS_OSS_READ_CLASS(klass) ((MSOssReadClass*)(klass)) +MSFilter * ms_oss_read_new(void); +gint ms_oss_read_set_device(MSOssRead *w,gint devid); +void ms_oss_read_start(MSOssRead *w); +void ms_oss_read_stop(MSOssRead *w); + +/* FOR INTERNAL USE*/ +void ms_oss_read_init(MSOssRead *r); +void ms_oss_read_class_init(MSOssReadClass *klass); +void ms_oss_read_destroy( MSOssRead *obj); +void ms_oss_read_process(MSOssRead *f); +void ms_oss_read_setup(MSOssRead *f, MSSync *sync); + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msosswrite.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msosswrite.c new file mode 100644 index 00000000..cc86cd6b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msosswrite.c @@ -0,0 +1,247 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "msosswrite.h" +#include "mssync.h" +#include +#include + +MSFilterInfo oss_write_info={ + "OSS write", + 0, + MS_FILTER_OTHER, + ms_oss_write_new, + NULL +}; + + +static MSOssWriteClass *msosswriteclass=NULL; + +MSFilter * ms_oss_write_new() +{ + MSOssWrite *w; + + if (msosswriteclass==NULL) + { + msosswriteclass=g_new(MSOssWriteClass,1); + ms_oss_write_class_init( msosswriteclass ); + } + w=g_new(MSOssWrite,1); + MS_FILTER(w)->klass=MS_FILTER_CLASS(msosswriteclass); + ms_oss_write_init(w); + return(MS_FILTER(w)); +} + +/* FOR INTERNAL USE*/ +void ms_oss_write_init(MSOssWrite *w) +{ + ms_sound_write_init(MS_SOUND_WRITE(w)); + MS_FILTER(w)->infifos=w->f_inputs; + MS_FILTER(w)->infifos[0]=NULL; + MS_FILTER(w)->r_mingran=512; /* very few cards can do that...*/ + w->devid=0; + w->sndcard=NULL; + w->freq=8000; + w->channels=1; + w->dtmf_time=-1; +} + +gint ms_oss_write_set_property(MSOssWrite *f,MSFilterProperty prop, void *value) +{ + switch(prop){ + case MS_FILTER_PROPERTY_FREQ: + f->freq=((gint*)value)[0]; + break; + case MS_FILTER_PROPERTY_CHANNELS: + f->channels=((gint*)value)[0]; + break; + } + return 0; +} + +void ms_oss_write_class_init(MSOssWriteClass *klass) +{ + ms_sound_write_class_init(MS_SOUND_WRITE_CLASS(klass)); + MS_FILTER_CLASS(klass)->max_finputs=1; /* one fifo input only */ + MS_FILTER_CLASS(klass)->r_maxgran=MS_OSS_WRITE_DEF_GRAN; + MS_FILTER_CLASS(klass)->process= (MSFilterProcessFunc)ms_oss_write_process; + MS_FILTER_CLASS(klass)->destroy= (MSFilterDestroyFunc)ms_oss_write_destroy; + MS_FILTER_CLASS(klass)->setup= (MSFilterSetupFunc)ms_oss_write_setup; + MS_FILTER_CLASS(klass)->unsetup= (MSFilterSetupFunc)ms_oss_write_stop; + MS_FILTER_CLASS(klass)->set_property=(MSFilterPropertyFunc)ms_oss_write_set_property; + MS_FILTER_CLASS(klass)->info=&oss_write_info; + MS_SOUND_WRITE_CLASS(klass)->set_device=(gint (*)(MSSoundWrite*,gint))ms_oss_write_set_device; + MS_SOUND_WRITE_CLASS(klass)->start=(void (*)(MSSoundWrite*))ms_oss_write_start; + MS_SOUND_WRITE_CLASS(klass)->stop=(void (*)(MSSoundWrite*))ms_oss_write_stop; + MS_SOUND_WRITE_CLASS(klass)->set_level=(void (*)(MSSoundWrite*, gint))ms_oss_write_set_level; + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"OssWrite"); +} + +void ms_oss_write_destroy( MSOssWrite *obj) +{ + + g_free(obj); +} + +void ms_oss_write_process(MSOssWrite *f) +{ + MSFifo *fifo; + void *p; + int i; + gint gran=ms_filter_get_mingran(MS_FILTER(f)); + + /* always consume something */ + fifo=f->f_inputs[0]; + ms_fifo_get_read_ptr(fifo,gran,&p); + if (p==NULL) { + g_warning("Not enough data: gran=%i.",gran); + return; + } + g_return_if_fail(f->sndcard!=NULL); + if (f->dtmf_time!=-1){ + gint16 *buf=(gint16*)p; + /* generate a DTMF*/ + for(i=0;idtmf_time*f->lowfreq)); + buf[i]+=(gint16)(10000.0*sin(2*M_PI*(double)f->dtmf_time*f->highfreq)); + f->dtmf_time++; + /* //printf("buf[%i]=%i\n",i,buf[i]); */ + } + if (f->dtmf_time>f->dtmf_duration) f->dtmf_time=-1; /*finished*/ + } + snd_card_write(f->sndcard,p,gran); +} + +void ms_oss_write_start(MSOssWrite *w) +{ + gint bsize; + g_return_if_fail(w->devid!=-1); + w->sndcard=snd_card_manager_get_card(snd_card_manager,w->devid); + g_return_if_fail(w->sndcard!=NULL); + /* open the device for an audio telephony signal with minimum latency */ + snd_card_open_w(w->sndcard,16,w->channels==2,w->freq); + w->bsize=snd_card_get_bsize(w->sndcard); + /* //MS_FILTER(w)->r_mingran=w->bsize; */ + /* //ms_sync_set_samples_per_tick(MS_FILTER(w)->sync,bsize); */ +} + +void ms_oss_write_stop(MSOssWrite *w) +{ + g_return_if_fail(w->devid!=-1); + g_return_if_fail(w->sndcard!=NULL); + snd_card_close_w(w->sndcard); + w->sndcard=NULL; +} + +void ms_oss_write_set_level(MSOssWrite *w,gint a) +{ + +} + +gint ms_oss_write_set_device(MSOssWrite *w, gint devid) +{ + w->devid=devid; + return 0; +} + +void ms_oss_write_setup(MSOssWrite *r) +{ + /* //g_message("starting MSOssWrite.."); */ + ms_oss_write_start(r); +} + + + +void ms_oss_write_play_dtmf(MSOssWrite *w, char dtmf){ + + w->dtmf_duration=0.1*w->freq; + switch(dtmf){ + case '0': + w->lowfreq=941; + w->highfreq=1336; + break; + case '1': + w->lowfreq=697; + w->highfreq=1209; + break; + case '2': + w->lowfreq=697; + w->highfreq=1336; + break; + case '3': + w->lowfreq=697; + w->highfreq=1477; + break; + case '4': + w->lowfreq=770; + w->highfreq=1209; + break; + case '5': + w->lowfreq=770; + w->highfreq=1336; + break; + case '6': + w->lowfreq=770; + w->highfreq=1477; + break; + case '7': + w->lowfreq=852; + w->highfreq=1209; + break; + case '8': + w->lowfreq=852; + w->highfreq=1336; + break; + case '9': + w->lowfreq=852; + w->highfreq=1477; + break; + case '*': + w->lowfreq=941; + w->highfreq=1209; + break; + case '#': + w->lowfreq=941; + w->highfreq=1477; + break; + case 'A': + w->lowfreq=697; + w->highfreq=1633; + break; + case 'B': + w->lowfreq=770; + w->highfreq=1633; + break; + case 'C': + w->lowfreq=852; + w->highfreq=1633; + break; + case 'D': + w->lowfreq=941; + w->highfreq=1633; + break; + default: + g_warning("Not a dtmf key."); + return; + } + w->lowfreq=w->lowfreq/w->freq; + w->highfreq=w->highfreq/w->freq; + w->dtmf_time=0; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msosswrite.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msosswrite.h new file mode 100644 index 00000000..d4775341 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msosswrite.h @@ -0,0 +1,78 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#ifndef MSOSSWRITE_H +#define MSOSSWRITE_H + +#include "mssoundwrite.h" +#include "sndcard.h" + +/*this is the class that implements oss writing sink filter*/ + +#define MS_OSS_WRITE_MAX_INPUTS 1 /* max output per filter*/ + +#define MS_OSS_WRITE_DEF_GRAN (512*2) /* the default granularity*/ + +struct _MSOssWrite +{ + /* the MSOssWrite derivates from MSSoundWrite, so the MSSoundWrite object MUST be the first of the MSOssWrite object + in order to the object mechanism to work*/ + MSSoundWrite filter; + MSFifo *f_inputs[MS_OSS_WRITE_MAX_INPUTS]; + gint devid; /* the sound device id it depends on*/ + SndCard *sndcard; + gint bsize; + gint freq; + gint channels; + gdouble lowfreq; + gdouble highfreq; + gint dtmf_time; + gint dtmf_duration; +}; + +typedef struct _MSOssWrite MSOssWrite; + +struct _MSOssWriteClass +{ + /* the MSOssWrite derivates from MSSoundWrite, so the MSSoundWrite class MUST be the first of the MSOssWrite class + in order to the class mechanism to work*/ + MSSoundWriteClass parent_class; +}; + +typedef struct _MSOssWriteClass MSOssWriteClass; + +/* PUBLIC */ +#define MS_OSS_WRITE(filter) ((MSOssWrite*)(filter)) +#define MS_OSS_WRITE_CLASS(klass) ((MSOssWriteClass*)(klass)) +MSFilter * ms_oss_write_new(void); +gint ms_oss_write_set_device(MSOssWrite *w,gint devid); +void ms_oss_write_start(MSOssWrite *w); +void ms_oss_write_stop(MSOssWrite *w); +void ms_oss_write_set_level(MSOssWrite *w, gint level); +void ms_oss_write_play_dtmf(MSOssWrite *w, char dtmf); + +/* FOR INTERNAL USE*/ +void ms_oss_write_init(MSOssWrite *r); +void ms_oss_write_setup(MSOssWrite *r); +void ms_oss_write_class_init(MSOssWriteClass *klass); +void ms_oss_write_destroy( MSOssWrite *obj); +void ms_oss_write_process(MSOssWrite *f); + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqdispatcher.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqdispatcher.c new file mode 100644 index 00000000..6bd073b9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqdispatcher.c @@ -0,0 +1,91 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a dispatcher of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "msqdispatcher.h" + +static MSQdispatcherClass *ms_qdispatcher_class=NULL; + +MSFilter * ms_qdispatcher_new(void) +{ + MSQdispatcher *obj; + obj=g_malloc(sizeof(MSQdispatcher)); + if (ms_qdispatcher_class==NULL){ + ms_qdispatcher_class=g_malloc0(sizeof(MSQdispatcherClass)); + ms_qdispatcher_class_init(ms_qdispatcher_class); + } + MS_FILTER(obj)->klass=MS_FILTER_CLASS(ms_qdispatcher_class); + ms_qdispatcher_init(obj); + return MS_FILTER(obj); +} + + +void ms_qdispatcher_init(MSQdispatcher *obj) +{ + ms_filter_init(MS_FILTER(obj)); + + MS_FILTER(obj)->inqueues=obj->q_inputs; + MS_FILTER(obj)->outqueues=obj->q_outputs; + memset(obj->q_inputs,0,sizeof(MSQueue*)*MS_QDISPATCHER_MAX_INPUTS); + memset(obj->q_outputs,0,sizeof(MSQueue*)*MS_QDISPATCHER_MAX_OUTPUTS); +} + + + +void ms_qdispatcher_class_init(MSQdispatcherClass *klass) +{ + MSFilterClass *parent_class=MS_FILTER_CLASS(klass); + ms_filter_class_init(parent_class); + ms_filter_class_set_name(parent_class,"qdispatcher"); + parent_class->max_qinputs=MS_QDISPATCHER_MAX_INPUTS; + parent_class->max_qoutputs=MS_QDISPATCHER_MAX_OUTPUTS; + + parent_class->destroy=(MSFilterDestroyFunc)ms_qdispatcher_destroy; + parent_class->process=(MSFilterProcessFunc)ms_qdispatcher_process; +} + + +void ms_qdispatcher_destroy( MSQdispatcher *obj) +{ + g_free(obj); +} + +void ms_qdispatcher_process(MSQdispatcher *obj) +{ + gint i; + MSQueue *inq=obj->q_inputs[0]; + + if (inq!=NULL){ + MSQueue *outq; + MSMessage *m1,*m2; + while ( (m1=ms_queue_get(inq))!=NULL){ + /* dispatch incoming messages to output queues */ + for (i=0;iq_outputs[i]; + if (outq!=NULL){ + m2=ms_message_dup(m1); + ms_queue_put(outq,m2); + } + } + ms_message_destroy(m1); + } + } + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqdispatcher.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqdispatcher.h new file mode 100644 index 00000000..3b0c566d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqdispatcher.h @@ -0,0 +1,60 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a dispatcher of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSQDISPATCHER_H +#define MSQDISPATCHER_H + +#include "msfilter.h" + + +/*this is the class that implements a qdispatcher filter*/ + +#define MS_QDISPATCHER_MAX_INPUTS 1 +#define MS_QDISPATCHER_MAX_OUTPUTS 5 + +typedef struct _MSQdispatcher +{ + /* the MSQdispatcher derivates from MSFilter, so the MSFilter object MUST be the first of the MSQdispatcher object + in order to the object mechanism to work*/ + MSFilter filter; + MSQueue *q_inputs[MS_QDISPATCHER_MAX_INPUTS]; + MSQueue *q_outputs[MS_QDISPATCHER_MAX_OUTPUTS]; +} MSQdispatcher; + +typedef struct _MSQdispatcherClass +{ + /* the MSQdispatcher derivates from MSFilter, so the MSFilter class MUST be the first of the MSQdispatcher class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSQdispatcherClass; + +/* PUBLIC */ +#define MS_QDISPATCHER(filter) ((MSQdispatcher*)(filter)) +#define MS_QDISPATCHER_CLASS(klass) ((MSQdispatcherClass*)(klass)) +MSFilter * ms_qdispatcher_new(void); + +/* FOR INTERNAL USE*/ +void ms_qdispatcher_init(MSQdispatcher *r); +void ms_qdispatcher_class_init(MSQdispatcherClass *klass); +void ms_qdispatcher_destroy( MSQdispatcher *obj); +void ms_qdispatcher_process(MSQdispatcher *r); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqueue.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqueue.c new file mode 100644 index 00000000..46368956 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqueue.c @@ -0,0 +1,56 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "msqueue.h" +#include + +MSQueue * ms_queue_new() +{ + MSQueue *q=g_malloc(sizeof(MSQueue)); + memset(q,0,sizeof(MSQueue)); + return q; +} + +MSMessage *ms_queue_get(MSQueue *q) +{ + MSMessage *b=q->last; + if (b==NULL) return NULL; + q->last=b->prev; + if (b->prev==NULL) q->first=NULL; /* it was the only element of the queue*/ + q->size--; + b->next=b->prev=NULL; + return(b); +} + +void ms_queue_put(MSQueue *q, MSMessage *m) +{ + MSMessage *mtmp=q->first; + g_return_if_fail(m!=NULL); + q->first=m; + m->next=mtmp; + if (mtmp!=NULL) + { + mtmp->prev=m; + } + else q->last=m; /* it was the first element of the q */ + q->size++; +} + + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqueue.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqueue.h new file mode 100644 index 00000000..73ab8d8d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msqueue.h @@ -0,0 +1,49 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef MSQUEUE_H +#define MSQUEUE_H + +#include "msbuffer.h" + +/* for the moment these are stupid queues limited to one element*/ + +typedef struct _MSQueue +{ + MSMessage *first; + MSMessage *last; + gint size; + void *prev_data; /*user data, usually the writting filter*/ + void *next_data; /* user data, usually the reading filter*/ +}MSQueue; + + +MSQueue * ms_queue_new(); + +MSMessage *ms_queue_get(MSQueue *q); + +void ms_queue_put(MSQueue *q, MSMessage *m); + +#define ms_queue_can_get(q) ( (q)->size!=0 ) + +#define ms_queue_destroy(q) g_free(q) + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msread.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msread.c new file mode 100644 index 00000000..6f0ec99d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msread.c @@ -0,0 +1,182 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "msread.h" +#include "mssync.h" +#include +#include +#include +#include +#include + +static MSReadClass *ms_read_class=NULL; + +MSFilter * ms_read_new(char *name) +{ + MSRead *r; + int fd=-1; + + r=g_new(MSRead,1); + ms_read_init(r); + if (ms_read_class==NULL) + { + ms_read_class=g_new(MSReadClass,1); + ms_read_class_init(ms_read_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_read_class); + r->fd=-1; + if (name!=NULL) ms_read_open(r,name); + return(MS_FILTER(r)); +} + + + +gint ms_read_open(MSRead *r, gchar *name) +{ + gint fd; + fd=open(name,O_RDONLY); + if (fd<0) { + r->fd=-1; + g_warning("ms_read_new: cannot open %s : %s",name,strerror(errno)); + return -1; + } + r->fd=fd; + if (strstr(name,".wav")!=NULL){ + /* skip the header */ + lseek(fd,20,SEEK_SET); +#ifdef WORDS_BIGENDIAN + r->need_swap=1; +#else + r->need_swap=0; +#endif + } + r->state=MS_READ_STATE_STARTED; + return 0; +} + +/* FOR INTERNAL USE*/ +void ms_read_init(MSRead *r) +{ + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->outfifos=r->foutputs; + MS_FILTER(r)->outqueues=r->qoutputs; + memset(r->foutputs,0,sizeof(MSFifo*)*MSREAD_MAX_OUTPUTS); + memset(r->qoutputs,0,sizeof(MSQueue*)*MSREAD_MAX_OUTPUTS); + r->fd=-1; + r->gran=320; + r->state=MS_READ_STATE_STOPPED; + r->need_swap=0; + r->rate=8000; +} + +gint ms_read_set_property(MSRead *f,MSFilterProperty prop, void *value) +{ + switch(prop){ + case MS_FILTER_PROPERTY_FREQ: + f->rate=((gint*)value)[0]; + break; + } + return 0; +} + +void ms_read_class_init(MSReadClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"dskreader"); + ms_filter_class_set_attr(MS_FILTER_CLASS(klass),FILTER_IS_SOURCE); + MS_FILTER_CLASS(klass)->max_foutputs=MSREAD_MAX_OUTPUTS; + MS_FILTER_CLASS(klass)->max_qoutputs=MSREAD_MAX_OUTPUTS; + MS_FILTER_CLASS(klass)->w_maxgran=MSREAD_DEF_GRAN; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_read_destroy; + MS_FILTER_CLASS(klass)->setup=(MSFilterSetupFunc)ms_read_setup; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_read_process; + MS_FILTER_CLASS(klass)->set_property=(MSFilterPropertyFunc)ms_read_set_property; +} + +void ms_read_process(MSRead *r) +{ + MSFifo *f; + MSQueue *q; + MSMessage *msg=NULL; + int err; + gint gran=r->gran; + void *p; + + f=r->foutputs[0]; + if ((f!=NULL) && (r->state==MS_READ_STATE_STARTED)) + { + ms_fifo_get_write_ptr(f,gran,&p); + if (p!=NULL) + { + err=read(r->fd,p,gran); + if (err<0) + { + /* temp: */ + g_warning("ms_read_process: failed to read: %s.\n",strerror(errno)); + } + else if (errstate=MS_READ_STATE_STOPPED; + close(r->fd); + r->fd=-1; + } + if (r->need_swap) swap_buffer(p,gran); + } + } + /* process output queues*/ + q=r->qoutputs[0]; + if ((q!=NULL) && (r->fd>0)) + { + msg=ms_message_new(r->gran); + err=read(r->fd,msg->data,r->gran); + if (err>0){ + msg->size=err; + ms_queue_put(q,msg); + if (r->need_swap) swap_buffer(msg->data,r->gran); + }else{ + ms_filter_notify_event(MS_FILTER(r),MS_READ_EVENT_EOF,NULL); + ms_trace("End of file reached."); + r->state=MS_READ_STATE_STOPPED; + } + } +} + +void ms_read_destroy( MSRead *obj) +{ + if (obj->fd!=0) close(obj->fd); + g_free(obj); +} + +gint ms_read_close(MSRead *obj) +{ + if (obj->fd!=0) { + close(obj->fd); + obj->fd=-1; + obj->state=MS_READ_STATE_STOPPED; + } +} + + +void ms_read_setup(MSRead *r, MSSync *sync) +{ + r->sync=sync; + r->gran=(r->rate*sync->interval/1000)*2; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msread.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msread.h new file mode 100644 index 00000000..93177f38 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msread.h @@ -0,0 +1,80 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef MSREAD_H +#define MSREAD_H + +#include "msfilter.h" +#include "mssync.h" + +/*this is the class that implements file reading source filter*/ + +#define MSREAD_MAX_OUTPUTS 1 /* max output per filter*/ + +#define MSREAD_DEF_GRAN 640 /* the default granularity*/ + +typedef enum{ + MS_READ_STATE_STARTED, + MS_READ_STATE_STOPPED, + MS_READ_STATE_EOF +}MSReadState; + +typedef struct _MSRead +{ + /* the MSRead derivates from MSFilter, so the MSFilter object MUST be the first of the MSRead object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *foutputs[MSREAD_MAX_OUTPUTS]; + MSQueue *qoutputs[MSREAD_MAX_OUTPUTS]; + MSSync *sync; + gint rate; + gint fd; /* the file descriptor of the file being read*/ + gint gran; /*granularity*/ /* for use with queues */ + gint need_swap; + gint state; +} MSRead; + +typedef struct _MSReadClass +{ + /* the MSRead derivates from MSFilter, so the MSFilter class MUST be the first of the MSRead class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSReadClass; + +/* PUBLIC */ +#define MS_READ(filter) ((MSRead*)(filter)) +#define MS_READ_CLASS(klass) ((MSReadClass*)(klass)) +MSFilter * ms_read_new(char *name); +/* set the granularity for reading file on disk */ +#define ms_read_set_bufsize(filter,sz) (filter)->gran=(sz) + +/* FOR INTERNAL USE*/ +void ms_read_init(MSRead *r); +void ms_read_class_init(MSReadClass *klass); +void ms_read_destroy( MSRead *obj); +void ms_read_process(MSRead *r); +void ms_read_setup(MSRead *r, MSSync *sync); + +typedef enum{ + MS_READ_EVENT_EOF /* end of file */ +} MSReadEvent; + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msringplayer.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msringplayer.c new file mode 100644 index 00000000..fb2006e8 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msringplayer.c @@ -0,0 +1,246 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "msringplayer.h" +#include "mssync.h" +#include +#include +#include +#include +#include + +#include "waveheader.h" + +#define WAVE_HEADER_OFFSET sizeof(wave_header_t) + +enum { PLAY_RING, PLAY_SILENCE}; + +static int supported_freq[6]={8000,11025,16000,22050,32000,44100}; + +gint freq_is_supported(gint freq){ + int i; + for (i=0;i<6;i++){ + if (abs(supported_freq[i]-freq)<50) return supported_freq[i]; + } + return 0; +} + +static MSRingPlayerClass *ms_ring_player_class=NULL; + +/** + * ms_ring_player_new: + * @name: The path to the 16-bit 8khz raw file to be played as a ring. + * @seconds: The number of seconds that separates two rings. + * + * Allocates a new MSRingPlayer object. + * + * + * Returns: a pointer the the object, NULL if name could not be open. + */ +MSFilter * ms_ring_player_new(char *name, gint seconds) +{ + MSRingPlayer *r; + int fd=-1; + + if ((name!=NULL) && (strlen(name)!=0)) + { + fd=open(name,O_RDONLY); + if (fd<0) + { + g_warning("ms_ring_player_new: failed to open %s.\n",name); + return NULL; + } + + }else { + g_warning("ms_ring_player_new: Bad file name"); + return NULL; + } + + r=g_new(MSRingPlayer,1); + ms_ring_player_init(r); + if (ms_ring_player_class==NULL) + { + ms_ring_player_class=g_new(MSRingPlayerClass,1); + ms_ring_player_class_init(ms_ring_player_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_ring_player_class); + + r->fd=fd; + r->silence=seconds; + r->freq=8000; + if (strstr(name,".wav")!=NULL){ + wave_header_t header; + int freq,freq2; + /* read the header */ + read(fd,&header,sizeof(wave_header_t)); + freq=wave_header_get_rate(&header); + if ((freq2=freq_is_supported(freq))>0){ + r->freq=freq2; + }else { + g_warning("Unsupported sampling rate %i",freq); + r->freq=8000; + } + r->channel=wave_header_get_channel(&header); + lseek(fd,WAVE_HEADER_OFFSET,SEEK_SET); +#ifdef WORDS_BIGENDIAN + r->need_swap=1; +#else + r->need_swap=0; +#endif + } + ms_ring_player_set_property(r, MS_FILTER_PROPERTY_FREQ,&r->freq); + r->state=PLAY_RING; + return(MS_FILTER(r)); +} + + +/* FOR INTERNAL USE*/ +void ms_ring_player_init(MSRingPlayer *r) +{ + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->outfifos=r->foutputs; + MS_FILTER(r)->outqueues=r->qoutputs; + memset(r->foutputs,0,sizeof(MSFifo*)*MS_RING_PLAYER_MAX_OUTPUTS); + memset(r->qoutputs,0,sizeof(MSQueue*)*MS_RING_PLAYER_MAX_OUTPUTS); + r->fd=-1; + r->current_pos=0; + r->need_swap=0; + r->sync=NULL; +} + +gint ms_ring_player_set_property(MSRingPlayer *f,MSFilterProperty prop, void *value) +{ + switch(prop){ + case MS_FILTER_PROPERTY_FREQ: + f->rate=((gint*)value)[0]*2; + f->silence_bytes=f->silence*f->rate; + if (f->sync!=NULL) + f->gran=(f->rate*f->sync->interval/1000)*2; + break; + } + return 0; +} + +gint ms_ring_player_get_property(MSRingPlayer *f,MSFilterProperty prop, void *value) +{ + switch(prop){ + case MS_FILTER_PROPERTY_FREQ: + ((gint*)value)[0]=f->freq; + + break; + case MS_FILTER_PROPERTY_CHANNELS: + ((gint*)value)[0]=f->channel; + break; + } + return 0; +} + +gint ms_ring_player_get_sample_freq(MSRingPlayer *obj){ + return obj->freq; +} + + +void ms_ring_player_class_init(MSRingPlayerClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"ringplay"); + ms_filter_class_set_attr(MS_FILTER_CLASS(klass),FILTER_IS_SOURCE); + MS_FILTER_CLASS(klass)->max_foutputs=MS_RING_PLAYER_MAX_OUTPUTS; + MS_FILTER_CLASS(klass)->max_qoutputs=MS_RING_PLAYER_MAX_OUTPUTS; + MS_FILTER_CLASS(klass)->w_maxgran=MS_RING_PLAYER_DEF_GRAN; + MS_FILTER_CLASS(klass)->setup=(MSFilterSetupFunc)ms_ring_player_setup; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_ring_player_destroy; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_ring_player_process; + MS_FILTER_CLASS(klass)->set_property=(MSFilterPropertyFunc)ms_ring_player_set_property; + MS_FILTER_CLASS(klass)->get_property=(MSFilterPropertyFunc)ms_ring_player_get_property; +} + +void ms_ring_player_process(MSRingPlayer *r) +{ + MSFifo *f; + gint err; + gint processed=0; + gint gran=r->gran; + char *p; + + g_return_if_fail(gran>0); + /* process output fifos*/ + + f=r->foutputs[0]; + ms_fifo_get_write_ptr(f,gran,(void**)&p); + g_return_if_fail(p!=NULL); + for (processed=0;processedstate){ + case PLAY_RING: + err=read(r->fd,&p[processed],gran-processed); + if (err<0) + { + memset(&p[processed],0,gran-processed); + processed=gran; + g_warning("ms_ring_player_process: failed to read: %s.\n",strerror(errno)); + return; + } + else if (errcurrent_pos=r->silence_bytes; + lseek(r->fd,WAVE_HEADER_OFFSET,SEEK_SET); + r->state=PLAY_SILENCE; + ms_filter_notify_event(MS_FILTER(r),MS_RING_PLAYER_END_OF_RING_EVENT,NULL); + } + if (r->need_swap) swap_buffer(&p[processed],err); + processed+=err; + break; + case PLAY_SILENCE: + err=gran-processed; + if (r->current_pos>err){ + memset(&p[processed],0,err); + r->current_pos-=gran; + processed=gran; + }else{ + memset(&p[processed],0,r->current_pos); + processed+=r->current_pos; + r->state=PLAY_RING; + } + break; + } + } +} + +/** + * ms_ring_player_destroy: + * @obj: A valid MSRingPlayer object. + * + * Destroy a MSRingPlayer object. + * + * + */ + +void ms_ring_player_destroy( MSRingPlayer *obj) +{ + if (obj->fd!=0) close(obj->fd); + g_free(obj); +} + +void ms_ring_player_setup(MSRingPlayer *r,MSSync *sync) +{ + r->sync=sync; + r->gran=(r->rate*r->sync->interval/1000)*r->channel; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msringplayer.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msringplayer.h new file mode 100644 index 00000000..1f5e67da --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msringplayer.h @@ -0,0 +1,81 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef MSRINGPLAYER_H +#define MSRINGPLAYER_H + +#include "msfilter.h" +#include "mssync.h" + + +/*this is the class that implements file reading source filter*/ + +#define MS_RING_PLAYER_MAX_OUTPUTS 1 /* max output per filter*/ + +#define MS_RING_PLAYER_DEF_GRAN 8192 /* the default granularity*/ + +#define MS_RING_PLAYER_END_OF_RING_EVENT 1 + +struct _MSRingPlayer +{ + /* the MSRingPlayer derivates from MSFilter, so the MSFilter object MUST be the first of the MSRingPlayer object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *foutputs[MS_RING_PLAYER_MAX_OUTPUTS]; + MSQueue *qoutputs[MS_RING_PLAYER_MAX_OUTPUTS];\ + MSSync *sync; + gint gran; + gint freq; + gint rate; + gint channel; /* number of interleaved channels */ + gint silence; /* silence time between each ring, in seconds */ + gint state; + gint fd; /* the file descriptor of the file being read*/ + gint silence_bytes; /*silence in number of bytes between each ring */ + gint current_pos; + gint need_swap; +}; + +typedef struct _MSRingPlayer MSRingPlayer; + +struct _MSRingPlayerClass +{ + /* the MSRingPlayer derivates from MSFilter, so the MSFilter class MUST be the first of the MSRingPlayer class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +}; + +typedef struct _MSRingPlayerClass MSRingPlayerClass; + +/* PUBLIC */ +#define MS_RING_PLAYER(filter) ((MSRingPlayer*)(filter)) +#define MS_RING_PLAYER_CLASS(klass) ((MSRingPlayerClass*)(klass)) +MSFilter * ms_ring_player_new(char *name, gint seconds); +gint ms_ring_player_get_sample_freq(MSRingPlayer *obj); + + +/* FOR INTERNAL USE*/ +void ms_ring_player_init(MSRingPlayer *r); +void ms_ring_player_class_init(MSRingPlayerClass *klass); +void ms_ring_player_destroy( MSRingPlayer *obj); +void ms_ring_player_process(MSRingPlayer *r); +#define ms_ring_player_set_bufsize(filter,sz) (filter)->gran=(sz) +void ms_ring_player_setup(MSRingPlayer *r,MSSync *sync); +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtprecv.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtprecv.c new file mode 100644 index 00000000..9b82e939 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtprecv.c @@ -0,0 +1,163 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "msrtprecv.h" + + +/* some utilities to convert mblk_t to MSMessage and vice-versa */ +MSMessage *msgb_2_ms_message(mblk_t* mp){ + MSMessage *msg; + MSBuffer *msbuf; + if (mp->b_datap->ref_count!=1) return NULL; /* cannot handle properly non-unique buffers*/ + /* create a MSBuffer using the mblk_t buffer */ + msg=ms_message_alloc(); + msbuf=ms_buffer_alloc(0); + msbuf->buffer=mp->b_datap->db_base; + msbuf->size=(char*)mp->b_datap->db_lim-(char*)mp->b_datap->db_base; + ms_message_set_buf(msg,msbuf); + msg->size=mp->b_wptr-mp->b_rptr; + msg->data=mp->b_rptr; + /* free the mblk_t */ + g_free(mp->b_datap); + g_free(mp); + return msg; +} + + +static MSRtpRecvClass *ms_rtp_recv_class=NULL; + +MSFilter * ms_rtp_recv_new(void) +{ + MSRtpRecv *r; + + r=g_new(MSRtpRecv,1); + ms_rtp_recv_init(r); + if (ms_rtp_recv_class==NULL) + { + ms_rtp_recv_class=g_new0(MSRtpRecvClass,1); + ms_rtp_recv_class_init(ms_rtp_recv_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_rtp_recv_class); + return(MS_FILTER(r)); +} + + +/* FOR INTERNAL USE*/ +void ms_rtp_recv_init(MSRtpRecv *r) +{ + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->outfifos=r->f_outputs; + MS_FILTER(r)->outqueues=r->q_outputs; + memset(r->f_outputs,0,sizeof(MSFifo*)*MSRTPRECV_MAX_OUTPUTS); + memset(r->q_outputs,0,sizeof(MSFifo*)*MSRTPRECV_MAX_OUTPUTS); + r->rtpsession=NULL; + r->stream_started=0; +} + +void ms_rtp_recv_class_init(MSRtpRecvClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"RTPRecv"); + MS_FILTER_CLASS(klass)->max_qoutputs=MSRTPRECV_MAX_OUTPUTS; + MS_FILTER_CLASS(klass)->max_foutputs=MSRTPRECV_MAX_OUTPUTS; + MS_FILTER_CLASS(klass)->w_maxgran=MSRTPRECV_DEF_GRAN; + ms_filter_class_set_attr(MS_FILTER_CLASS(klass),FILTER_IS_SOURCE); + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_rtp_recv_destroy; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_rtp_recv_process; + MS_FILTER_CLASS(klass)->setup=(MSFilterSetupFunc)ms_rtp_recv_setup; +} + +void ms_rtp_recv_process(MSRtpRecv *r) +{ + MSFifo *fo; + MSQueue *qo; + MSSync *sync= r->sync; + void *d; + mblk_t *mp; + gint len; + gint gran=ms_sync_get_samples_per_tick(MS_SYNC(sync)); + + if (r->rtpsession==NULL) return; + /* process output fifo and output queue*/ + fo=r->f_outputs[0]; + if (fo!=NULL) + { + while( (mp=rtp_session_recvm_with_ts(r->rtpsession,r->prev_ts))!=NULL) { + /* try to get rtp packets and paste them to the output fifo */ + r->stream_started=1; + len=mp->b_cont->b_wptr-mp->b_cont->b_rptr; + ms_fifo_get_write_ptr(fo,len,&d); + if (d!=NULL){ + memcpy(d,mp->b_cont->b_rptr,len); + }else ms_warning("ms_rtp_recv_process: no space on output fifo !"); + freemsg(mp); + } + r->prev_ts+=gran; + + } + qo=r->q_outputs[0]; + if (qo!=NULL) + { + guint32 clock; + gint got=0; + /* we are connected with queues (surely for video)*/ + /* use the sync system time to compute a timestamp */ + PayloadType *pt=rtp_profile_get_payload(r->rtpsession->profile,r->rtpsession->payload_type); + if (pt==NULL) { + ms_warning("ms_rtp_recv_process(): NULL RtpPayload- skipping."); + return; + } + clock=(guint32)(((double)sync->time*(double)pt->clock_rate)/1000.0); + /*g_message("Querying packet with timestamp %u",clock);*/ + /* get rtp packet, and send them through the output queue */ + while ( (mp=rtp_session_recvm_with_ts(r->rtpsession,clock))!=NULL ){ + MSMessage *msg; + mblk_t *mdata; + /*g_message("Got packet with timestamp %u",clock);*/ + got++; + r->stream_started=1; + mdata=mp->b_cont; + freeb(mp); + msg=msgb_2_ms_message(mdata); + ms_queue_put(qo,msg); + } + } +} + +void ms_rtp_recv_destroy( MSRtpRecv *obj) +{ + g_free(obj); +} + +RtpSession * ms_rtp_recv_set_session(MSRtpRecv *obj,RtpSession *session) +{ + RtpSession *old=obj->rtpsession; + obj->rtpsession=session; + obj->prev_ts=0; + return old; +} + + +void ms_rtp_recv_setup(MSRtpRecv *r,MSSync *sync) +{ + r->sync=sync; + r->stream_started=0; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtprecv.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtprecv.h new file mode 100644 index 00000000..8c2c2ed7 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtprecv.h @@ -0,0 +1,80 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSRTPRECV_H +#define MSRTPRECV_H + +#include "msfilter.h" +#include "mssync.h" + +/* because of a conflict between config.h from oRTP and config.h from linphone:*/ +#undef PACKAGE +#undef VERSION +#include + +/*this is the class that implements a copy filter*/ + +#define MSRTPRECV_MAX_OUTPUTS 1 /* max output per filter*/ + +#define MSRTPRECV_DEF_GRAN 4096 /* the default granularity*/ + +struct _MSRtpRecv +{ + /* the MSCopy derivates from MSFilter, so the MSFilter object MUST be the first of the MSCopy object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_outputs[MSRTPRECV_MAX_OUTPUTS]; + MSQueue *q_outputs[MSRTPRECV_MAX_OUTPUTS]; + MSSync *sync; + RtpSession *rtpsession; + guint32 prev_ts; + gint stream_started; +}; + +typedef struct _MSRtpRecv MSRtpRecv; + +struct _MSRtpRecvClass +{ + /* the MSCopy derivates from MSFilter, so the MSFilter class MUST be the first of the MSCopy class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +}; + +typedef struct _MSRtpRecvClass MSRtpRecvClass; + +/* PUBLIC */ +#define MS_RTP_RECV(filter) ((MSRtpRecv*)(filter)) +#define MS_RTP_RECV_CLASS(klass) ((MSRtpRecvClass*)(klass)) +MSFilter * ms_rtp_recv_new(void); +RtpSession * ms_rtp_recv_set_session(MSRtpRecv *obj,RtpSession *session); +#define ms_rtp_recv_unset_session(obj) (ms_rtp_recv_set_session((obj),NULL)) +#define ms_rtp_recv_get_session(obj) ((obj)->rtpsession) + + + +/* FOR INTERNAL USE*/ +void ms_rtp_recv_init(MSRtpRecv *r); +void ms_rtp_recv_class_init(MSRtpRecvClass *klass); +void ms_rtp_recv_destroy( MSRtpRecv *obj); +void ms_rtp_recv_process(MSRtpRecv *r); +void ms_rtp_recv_setup(MSRtpRecv *r,MSSync *sync); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtpsend.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtpsend.c new file mode 100644 index 00000000..cfcb6b34 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtpsend.c @@ -0,0 +1,211 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "msrtpsend.h" +#include +#include "mssync.h" +#include "mscodec.h" + + + +static MSRtpSendClass *ms_rtp_send_class=NULL; + +MSFilter * ms_rtp_send_new(void) +{ + MSRtpSend *r; + + r=g_new(MSRtpSend,1); + + if (ms_rtp_send_class==NULL) + { + ms_rtp_send_class=g_new(MSRtpSendClass,1); + ms_rtp_send_class_init(ms_rtp_send_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_rtp_send_class); + ms_rtp_send_init(r); + return(MS_FILTER(r)); +} + + +void ms_rtp_send_init(MSRtpSend *r) +{ + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->infifos=r->f_inputs; + MS_FILTER(r)->inqueues=r->q_inputs; + MS_FILTER(r)->r_mingran=MSRTPSEND_DEF_GRAN; + memset(r->f_inputs,0,sizeof(MSFifo*)*MSRTPSEND_MAX_INPUTS); + memset(r->q_inputs,0,sizeof(MSFifo*)*MSRTPSEND_MAX_INPUTS); + r->rtpsession=NULL; + r->ts=0; + r->ts_inc=0; + r->flags=0; + r->delay=0; +} + +void ms_rtp_send_class_init(MSRtpSendClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"RTPSend"); + MS_FILTER_CLASS(klass)->max_qinputs=MSRTPSEND_MAX_INPUTS; + MS_FILTER_CLASS(klass)->max_finputs=MSRTPSEND_MAX_INPUTS; + MS_FILTER_CLASS(klass)->r_maxgran=MSRTPSEND_DEF_GRAN; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_rtp_send_destroy; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_rtp_send_process; + MS_FILTER_CLASS(klass)->setup=(MSFilterSetupFunc)ms_rtp_send_setup; +} + +void ms_rtp_send_set_timing(MSRtpSend *r, guint32 ts_inc, gint payload_size) +{ + r->ts_inc=ts_inc; + r->packet_size=payload_size; + if (r->ts_inc!=0) r->flags|=RTPSEND_CONFIGURED; + else r->flags&=~RTPSEND_CONFIGURED; + MS_FILTER(r)->r_mingran=payload_size; + /*g_message("ms_rtp_send_set_timing: ts_inc=%i",ts_inc);*/ +} + +guint32 get_new_timestamp(MSRtpSend *r,guint32 synctime) +{ + guint32 clockts; + /* use the sync system time to compute a timestamp */ + PayloadType *pt=rtp_profile_get_payload(r->rtpsession->profile,r->rtpsession->payload_type); + g_return_val_if_fail(pt!=NULL,0); + clockts=(guint32)(((double)synctime * (double)pt->clock_rate)/1000.0); + ms_trace("ms_rtp_send_process: sync->time=%i clock=%i",synctime,clockts); + if (r->flags & RTPSEND_CONFIGURED){ + if (RTP_TIMESTAMP_IS_STRICTLY_NEWER_THAN(clockts,r->ts+(2*r->ts_inc) )){ + r->ts=clockts; + } + else r->ts+=r->ts_inc; + }else{ + r->ts=clockts; + } + return r->ts; +} + + +void ms_rtp_send_process(MSRtpSend *r) +{ + MSFifo *fi; + MSQueue *qi; + MSSync *sync= r->sync; + int gran=ms_sync_get_samples_per_tick(sync); + guint32 ts; + void *s; + guint skip; + guint32 synctime=sync->time; + + g_return_if_fail(gran>0); + if (r->rtpsession==NULL) return; + + ms_filter_lock(MS_FILTER(r)); + skip=r->delay!=0; + if (skip) r->delay--; + /* process output fifo and output queue*/ + fi=r->f_inputs[0]; + if (fi!=NULL) + { + ts=get_new_timestamp(r,synctime); + /* try to read r->packet_size bytes and send them in a rtp packet*/ + ms_fifo_get_read_ptr(fi,r->packet_size,&s); + if (!skip){ + rtp_session_send_with_ts(r->rtpsession,s,r->packet_size,ts); + ms_trace("len=%i, ts=%i ",r->packet_size,ts); + } + } + qi=r->q_inputs[0]; + if (qi!=NULL) + { + MSMessage *msg; + /* read a MSMessage and send it through the network*/ + while ( (msg=ms_queue_get(qi))!=NULL){ + ts=get_new_timestamp(r,synctime); + if (!skip) { + /*g_message("Sending packet with ts=%u",ts);*/ + rtp_session_send_with_ts(r->rtpsession,msg->data,msg->size,ts); + + } + ms_message_destroy(msg); + } + } + ms_filter_unlock(MS_FILTER(r)); +} + +void ms_rtp_send_destroy( MSRtpSend *obj) +{ + g_free(obj); +} + +RtpSession * ms_rtp_send_set_session(MSRtpSend *obj,RtpSession *session) +{ + RtpSession *old=obj->rtpsession; + obj->rtpsession=session; + obj->ts=0; + obj->ts_inc=0; + return old; +} + +void ms_rtp_send_setup(MSRtpSend *r, MSSync *sync) +{ + MSFilter *codec; + MSCodecInfo *info; + r->sync=sync; + codec=ms_filter_search_upstream_by_type(MS_FILTER(r),MS_FILTER_AUDIO_CODEC); + if (codec==NULL) codec=ms_filter_search_upstream_by_type(MS_FILTER(r),MS_FILTER_VIDEO_CODEC); + if (codec==NULL){ + g_warning("ms_rtp_send_setup: could not find upstream codec."); + return; + } + info=MS_CODEC_INFO(codec->klass->info); + if (info->info.type==MS_FILTER_AUDIO_CODEC){ + int ts_inc=info->fr_size/2; + int psize=info->dt_size; + if (ts_inc==0){ + /* dont'use the normal frame size: this is a variable frame size codec */ + /* use the MS_FILTER(codec)->r_mingran */ + ts_inc=MS_FILTER(codec)->r_mingran/2; + psize=0; + } + ms_rtp_send_set_timing(r,ts_inc,psize); + } +} + +gint ms_rtp_send_dtmf(MSRtpSend *r, gchar dtmf) +{ + gint res; + + if (r->rtpsession==NULL) return -1; + if (rtp_session_telephone_events_supported(r->rtpsession)==-1){ + g_warning("ERROR : telephone events not supported.\n"); + return -1; + } + + ms_filter_lock(MS_FILTER(r)); + g_message("Sending DTMF."); + res=rtp_session_send_dtmf(r->rtpsession, dtmf, r->ts); + if (res==0){ + /* //r->ts+=r->ts_inc; */ + r->delay+=2; + }else g_warning("Could not send dtmf."); + + ms_filter_unlock(MS_FILTER(r)); + + return res; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtpsend.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtpsend.h new file mode 100644 index 00000000..b70f4e55 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msrtpsend.h @@ -0,0 +1,85 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSRTPSEND_H +#define MSRTPSEND_H + +#include "msfilter.h" +#include "mssync.h" + +#undef PACKAGE +#undef VERSION +#include + + +/*this is the class that implements a sending through rtp filter*/ + +#define MSRTPSEND_MAX_INPUTS 1 /* max input per filter*/ + +#define MSRTPSEND_DEF_GRAN 4096/* the default granularity*/ + +struct _MSRtpSend +{ + /* the MSCopy derivates from MSFilter, so the MSFilter object MUST be the first of the MSCopy object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MSRTPSEND_MAX_INPUTS]; + MSQueue *q_inputs[MSRTPSEND_MAX_INPUTS]; + MSSync *sync; + RtpSession *rtpsession; + guint32 ts; + guint32 ts_inc; /* the timestamp increment */ + gint packet_size; + guint flags; + guint delay; /* number of _proccess call which must be skipped */ +#define RTPSEND_CONFIGURED (1) +}; + +typedef struct _MSRtpSend MSRtpSend; + +struct _MSRtpSendClass +{ + /* the MSRtpSend derivates from MSFilter, so the MSFilter class MUST be the first of the MSCopy class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +}; + +typedef struct _MSRtpSendClass MSRtpSendClass; + +/* PUBLIC */ +#define MS_RTP_SEND(filter) ((MSRtpSend*)(filter)) +#define MS_RTP_SEND_CLASS(klass) ((MSRtpSendClass*)(klass)) +MSFilter * ms_rtp_send_new(void); +RtpSession * ms_rtp_send_set_session(MSRtpSend *obj,RtpSession *session); +#define ms_rtp_send_unset_session(obj) (ms_rtp_send_set_session((obj),NULL)) +#define ms_rtp_send_get_session(obj) ((obj)->rtpsession) +void ms_rtp_send_set_timing(MSRtpSend *r, guint32 ts_inc, gint payload_size); +gint ms_rtp_send_dtmf(MSRtpSend *r, gchar dtmf); + + +/* FOR INTERNAL USE*/ +void ms_rtp_send_init(MSRtpSend *r); +void ms_rtp_send_class_init(MSRtpSendClass *klass); +void ms_rtp_send_destroy( MSRtpSend *obj); +void ms_rtp_send_process(MSRtpSend *r); +void ms_rtp_send_setup(MSRtpSend *r, MSSync *sync); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssdlout.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssdlout.h new file mode 100644 index 00000000..fd6ec547 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssdlout.h @@ -0,0 +1,64 @@ +/*************************************************************************** + * mssdlout.h + * + * Mon Jul 11 16:18:55 2005 + * Copyright 2005 Simon Morlat + * Email simon dot morlat at linphone dot org + ****************************************************************************/ + +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef mssdlout_h +#define mssdlout_h + +#include "msfilter.h" + +#include +#include + +struct _MSSdlOut +{ + MSFilter parent; + MSQueue *input[2]; + gint width,height; + const gchar *format; + SDL_Surface *screen; + SDL_Overlay *overlay; + MSMessage *oldinm1; + gboolean use_yuv; +}; + + +typedef struct _MSSdlOut MSSdlOut; + +struct _MSSdlOutClass +{ + MSFilterClass parent_class; +}; + +typedef struct _MSSdlOutClass MSSdlOutClass; + +MSFilter * ms_sdl_out_new(void); +void ms_sdl_out_set_format(MSSdlOut *obj, const char *fmt); + +#define MS_SDL_OUT(obj) ((MSSdlOut*)obj) + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundread.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundread.c new file mode 100644 index 00000000..3803b018 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundread.c @@ -0,0 +1,39 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation + + */ + +#include "mssoundread.h" + + +void ms_sound_read_init(MSSoundRead *w) +{ + ms_filter_init(MS_FILTER(w)); + +} + +void ms_sound_read_class_init(MSSoundReadClass *klass) +{ + int i; + ms_filter_class_init(MS_FILTER_CLASS(klass)); + MS_FILTER_CLASS(klass)->max_foutputs=1; /* one fifo output only */ + + ms_filter_class_set_attr( MS_FILTER_CLASS(klass),FILTER_IS_SOURCE); +} + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundread.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundread.h new file mode 100644 index 00000000..7f2cab93 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundread.h @@ -0,0 +1,80 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#ifndef MSSOUNDREAD_H +#define MSSOUNDREAD_H + +#include "msfilter.h" +#include "mssync.h" + + + +struct _MSSoundRead +{ + /* the MSOssRead derivates from MSFilter, so the MSFilter object MUST be the first of the MSOssRead object + in order to the object mechanism to work*/ + MSFilter filter; +}; + +typedef struct _MSSoundRead MSSoundRead; + +struct _MSSoundReadClass +{ + /* the MSOssRead derivates from MSFilter, so the MSFilter class MUST be the first of the MSOssRead class + in order to the class mechanism to work*/ + MSFilterClass parent_class; + gint (*set_device)(MSSoundRead *, gint devid); + void (*start)(MSSoundRead *); + void (*stop)(MSSoundRead*); + void (*set_level)(MSSoundRead *, gint a); +}; + +typedef struct _MSSoundReadClass MSSoundReadClass; + +/* PUBLIC */ +#define MS_SOUND_READ(filter) ((MSSoundRead*)(filter)) +#define MS_SOUND_READ_CLASS(klass) ((MSSoundReadClass*)(klass)) + +static inline int ms_sound_read_set_device(MSSoundRead *r,gint devid) +{ + return MS_SOUND_READ_CLASS( MS_FILTER(r)->klass )->set_device(r,devid); +} + +static inline void ms_sound_read_start(MSSoundRead *r) +{ + MS_SOUND_READ_CLASS( MS_FILTER(r)->klass )->start(r); +} + +static inline void ms_sound_read_stop(MSSoundRead *w) +{ + MS_SOUND_READ_CLASS( MS_FILTER(w)->klass )->stop(w); +} + +static inline void ms_sound_read_set_level(MSSoundRead *w,gint a) +{ + MS_SOUND_READ_CLASS( MS_FILTER(w)->klass )->set_level(w,a); +} + +/* FOR INTERNAL USE*/ +void ms_sound_read_init(MSSoundRead *r); +void ms_sound_read_class_init(MSSoundReadClass *klass); + + +#endif + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundwrite.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundwrite.c new file mode 100644 index 00000000..9c5879f4 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundwrite.c @@ -0,0 +1,39 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation + + */ + +#include "mssoundwrite.h" + + +void ms_sound_write_init(MSSoundWrite *w) +{ + ms_filter_init(MS_FILTER(w)); + +} + +void ms_sound_write_class_init(MSSoundWriteClass *klass) +{ + int i; + ms_filter_class_init(MS_FILTER_CLASS(klass)); + MS_FILTER_CLASS(klass)->max_finputs=1; /* one fifo output only */ + + ms_filter_class_set_attr( MS_FILTER_CLASS(klass),FILTER_IS_SINK); +} + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundwrite.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundwrite.h new file mode 100644 index 00000000..e6d79874 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssoundwrite.h @@ -0,0 +1,80 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#ifndef MSSOUNDWRITE_H +#define MSSOUNDWRITE_H + +#include "msfilter.h" +#include "mssync.h" + + + +struct _MSSoundWrite +{ + /* the MSOssWrite derivates from MSFilter, so the MSFilter object MUST be the first of the MSOssWrite object + in order to the object mechanism to work*/ + MSFilter filter; +}; + +typedef struct _MSSoundWrite MSSoundWrite; + +struct _MSSoundWriteClass +{ + /* the MSOssWrite derivates from MSFilter, so the MSFilter class MUST be the first of the MSOssWrite class + in order to the class mechanism to work*/ + MSFilterClass parent_class; + gint (*set_device)(MSSoundWrite *, gint devid); + void (*start)(MSSoundWrite *); + void (*stop)(MSSoundWrite*); + void (*set_level)(MSSoundWrite *, gint a); +}; + +typedef struct _MSSoundWriteClass MSSoundWriteClass; + +/* PUBLIC */ +#define MS_SOUND_WRITE(filter) ((MSSoundWrite*)(filter)) +#define MS_SOUND_WRITE_CLASS(klass) ((MSSoundWriteClass*)(klass)) + +static inline int ms_sound_write_set_device(MSSoundWrite *r,gint devid) +{ + return MS_SOUND_WRITE_CLASS( MS_FILTER(r)->klass )->set_device(r,devid); +} + +static inline void ms_sound_write_start(MSSoundWrite *r) +{ + MS_SOUND_WRITE_CLASS( MS_FILTER(r)->klass )->start(r); +} + +static inline void ms_sound_write_stop(MSSoundWrite *w) +{ + MS_SOUND_WRITE_CLASS( MS_FILTER(w)->klass )->stop(w); +} + +static inline void ms_sound_write_set_level(MSSoundWrite *w,gint a) +{ + MS_SOUND_WRITE_CLASS( MS_FILTER(w)->klass )->set_level(w,a); +} + +/* FOR INTERNAL USE*/ +void ms_sound_write_init(MSSoundWrite *r); +void ms_sound_write_class_init(MSSoundWriteClass *klass); + + +#endif + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexdec.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexdec.c new file mode 100644 index 00000000..b91ca360 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexdec.c @@ -0,0 +1,218 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include + +#ifdef HAVE_SPEEX + +#include "msspeexdec.h" + +#ifdef HAVE_GLIB +#include +#endif + +extern MSFilter * ms_speex_enc_new(); + +MSCodecInfo speex_info= +{ + { + "Speex codec", + 0, + MS_FILTER_AUDIO_CODEC, + ms_speex_dec_new, + "A high quality variable bit-rate codec from Jean Marc Valin and David Rowe." + }, + ms_speex_enc_new, + ms_speex_dec_new, + 0, /*frame size */ + 0, + 8000, /*minimal bitrate */ + -1, /* sampling frequency */ + 110, /* payload type */ + "speex", + 1, + 1 +}; + + + +void ms_speex_codec_init() +{ + + ms_filter_register(MS_FILTER_INFO(&speex_info)); + /* //ms_filter_register(MS_FILTER_INFO(&speex_lbr_info)); */ +} + +#ifdef HAVE_GLIB +gchar * g_module_check_init(GModule *module) +{ + ms_speex_codec_init(); + + return NULL; +} +#else +gchar * g_module_check_init() +{ + ms_speex_codec_init(); + + return NULL; +} +#endif + +static MSSpeexDecClass * ms_speex_dec_class=NULL; +/* //static MSSpeexDecClass * ms_speexnb_dec_class=NULL; */ + +MSFilter * ms_speex_dec_new() +{ + MSSpeexDec *obj=g_new(MSSpeexDec,1); + + if (ms_speex_dec_class==NULL){ + ms_speex_dec_class=g_new(MSSpeexDecClass,1); + ms_speex_dec_class_init(ms_speex_dec_class); + } + MS_FILTER(obj)->klass=MS_FILTER_CLASS(ms_speex_dec_class); + + ms_speex_dec_init(obj); + return MS_FILTER(obj); +} + +void ms_speex_dec_init(MSSpeexDec *obj) +{ + ms_filter_init(MS_FILTER(obj)); + obj->initialized=0; + MS_FILTER(obj)->outfifos=obj->outf; + MS_FILTER(obj)->inqueues=obj->inq; + obj->outf[0]=NULL; + obj->inq[0]=NULL; + obj->frequency=8000; /*default value */ + +} + +void ms_speex_dec_init_core(MSSpeexDec *obj,const SpeexMode *mode) +{ + int pf=1; + + obj->speex_state=speex_decoder_init(mode); + speex_bits_init(&obj->bits); + /* enable the perceptual post filter */ + speex_decoder_ctl(obj->speex_state,SPEEX_SET_PF, &pf); + + speex_mode_query(mode, SPEEX_MODE_FRAME_SIZE, &obj->frame_size); + + obj->initialized=1; +} + +int ms_speex_dec_set_property(MSSpeexDec *obj, MSFilterProperty prop, int *value) +{ + if (obj->initialized){ + /* we are called when speex is running !! forbid that! */ + ms_warning("ms_speex_dec_set_property: cannot call this function when running!"); + return -1; + } + switch(prop){ + case MS_FILTER_PROPERTY_FREQ: + obj->frequency=value[0]; + break; + } + return 0; +} + +void ms_speex_dec_setup(MSSpeexDec *obj) +{ + const SpeexMode *mode; + g_message("Speex decoder setup: freq=%i",obj->frequency); + if ( obj->frequency< 16000) mode=&speex_nb_mode; + else mode=&speex_wb_mode; + ms_speex_dec_init_core(obj,mode); +} + +void ms_speex_dec_unsetup(MSSpeexDec *obj) +{ + ms_speex_dec_uninit_core(obj); +} + +void ms_speex_dec_class_init(MSSpeexDecClass *klass) +{ + gint frame_size=0; + + ms_filter_class_init(MS_FILTER_CLASS(klass)); + /* use the largest frame size to configure fifos */ + speex_mode_query(&speex_wb_mode, SPEEX_MODE_FRAME_SIZE, &frame_size); + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_speex_dec_process; + MS_FILTER_CLASS(klass)->setup=(MSFilterSetupFunc)ms_speex_dec_setup; + MS_FILTER_CLASS(klass)->unsetup=(MSFilterSetupFunc)ms_speex_dec_unsetup; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_speex_dec_destroy; + MS_FILTER_CLASS(klass)->set_property=(MSFilterPropertyFunc)ms_speex_dec_set_property; + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"SpeexDecoder"); + MS_FILTER_CLASS(klass)->info=(MSFilterInfo*)&speex_info; + MS_FILTER_CLASS(klass)->max_foutputs=1; + MS_FILTER_CLASS(klass)->max_qinputs=1; + MS_FILTER_CLASS(klass)->w_maxgran=frame_size*2; + ms_trace("ms_speex_dec_class_init: w_maxgran is %i.",MS_FILTER_CLASS(klass)->w_maxgran); +} + +void ms_speex_dec_uninit_core(MSSpeexDec *obj) +{ + speex_decoder_destroy(obj->speex_state); + obj->initialized=0; +} + +void ms_speex_dec_uninit(MSSpeexDec *obj) +{ + +} + +void ms_speex_dec_destroy(MSSpeexDec *obj) +{ + ms_speex_dec_uninit(obj); + g_free(obj); +} + +void ms_speex_dec_process(MSSpeexDec *obj) +{ + MSFifo *outf=obj->outf[0]; + MSQueue *inq=obj->inq[0]; + gint16 *output; + gint gran=obj->frame_size*2; + gint i; + MSMessage *m; + + g_return_if_fail(inq!=NULL); + g_return_if_fail(outf!=NULL); + + m=ms_queue_get(inq); + g_return_if_fail(m!=NULL); + speex_bits_reset(&obj->bits); + ms_fifo_get_write_ptr(outf,gran,(void**)&output); + g_return_if_fail(output!=NULL); + if (m->data!=NULL){ + + speex_bits_read_from(&obj->bits,m->data,m->size); + /* decode */ + speex_decode_int(obj->speex_state,&obj->bits,(short*)output); + }else{ + /* we have a missing packet */ + speex_decode_int(obj->speex_state,NULL,(short*)output); + } + ms_message_destroy(m); + +} + +#endif /* HAVE_SPEEX */ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexdec.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexdec.h new file mode 100644 index 00000000..d4e745fe --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexdec.h @@ -0,0 +1,69 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSSPEEXDEC_H +#define MSSPEEXDEC_H + +#include +#include + +struct _MSSpeexDec +{ + MSFilter parent; + MSQueue *inq[1]; /* speex has an input q because it can be variable bit rate */ + MSFifo *outf[1]; + void *speex_state; + SpeexBits bits; + int frequency; + int frame_size; + int initialized; +}; + +typedef struct _MSSpeexDec MSSpeexDec; + + +struct _MSSpeexDecClass +{ + MSFilterClass parent; +}; + +typedef struct _MSSpeexDecClass MSSpeexDecClass; + + +#define MS_SPEEX_DEC(o) ((MSSpeexDec*)(o)) +#define MS_SPEEX_DEC_CLASS(o) ((MSSpeexDecClass*)(o)) + +/* call this before if don't load the plugin dynamically */ +void ms_speex_codec_init(); + +/* mediastreamer compliant constructor */ +MSFilter * ms_speex_dec_new(); + +void ms_speex_dec_init(MSSpeexDec *obj); +void ms_speex_dec_init_core(MSSpeexDec *obj,const SpeexMode *mode); +void ms_speex_dec_class_init(MSSpeexDecClass *klass); +void ms_speex_dec_uninit(MSSpeexDec *obj); +void ms_speex_dec_uninit_core(MSSpeexDec *obj); + +void ms_speex_dec_process(MSSpeexDec *obj); +void ms_speex_dec_destroy(MSSpeexDec *obj); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexenc.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexenc.c new file mode 100644 index 00000000..abf976e6 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexenc.c @@ -0,0 +1,192 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include + +#ifdef HAVE_SPEEX + +#include "msspeexenc.h" +#include "ms.h" +extern MSCodecInfo speex_info; + +static MSSpeexEncClass * ms_speex_enc_class=NULL; + +MSFilter * ms_speex_enc_new() +{ + MSSpeexEnc *obj=g_new(MSSpeexEnc,1); + + if (ms_speex_enc_class==NULL){ + ms_speex_enc_class=g_new(MSSpeexEncClass,1); + ms_speex_enc_class_init(ms_speex_enc_class); + } + MS_FILTER(obj)->klass=MS_FILTER_CLASS(ms_speex_enc_class); + ms_speex_enc_init(MS_SPEEX_ENC(obj)); + return MS_FILTER(obj); +} + +void ms_speex_enc_init(MSSpeexEnc *obj) +{ + ms_filter_init(MS_FILTER(obj)); + MS_FILTER(obj)->infifos=obj->inf; + MS_FILTER(obj)->outqueues=obj->outq; + obj->inf[0]=NULL; + obj->outq[0]=NULL; + obj->frequency=8000; + obj->bitrate=30000; + obj->initialized=0; +} + +void ms_speex_enc_init_core(MSSpeexEnc *obj,const SpeexMode *mode, gint bitrate) +{ + int proc_type, proc_speed; + gchar *proc_vendor; + int tmp; + int frame_size; + + obj->speex_state=speex_encoder_init(mode); + speex_bits_init(&obj->bits); + + if (bitrate>0) { + bitrate++; + speex_encoder_ctl(obj->speex_state, SPEEX_SET_BITRATE, &bitrate); + g_message("Setting speex output bitrate less or equal than %i",bitrate-1); + } + + proc_speed=ms_proc_get_speed(); + proc_vendor=ms_proc_get_param("vendor_id"); + if (proc_speed<0 || proc_vendor==NULL){ + g_warning("Can't guess processor features: setting speex encoder to its lowest complexity."); + tmp=1; + speex_encoder_ctl(obj->speex_state,SPEEX_SET_COMPLEXITY,&tmp); + }else if ((proc_speed!=-1) && (proc_speed<200)){ + g_warning("A cpu speed less than 200 Mhz is not enough: let's reduce the complexity of the speex codec."); + tmp=1; + speex_encoder_ctl(obj->speex_state,SPEEX_SET_COMPLEXITY,&tmp); + }else if (proc_vendor!=NULL) { + if (strncmp(proc_vendor,"GenuineIntel",strlen("GenuineIntel"))==0){ + proc_type=ms_proc_get_type(); + if (proc_type==5){ + g_warning("A pentium I is not enough fast for speex codec in normal mode: let's reduce its complexity."); + tmp=1; + speex_encoder_ctl(obj->speex_state,SPEEX_SET_COMPLEXITY,&tmp); + } + } + g_free(proc_vendor); + } + /* guess the used input frame size */ + speex_mode_query(mode, SPEEX_MODE_FRAME_SIZE, &frame_size); + MS_FILTER(obj)->r_mingran=frame_size*2; + ms_trace("ms_speex_init: using frame size of %i.",MS_FILTER(obj)->r_mingran); + + obj->initialized=1; +} + +/* must be called before the encoder is running*/ +int ms_speex_enc_set_property(MSSpeexEnc *obj,int property,int *value) +{ + if (obj->initialized){ + /* we are called when speex is running !! forbid that! */ + ms_warning("ms_speex_enc_set_property: cannot call this function when running!"); + return -1; + } + switch(property){ + case MS_FILTER_PROPERTY_FREQ: + obj->frequency=value[0]; + break; + case MS_FILTER_PROPERTY_BITRATE: /* to specify max bitrate */ + obj->bitrate=value[0]; + break; + } + return 0; +} + +void ms_speex_enc_setup(MSSpeexEnc *obj) +{ + const SpeexMode *mode; + int quality; + g_message("Speex encoder setup: freq=%i",obj->frequency); + if ( obj->frequency< 16000) mode=&speex_nb_mode; + else mode=&speex_wb_mode; + ms_speex_enc_init_core(obj,mode,obj->bitrate); + +} + +void ms_speex_enc_unsetup(MSSpeexEnc *obj) +{ + ms_speex_enc_uninit_core(obj); +} + +void ms_speex_enc_class_init(MSSpeexEncClass *klass) +{ + gint frame_size=0; + + ms_filter_class_init(MS_FILTER_CLASS(klass)); + /* we take the larger (wb) frame size */ + speex_mode_query(&speex_wb_mode, SPEEX_MODE_FRAME_SIZE, &frame_size); + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_speex_enc_process; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_speex_enc_destroy; + MS_FILTER_CLASS(klass)->setup=(MSFilterSetupFunc)ms_speex_enc_setup; + MS_FILTER_CLASS(klass)->unsetup=(MSFilterSetupFunc)ms_speex_enc_unsetup; + MS_FILTER_CLASS(klass)->set_property=(MSFilterPropertyFunc)ms_speex_enc_set_property; + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"SpeexEncoder"); + MS_FILTER_CLASS(klass)->info=(MSFilterInfo*)&speex_info; + MS_FILTER_CLASS(klass)->max_finputs=1; + MS_FILTER_CLASS(klass)->max_qoutputs=1; + MS_FILTER_CLASS(klass)->r_maxgran=frame_size*2; + ms_trace("ms_speex_enc_class_init: r_maxgran is %i.",MS_FILTER_CLASS(klass)->r_maxgran); +} + +void ms_speex_enc_uninit_core(MSSpeexEnc *obj) +{ + if (obj->initialized){ + speex_encoder_destroy(obj->speex_state); + obj->initialized=0; + } +} + +void ms_speex_enc_destroy(MSSpeexEnc *obj) +{ + ms_speex_enc_uninit_core(obj); + g_free(obj); +} + +void ms_speex_enc_process(MSSpeexEnc *obj) +{ + MSFifo *inf=obj->inf[0]; + MSQueue *outq=obj->outq[0]; + gint16 *input; + gint gran=MS_FILTER(obj)->r_mingran; + gint i; + MSMessage *m; + + g_return_if_fail(inf!=NULL); + g_return_if_fail(outq!=NULL); + + ms_fifo_get_read_ptr(inf,gran,(void**)&input); + g_return_if_fail(input!=NULL); + /* encode */ + speex_bits_reset(&obj->bits); + speex_encode_int(obj->speex_state,(short*)input,&obj->bits); + m=ms_message_new(speex_bits_nbytes(&obj->bits)); + m->size=speex_bits_write(&obj->bits,m->data,m->size); + ms_queue_put(outq,m); +} + +#endif /* HAVE_SPEEX */ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexenc.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexenc.h new file mode 100644 index 00000000..41655b9f --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msspeexenc.h @@ -0,0 +1,66 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSSPEEXENC_H +#define MSSPEEXENC_H + +#include +#include + +struct _MSSpeexEnc +{ + MSFilter parent; + MSFifo *inf[1]; + MSQueue *outq[1]; /* speex has an output q because it can be variable bit rate */ + void *speex_state; + SpeexBits bits; + int frequency; + int bitrate; + int initialized; +}; + +typedef struct _MSSpeexEnc MSSpeexEnc; + + +struct _MSSpeexEncClass +{ + MSFilterClass parent; +}; + +typedef struct _MSSpeexEncClass MSSpeexEncClass; + + +#define MS_SPEEX_ENC(o) ((MSSpeexEnc*)(o)) +#define MS_SPEEX_ENC_CLASS(o) ((MSSpeexEncClass*)(o)) + +/* generic constructor */ +MSFilter * ms_speex_enc_new(); + +void ms_speex_enc_init_core(MSSpeexEnc *obj,const SpeexMode *mode, gint quality); +void ms_speex_enc_uninit_core(MSSpeexEnc *obj); +void ms_speex_enc_init(MSSpeexEnc *obj); +void ms_speex_enc_class_init(MSSpeexEncClass *klass); + + +void ms_speex_enc_process(MSSpeexEnc *obj); +void ms_speex_enc_destroy(MSSpeexEnc *obj); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssync.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssync.c new file mode 100644 index 00000000..7656211b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssync.c @@ -0,0 +1,193 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "mssync.h" +#include + +/* TODO: + -define an uninit function that free the mutex +*/ + +/** + * function_name:ms_sync_get_bytes_per_tick + * @sync: A #MSSync object. + * + * Returns the number of bytes per tick. This is a usefull information for sources, so + * that they can know how much data they must deliver each time they are called. + * + */ + +/* private */ +void ms_sync_init(MSSync *sync) +{ + sync->klass=NULL; + sync->lock=g_mutex_new(); + sync->thread_cond=g_cond_new(); + sync->stop_cond=g_cond_new(); + sync->attached_filters=NULL; + sync->execution_list=NULL; + sync->filters=0; + sync->run=0; + sync->flags=0; + sync->samples_per_tick=0; + sync->ticks=0; + sync->time=0; + sync->thread=NULL; +} + +void ms_sync_class_init(MSSyncClass *klass) +{ + klass->max_filters=0; + klass->synchronize=NULL; + klass->attach=ms_sync_attach_generic; + klass->detach=ms_sync_detach_generic; + klass->destroy=NULL; +} + +/* public*/ + + +/** + * ms_sync_attach: + * @sync: A #MSSync object. + * @f: A #MSFilter object. + * + * Attach a chain of filters to a synchronisation source @sync. Filter @f must be the first filter of the processing chain. + * In order to be run, each chain of filter must be attached to a synchronisation source, that will be responsible for scheduling + * the processing. Multiple chains can be attached to a single synchronisation. + * + * Returns: 0 if successfull, a negative value reprensenting the errno.h error. + */ +int ms_sync_attach(MSSync *sync,MSFilter *f) +{ + gint err; + ms_sync_lock(sync); + err=sync->klass->attach(sync,f); + ms_sync_update(sync); + ms_sync_unlock(sync); + return(err); +} + +int ms_sync_attach_generic(MSSync *sync,MSFilter *f) +{ + int i; + /* //printf("attr: %i\n",f->klass->attributes); */ + g_return_val_if_fail(f->klass->attributes & FILTER_IS_SOURCE,-EINVAL); + g_return_val_if_fail(sync->attached_filters!=NULL,-EFAULT); + + + /* find a free place to attach*/ + for (i=0;iklass->max_filters;i++) + { + if (sync->attached_filters[i]==NULL) + { + sync->attached_filters[i]=f; + sync->filters++; + ms_trace("Filter succesfully attached to sync."); + return 0; + } + } + g_warning("No more link on sync !"); + return(-EMLINK); +} + +/** + * ms_sync_detach: + * @sync: A #MSSync object. + * @f: A #MSFilter object. + * + * Dettach a chain of filters to a synchronisation source. Filter @f must be the first filter of the processing chain. + * The processing chain will no more be executed. + * + * Returns: 0 if successfull, a negative value reprensenting the errno.h error. + */ +int ms_sync_detach(MSSync *sync,MSFilter *f) +{ + gint err; + ms_sync_lock(sync); + err=sync->klass->detach(sync,f); + ms_sync_update(sync); + ms_sync_unlock(sync); + return(err); +} + +int ms_sync_detach_generic(MSSync *sync,MSFilter *f) +{ + int i; + g_return_val_if_fail(f->klass->attributes & FILTER_IS_SOURCE,-EINVAL); + g_return_val_if_fail(sync->attached_filters!=NULL,-EFAULT); + for (i=0;ifilters;i++) + { + if (sync->attached_filters[i]==f) + { + sync->attached_filters[i]=NULL; + sync->filters--; + return 0; + } + } + return(-EMLINK); +} + +void ms_sync_set_samples_per_tick(MSSync *sync,gint size) +{ + if (sync->samples_per_tick==0) + { + sync->samples_per_tick=size; + g_cond_signal(sync->thread_cond); + } + else sync->samples_per_tick=size; +} + +/* call the setup func of each filter attached to the graph */ +void ms_sync_setup(MSSync *sync) +{ + GList *elem=sync->execution_list; + MSFilter *f; + while(elem!=NULL){ + f=(MSFilter*)elem->data; + if (f->klass->setup!=NULL){ + f->klass->setup(f,sync); + } + elem=g_list_next(elem); + } +} + +/* call the unsetup func of each filter attached to the graph */ +void ms_sync_unsetup(MSSync *sync) +{ + GList *elem=sync->execution_list; + MSFilter *f; + while(elem!=NULL){ + f=(MSFilter*)elem->data; + if (f->klass->unsetup!=NULL){ + f->klass->unsetup(f,sync); + } + elem=g_list_next(elem); + } +} + + +int ms_sync_uninit(MSSync *sync) +{ + g_mutex_free(sync->lock); + g_cond_free(sync->thread_cond); + g_cond_free(sync->stop_cond); +} + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssync.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssync.h new file mode 100644 index 00000000..012c068f --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mssync.h @@ -0,0 +1,136 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef MS_SYNC_H +#define MS_SYNC_H + + +#include "msfilter.h" + +struct _MSSync +{ + struct _MSSyncClass *klass; + GMutex *lock; + MSFilter **attached_filters; /* pointer to a table of pointer of filters*/ + GList *execution_list; /* the list of filters to be executed. This is filled with compilation */ + gint filters; /*number of filters attached to the sync */ + gint run; /* flag to indicate whether the sync must be run or not */ + GThread * thread; /* the thread ressource if this sync is run by a thread*/ + GCond *thread_cond; + GCond *stop_cond; + guint32 flags; + gint interval; /* in miliseconds*/ +#define MS_SYNC_NEED_UPDATE (0x0001) /* a modification has occured in the processing chains + attached to this sync; so the execution list has to be updated */ + guint samples_per_tick; /* number of bytes produced by sources of the processing chains*/ + guint32 ticks; + guint32 time; /* a time since the start of the sync expressed in milisec*/ +}; + +typedef struct _MSSync MSSync; + +typedef void (*MSSyncDestroyFunc)(MSSync*); +typedef void (*MSSyncSyncFunc)(MSSync*); +typedef int (*MSSyncAttachFunc)(MSSync*,MSFilter*); +typedef int (*MSSyncDetachFunc)(MSSync*,MSFilter*); + +typedef struct _MSSyncClass +{ + gint max_filters; /* the maximum number of filters that can be attached to this sync*/ + MSSyncSyncFunc synchronize; + MSSyncDestroyFunc destroy; + MSSyncAttachFunc attach; + MSSyncDetachFunc detach; +} MSSyncClass; + +/* private */ +void ms_sync_init(MSSync *sync); +void ms_sync_class_init(MSSyncClass *klass); + +int ms_sync_attach_generic(MSSync *sync,MSFilter *f); +int ms_sync_detach_generic(MSSync *sync,MSFilter *f); + +/* public*/ + +#define MS_SYNC(sync) ((MSSync*)(sync)) +#define MS_SYNC_CLASS(klass) ((MSSyncClass*)(klass)) + +#define ms_sync_synchronize(_sync) \ +do \ +{ \ + MSSync *__sync=_sync; \ + __sync->ticks++; \ + ((__sync)->klass->synchronize((__sync))); \ +}while(0) + +void ms_sync_setup(MSSync *sync); + +void ms_sync_unsetup(MSSync *sync); + +#define ms_sync_update(sync) (sync)->flags|=MS_SYNC_NEED_UPDATE + +#define ms_sync_get_samples_per_tick(sync) ((sync)->samples_per_tick) + +void ms_sync_set_samples_per_tick(MSSync *sync,gint size); + +#define ms_sync_get_tick_count(sync) ((sync)->ticks) + +#define ms_sync_suspend(sync) g_cond_wait((sync)->thread_cond,(sync)->lock) + +#define ms_sync_lock(sync) g_mutex_lock((sync)->lock) + +#define ms_sync_unlock(sync) g_mutex_unlock((sync)->lock) + +#define ms_sync_trylock(sync) g_mutex_trylock((sync)->lock) + +/** + * function_name:ms_sync_attach + * @sync: A #MSSync object. + * @f: A #MSFilter object. + * + * Attach a chain of filters to a synchronisation source. Filter @f must be the first filter of the processing chain. + * + * Returns: 0 if successfull, a negative value reprensenting the errno.h error. + */ +int ms_sync_attach(MSSync *sync,MSFilter *f); + +/** + * ms_sync_detach: + * @sync: A #MSSync object. + * @f: A #MSFilter object. + * + * Dettach a chain of filters to a synchronisation source. Filter @f must be the first filter of the processing chain. + * The processing chain will no more be executed. + * + * Returns: 0 if successfull, a negative value reprensenting the errno.h error. + */ +int ms_sync_detach(MSSync *sync,MSFilter *f); + +int ms_sync_uninit(MSSync *sync); + +#define ms_sync_start(sync) ms_start((sync)) +#define ms_sync_stop(sync) ms_stop((sync)) + + +/*destroy*/ +#define ms_sync_destroy(sync) (sync)->klass->destroy((sync)) + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstimer.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstimer.c new file mode 100644 index 00000000..29b81d3c --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstimer.c @@ -0,0 +1,114 @@ + /* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "mstimer.h" +#include +#include +#include +#include + +static MSTimerClass *ms_timer_class=NULL; + + +void ms_timer_init(MSTimer *sync) +{ + ms_sync_init(MS_SYNC(sync)); + MS_SYNC(sync)->attached_filters=sync->filters; + memset(sync->filters,0,MSTIMER_MAX_FILTERS*sizeof(MSFilter*)); + MS_SYNC(sync)->samples_per_tick=160; + ms_timer_set_interval(sync,20); + sync->state=MS_TIMER_STOPPED; +} + +void ms_timer_class_init(MSTimerClass *klass) +{ + ms_sync_class_init(MS_SYNC_CLASS(klass)); + MS_SYNC_CLASS(klass)->max_filters=MSTIMER_MAX_FILTERS; + MS_SYNC_CLASS(klass)->synchronize=(MSSyncSyncFunc)ms_timer_synchronize; + MS_SYNC_CLASS(klass)->destroy=(MSSyncDestroyFunc)ms_timer_destroy; + /* no need to overload these function*/ + MS_SYNC_CLASS(klass)->attach=ms_sync_attach_generic; + MS_SYNC_CLASS(klass)->detach=ms_sync_detach_generic; +} + +void ms_timer_destroy(MSTimer *timer) +{ + g_free(timer); +} + + +void ms_timer_synchronize(MSTimer *timer) +{ + /* //printf("ticks=%i \n",MS_SYNC(timer)->ticks); */ + if (timer->state==MS_TIMER_STOPPED){ + timer->state=MS_TIMER_RUNNING; + gettimeofday(&timer->orig,NULL); + timer->sync.time=0; + } + else { + gint32 diff,time; + struct timeval tv,cur; + + gettimeofday(&cur,NULL); + time=((cur.tv_usec-timer->orig.tv_usec)/1000 ) + ((cur.tv_sec-timer->orig.tv_sec)*1000 ); + if ( (diff=time-timer->sync.time)>50){ + g_warning("Must catchup %i miliseconds.",diff); + } + while((diff = timer->sync.time-time) > 0) + { + tv.tv_sec = diff/1000; + tv.tv_usec = (diff%1000)*1000; + select(0,NULL,NULL,NULL,&tv); + gettimeofday(&cur,NULL); + time=((cur.tv_usec-timer->orig.tv_usec)/1000 ) + ((cur.tv_sec-timer->orig.tv_sec)*1000 ); + } + } + timer->sync.time+=timer->milisec; + return; +} + + +MSSync *ms_timer_new() +{ + MSTimer *timer; + + timer=g_malloc(sizeof(MSTimer)); + ms_timer_init(timer); + if (ms_timer_class==NULL) + { + ms_timer_class=g_new(MSTimerClass,1); + ms_timer_class_init(ms_timer_class); + } + MS_SYNC(timer)->klass=MS_SYNC_CLASS(ms_timer_class); + return(MS_SYNC(timer)); +} + +void ms_timer_set_interval(MSTimer *timer, int milisec) +{ + + MS_SYNC(timer)->ticks=0; + MS_SYNC(timer)->interval=milisec; + timer->interval.tv_sec=milisec/1000; + timer->interval.tv_usec=(milisec % 1000)*1000; + timer->milisec=milisec; + + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstimer.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstimer.h new file mode 100644 index 00000000..5c7e8ede --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstimer.h @@ -0,0 +1,68 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#ifndef MSTIMER_H +#define MSTIMER_H + +#include "mssync.h" +#include + +#define MSTIMER_MAX_FILTERS 10 + +/* MSTimer derivates from MSSync base class*/ + +typedef struct _MSTimer +{ + /* the MSSync must be the first field of the object in order to the object mechanism to work*/ + MSSync sync; + MSFilter *filters[MSTIMER_MAX_FILTERS]; + gint milisec; /* the interval */ + struct timeval interval; + struct timeval orig; + gint state; +} MSTimer; + + +typedef struct _MSTimerClass +{ + /* the MSSyncClass must be the first field of the class in order to the class mechanism to work*/ + MSSyncClass parent_class; +} MSTimerClass; + + +/*private*/ +#define MS_TIMER_RUNNING 1 +#define MS_TIMER_STOPPED 0 +void ms_timer_init(MSTimer *sync); +void ms_timer_class_init(MSTimerClass *sync); + +void ms_timer_destroy(MSTimer *timer); +void ms_timer_synchronize(MSTimer *timer); + +/*public*/ +void ms_timer_set_interval(MSTimer *timer, gint milisec); + +/* casts a MSSync object into a MSTimer */ +#define MS_TIMER(sync) ((MSTimer*)(sync)) +/* casts a MSSync class into a MSTimer class */ +#define MS_TIMER_CLASS(klass) ((MSTimerClass*)(klass)) + +MSSync *ms_timer_new(); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstruespeechdecoder.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstruespeechdecoder.h new file mode 100644 index 00000000..62477436 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstruespeechdecoder.h @@ -0,0 +1,55 @@ +/* + Copyright (C) 2003 Robert W. Brewer + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSTRUESPEECHDECODER_H +#define MSTRUESPEECHDECODER_H + +#include "msfilter.h" +#include "mstruespeechencoder.h" + + + +typedef struct _MSTrueSpeechDecoder +{ + /* the MSTrueSpeechDecoder derives from MSFilter, so the MSFilter + object MUST be the first of the MSTrueSpeechDecoder object + in order for the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MS_TRUESPEECH_CODEC_MAX_IN_OUT]; + MSFifo *f_outputs[MS_TRUESPEECH_CODEC_MAX_IN_OUT]; + Win32Codec* codec; +} MSTrueSpeechDecoder; + +typedef struct _MSTrueSpeechDecoderClass +{ + /* the MSTrueSpeechDecoder derives from MSFilter, + so the MSFilter class MUST be the first of the MSTrueSpechDecoder + class + in order for the class mechanism to work*/ + MSFilterClass parent_class; + Win32CodecDriver* driver; +} MSTrueSpeechDecoderClass; + +/* PUBLIC */ +#define MS_TRUESPEECHDECODER(filter) ((MSTrueSpechMDecoder*)(filter)) +#define MS_TRUESPEECHDECODER_CLASS(klass) ((MSTrueSpeechDecoderClass*)(klass)) +MSFilter * ms_truespeechdecoder_new(void); + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstruespeechencoder.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstruespeechencoder.h new file mode 100644 index 00000000..04e40bb8 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mstruespeechencoder.h @@ -0,0 +1,62 @@ +/* + Copyright (C) 2003 Robert W. Brewer + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef MSTRUESPEECHENCODER_H +#define MSTRUESPEECHENCODER_H + +#include "msfilter.h" +#include + + +#define MS_TRUESPEECH_CODEC_MAX_IN_OUT 1 /* max inputs/outputs per filter*/ + +#define TRUESPEECH_FORMAT_TAG 0x22 +#define TRUESPEECH_DLL "tssoft32.acm" + +typedef struct _MSTrueSpeechEncoder +{ + /* the MSTrueSpeechEncoder derives from MSFilter, so the MSFilter + object MUST be the first of the MSTrueSpeechEncoder object + in order for the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MS_TRUESPEECH_CODEC_MAX_IN_OUT]; + MSFifo *f_outputs[MS_TRUESPEECH_CODEC_MAX_IN_OUT]; + Win32Codec* codec; +} MSTrueSpeechEncoder; + +typedef struct _MSTrueSpeechEncoderClass +{ + /* the MSTrueSpeechEncoder derives from MSFilter, + so the MSFilter class MUST be the first of the MSTrueSpechEncoder + class + in order for the class mechanism to work*/ + MSFilterClass parent_class; + Win32CodecDriver* driver; +} MSTrueSpeechEncoderClass; + +/* PUBLIC */ +#define MS_TRUESPEECHENCODER(filter) ((MSTrueSpechMEncoder*)(filter)) +#define MS_TRUESPEECHENCODER_CLASS(klass) ((MSTrueSpeechEncoderClass*)(klass)) +MSFilter * ms_truespeechencoder_new(void); + +/* for internal use only */ +WAVEFORMATEX* ms_truespeechencoder_wf_create(); + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msutils.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msutils.h new file mode 100644 index 00000000..012b87d8 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msutils.h @@ -0,0 +1,61 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#ifndef MSUTILS_H +#define MSUTILS_H + +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef HAVE_GLIB +#include +#else +#include +#endif +#include + +#ifndef ENODATA +/* this is for freeBSD .*/ +#define ENODATA EWOULDBLOCK +#endif + +#ifdef MS_DEBUG + +#define ms_trace g_message + +#else + +#define ms_trace(...) +#endif + +#define ms_warning g_warning +#define ms_error g_error + +#define VIDEO_SIZE_CIF_W 352 +#define VIDEO_SIZE_CIF_H 288 +#define VIDEO_SIZE_QCIF_W 176 +#define VIDEO_SIZE_QCIF_H 144 +#define VIDEO_SIZE_4CIF_W 704 +#define VIDEO_SIZE_4CIF_H 576 +#define VIDEO_SIZE_MAX_W VIDEO_SIZE_4CIF_W +#define VIDEO_SIZE_MAX_H VIDEO_SIZE_4CIF_H + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msv4l.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msv4l.h new file mode 100644 index 00000000..e19ac9ea --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msv4l.h @@ -0,0 +1,96 @@ + /* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef MSV4L_H +#define MSV4L_H + +#include +#include +#include + +struct _MSV4l +{ + MSVideoSource parent; + int fd; + char *device; + struct video_capability cap; + struct video_channel channel; + struct video_window win; + struct video_picture pict; + struct video_mmap vmap; + struct video_mbuf vmbuf; + struct video_capture vcap; + gint bsize; + gint use_mmap; + gint frame; + guint query_frame; + gchar *mmapdbuf; /* the mmap'd buffer */ + MSBuffer img[VIDEO_MAX_FRAME]; /* the buffer wrappers used for mmaps */ + gint width; /* the capture image size - can be cropped to output size */ + gint height; + MSBuffer *allocdbuf; /* the buffer allocated for read() and mire */ + gint count; + MSBuffer *image_grabbed; + GCond *cond; + GCond *stopcond; + GThread *v4lthread; + gboolean grab_image; + gboolean thread_run; + gboolean thread_exited; +}; + +typedef struct _MSV4l MSV4l; + + +struct _MSV4lClass +{ + MSVideoSourceClass parent_class; + +}; + +typedef struct _MSV4lClass MSV4lClass; + + +/* PUBLIC API */ +#define MS_V4L(v) ((MSV4l*)(v)) +#define MS_V4L_CLASS(k) ((MSV4lClass*)(k)) +MSFilter * ms_v4l_new(); + +void ms_v4l_start(MSV4l *obj); +void ms_v4l_stop(MSV4l *obj); +int ms_v4l_set_device(MSV4l *f, const gchar *device); +gint ms_v4l_get_width(MSV4l *v4l); +gint ms_v4l_get_height(MSV4l *v4l); +void ms_v4l_set_size(MSV4l *v4l, gint w, gint h); + +/* PRIVATE API */ +void ms_v4l_init(MSV4l *obj); +void ms_v4l_class_init(MSV4lClass *klass); +int v4l_configure(MSV4l *f); + +void v4l_process(MSV4l *obj); + +void ms_v4l_uninit(MSV4l *obj); + +void ms_v4l_destroy(MSV4l *obj); + +extern MSFilterInfo v4l_info; + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msvideosource.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msvideosource.h new file mode 100644 index 00000000..9a27f836 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/msvideosource.h @@ -0,0 +1,74 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef MSVIDEOSOURCE_H +#define MSVIDEOSOURCE_H + + +#include "msfilter.h" + +/* this is the video input abstract class */ + +#define MSVIDEOSOURCE_MAX_OUTPUTS 1 /* max output per filter*/ + +typedef struct _MSVideoSource +{ + /* the MSVideoSource derivates from MSFilter, so the MSFilter object MUST be the first of the MSVideoSource object + in order to the object mechanism to work*/ + MSFilter filter; + MSQueue *outputs[MSVIDEOSOURCE_MAX_OUTPUTS]; + gchar *dev_name; + gint width, height; + gchar *format; + gint frame_rate; + gint frame_rate_base; +} MSVideoSource; + +typedef struct _MSVideoSourceClass +{ + /* the MSVideoSource derivates from MSFilter, so the MSFilter class MUST be the first of the MSVideoSource class + in order to the class mechanism to work*/ + MSFilterClass parent_class; + gint (*set_device)(MSVideoSource *s, const gchar *name); + void (*start)(MSVideoSource *s); + void (*stop)(MSVideoSource *s); + void (*set_size)(MSVideoSource *s, gint width, gint height); + void (*set_frame_rate)(MSVideoSource *s, gint frame_rate, gint frame_rate_base); +} MSVideoSourceClass; + +/* PUBLIC */ +void ms_video_source_register_all(); +int ms_video_source_set_device(MSVideoSource *f, const gchar *device); +gchar* ms_video_source_get_device_name(MSVideoSource *f); +void ms_video_source_start(MSVideoSource *f); +void ms_video_source_stop(MSVideoSource *f); +void ms_video_source_set_size(MSVideoSource *f, gint width, gint height); +void ms_video_source_set_frame_rate(MSVideoSource *f, gint frame_rate, gint frame_rate_base); +gchar* ms_video_source_get_format(MSVideoSource *f); + +#define MS_VIDEO_SOURCE(obj) ((MSVideoSource*)(obj)) +#define MS_VIDEO_SOURCE_CLASS(klass) ((MSVideoSourceClass*)(klass)) + + +/* FOR INTERNAL USE*/ +void ms_video_source_init(MSVideoSource *f); +void ms_video_source_class_init(MSVideoSourceClass *klass); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mswrite.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mswrite.c new file mode 100644 index 00000000..178e294c --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mswrite.c @@ -0,0 +1,121 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "mswrite.h" +#include +#include +#include +#include +#include +#include + +static MSWriteClass *ms_write_class=NULL; + +MSFilter * ms_write_new(char *name) +{ + MSWrite *r; + int fd=-1; + + r=g_new(MSWrite,1); + ms_write_init(r); + if (ms_write_class==NULL) + { + ms_write_class=g_new(MSWriteClass,1); + ms_write_class_init(ms_write_class); + } + MS_FILTER(r)->klass=MS_FILTER_CLASS(ms_write_class); + if ((name!=NULL) && (strlen(name)!=0)) + { + fd=open(name,O_WRONLY | O_CREAT | O_TRUNC,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); + if (fd<0) g_error("ms_write_new: failed to open %s.\n",name); + } + r->fd=fd; + return(MS_FILTER(r)); +} + + +/* FOR INTERNAL USE*/ +void ms_write_init(MSWrite *r) +{ + ms_filter_init(MS_FILTER(r)); + MS_FILTER(r)->infifos=r->f_inputs; + MS_FILTER(r)->inqueues=r->q_inputs; + MS_FILTER(r)->r_mingran=MSWRITE_MIN_GRAN; + memset(r->f_inputs,0,sizeof(MSFifo*)*MSWRITE_MAX_INPUTS); + memset(r->q_inputs,0,sizeof(MSQueue*)*MSWRITE_MAX_INPUTS); + r->fd=-1; +} + +void ms_write_class_init(MSWriteClass *klass) +{ + ms_filter_class_init(MS_FILTER_CLASS(klass)); + ms_filter_class_set_name(MS_FILTER_CLASS(klass),"dskwriter"); + MS_FILTER_CLASS(klass)->max_finputs=MSWRITE_MAX_INPUTS; + MS_FILTER_CLASS(klass)->max_qinputs=MSWRITE_MAX_INPUTS; + MS_FILTER_CLASS(klass)->r_maxgran=MSWRITE_DEF_GRAN; + MS_FILTER_CLASS(klass)->destroy=(MSFilterDestroyFunc)ms_write_destroy; + MS_FILTER_CLASS(klass)->process=(MSFilterProcessFunc)ms_write_process; +} + +void ms_write_process(MSWrite *r) +{ + MSFifo *f; + MSQueue *q; + MSMessage *buf=NULL; + int i,j,err1,err2; + gint gran=ms_filter_get_mingran(MS_FILTER(r)); + void *p; + + /* process output fifos*/ + for (i=0,j=0;(iklass->max_finputs)&&(jfinputs);i++) + { + f=r->f_inputs[i]; + if (f!=NULL) + { + if ( (err1=ms_fifo_get_read_ptr(f,gran,&p))>0 ) + { + + err2=write(r->fd,p,gran); + if (err2<0) g_warning("ms_write_process: failed to write: %s.\n",strerror(errno)); + } + j++; + } + } + /* process output queues*/ + for (i=0,j=0;(iklass->max_qinputs)&&(jqinputs);i++) + { + q=r->q_inputs[i]; + if (q!=NULL) + { + while ( (buf=ms_queue_get(q))!=NULL ){ + write(r->fd,buf->data,buf->size); + j++; + ms_message_destroy(buf); + } + } + } +} + +void ms_write_destroy( MSWrite *obj) +{ + if (obj->fd!=0) close(obj->fd); + g_free(obj); +} + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mswrite.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mswrite.h new file mode 100644 index 00000000..cd766d10 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/mswrite.h @@ -0,0 +1,63 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef MSWRITE_H +#define MSWRITE_H + +#include "msfilter.h" + + +/*this is the class that implements writing reading sink filter*/ + +#define MSWRITE_MAX_INPUTS 1 /* max output per filter*/ + +#define MSWRITE_DEF_GRAN 512 /* the default granularity*/ +#define MSWRITE_MIN_GRAN 64 + +typedef struct _MSWrite +{ + /* the MSWrite derivates from MSFilter, so the MSFilter object MUST be the first of the MSWrite object + in order to the object mechanism to work*/ + MSFilter filter; + MSFifo *f_inputs[MSWRITE_MAX_INPUTS]; + MSQueue *q_inputs[MSWRITE_MAX_INPUTS]; + gint fd; /* the file descriptor of the file being written*/ +} MSWrite; + +typedef struct _MSWriteClass +{ + /* the MSWrite derivates from MSFilter, so the MSFilter class MUST be the first of the MSWrite class + in order to the class mechanism to work*/ + MSFilterClass parent_class; +} MSWriteClass; + +/* PUBLIC */ +#define MS_WRITE(filter) ((MSWrite*)(filter)) +#define MS_WRITE_CLASS(klass) ((MSWriteClass*)(klass)) +MSFilter * ms_write_new(char *name); + +/* FOR INTERNAL USE*/ +void ms_write_init(MSWrite *r); +void ms_write_class_init(MSWriteClass *klass); +void ms_write_destroy( MSWrite *obj); +void ms_write_process(MSWrite *r); + +#endif + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/osscard.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/osscard.c new file mode 100644 index 00000000..636c5792 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/osscard.c @@ -0,0 +1,495 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "osscard.h" + +#include "msossread.h" +#include "msosswrite.h" + +#ifdef HAVE_SYS_SOUNDCARD_H +#include + +#include +#include +#include + +#if 0 +void * oss_thread(OssCard *obj) +{ + gint i; + gint err; + g_message("oss_thread: starting **********"); + while(1){ + for(i=0;ilock); + if (obj->ref==0){ + g_cond_signal(obj->cond); + g_mutex_unlock(obj->lock); + g_thread_exit(NULL); + } + g_mutex_unlock(obj->lock); + obj->readindex=i; + + err=read(obj->fd,obj->readbuf[i],SND_CARD(obj)->bsize); + if (err<0) g_warning("oss_thread: read() error:%s.",strerror(errno)); + obj->writeindex=i; + write(obj->fd,obj->writebuf[i],SND_CARD(obj)->bsize); + memset(obj->writebuf[i],0,SND_CARD(obj)->bsize); + } + } +} +#endif +int oss_open(OssCard *obj, int bits,int stereo, int rate) +{ + int fd; + int p=0,cond=0; + int i=0; + int min_size=0,blocksize=512; + int err; + + //g_message("opening sound device"); + fd=open(obj->dev_name,O_RDWR|O_NONBLOCK); + if (fd<0) return -EWOULDBLOCK; + /* unset nonblocking mode */ + /* We wanted non blocking open but now put it back to normal ; thanks Xine !*/ + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL)&~O_NONBLOCK); + + /* reset is maybe not needed but takes time*/ + /*ioctl(fd, SNDCTL_DSP_RESET, 0); */ + + +#ifdef WORDS_BIGENDIAN + p=AFMT_U16_BE; +#else + p=AFMT_U16_LE; +#endif + + err=ioctl(fd,SNDCTL_DSP_SETFMT,&p); + if (err<0){ + g_warning("oss_open: can't set sample format:%s.",strerror(errno)); + } + + + p = bits; /* 16 bits */ + err=ioctl(fd, SNDCTL_DSP_SAMPLESIZE, &p); + if (err<0){ + g_warning("oss_open: can't set sample size to %i:%s.",bits,strerror(errno)); + } + + p = rate; /* rate in khz*/ + err=ioctl(fd, SNDCTL_DSP_SPEED, &p); + if (err<0){ + g_warning("oss_open: can't set sample rate to %i:%s.",rate,strerror(errno)); + } + + p = stereo; /* stereo or not */ + err=ioctl(fd, SNDCTL_DSP_STEREO, &p); + if (err<0){ + g_warning("oss_open: can't set mono/stereo mode:%s.",strerror(errno)); + } + + if (rate==16000) blocksize=4096; /* oss emulation is not very good at 16khz */ + else blocksize=blocksize*(rate/8000); + ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &min_size); + + /* try to subdivide BLKSIZE to reach blocksize if necessary */ + if (min_size>blocksize) + { + cond=1; + p=min_size/blocksize; + while(cond) + { + i=ioctl(fd, SNDCTL_DSP_SUBDIVIDE, &p); + //printf("SUB_DIVIDE said error=%i,errno=%i\n",i,errno); + if ((i==0) || (p==1)) cond=0; + else p=p/2; + } + } + ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &min_size); + if (min_size>blocksize) + { + g_warning("dsp block size set to %i.",min_size); + }else{ + /* no need to access the card with less latency than needed*/ + min_size=blocksize; + } + + g_message("dsp blocksize is %i.",min_size); + + /* start recording !!! Alex */ + { + int fl,res; + + fl=PCM_ENABLE_OUTPUT|PCM_ENABLE_INPUT; + res=ioctl(fd, SNDCTL_DSP_SETTRIGGER, &fl); + if (res<0) g_warning("OSS_TRIGGER: %s",strerror(errno)); + } + + obj->fd=fd; + obj->readpos=0; + obj->writepos=0; + SND_CARD(obj)->bits=bits; + SND_CARD(obj)->stereo=stereo; + SND_CARD(obj)->rate=rate; + SND_CARD(obj)->bsize=min_size; + return fd; +} + +int oss_card_probe(OssCard *obj,int bits,int stereo,int rate) +{ + + int fd; + int p=0,cond=0; + int i=0; + int min_size=0,blocksize=512; + + if (obj->fd>0) return SND_CARD(obj)->bsize; + fd=open(obj->dev_name,O_RDWR|O_NONBLOCK); + if (fd<0) { + g_warning("oss_card_probe: can't open %s: %s.",obj->dev_name,strerror(errno)); + return -1; + } + ioctl(fd, SNDCTL_DSP_RESET, 0); + + p = bits; /* 16 bits */ + ioctl(fd, SNDCTL_DSP_SAMPLESIZE, &p); + + p = stereo; /* number of channels */ + ioctl(fd, SNDCTL_DSP_CHANNELS, &p); + + p = rate; /* rate in khz*/ + ioctl(fd, SNDCTL_DSP_SPEED, &p); + + ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &min_size); + + /* try to subdivide BLKSIZE to reach blocksize if necessary */ + if (min_size>blocksize) + { + cond=1; + p=min_size/blocksize; + while(cond) + { + i=ioctl(fd, SNDCTL_DSP_SUBDIVIDE, &p); + //printf("SUB_DIVIDE said error=%i,errno=%i\n",i,errno); + if ((i==0) || (p==1)) cond=0; + else p=p/2; + } + } + ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &min_size); + if (min_size>blocksize) + { + g_warning("dsp block size set to %i.",min_size); + }else{ + /* no need to access the card with less latency than needed*/ + min_size=blocksize; + } + close(fd); + return min_size; +} + + +int oss_card_open(OssCard *obj,int bits,int stereo,int rate) +{ + int fd; + obj->ref++; + if (obj->fd==0){ + fd=oss_open(obj,bits,stereo,rate); + if (fd<0) { + obj->fd=0; + obj->ref--; + return -1; + } + } + + obj->readbuf=g_malloc0(SND_CARD(obj)->bsize); + obj->writebuf=g_malloc0(SND_CARD(obj)->bsize); + + SND_CARD(obj)->flags|=SND_CARD_FLAGS_OPENED; + return 0; +} + +void oss_card_close(OssCard *obj) +{ + int i; + obj->ref--; + if (obj->ref==0) { + close(obj->fd); + obj->fd=0; + SND_CARD(obj)->flags&=~SND_CARD_FLAGS_OPENED; + g_free(obj->readbuf); + obj->readbuf=NULL; + g_free(obj->writebuf); + obj->writebuf=NULL; + + } +} + +void oss_card_destroy(OssCard *obj) +{ + snd_card_uninit(SND_CARD(obj)); + g_free(obj->dev_name); + g_free(obj->mixdev_name); + if (obj->readbuf!=NULL) g_free(obj->readbuf); + if (obj->writebuf!=NULL) g_free(obj->writebuf); +} + +gboolean oss_card_can_read(OssCard *obj) +{ + struct timeval tout={0,0}; + int err; + fd_set fdset; + if (obj->readpos!=0) return TRUE; + FD_ZERO(&fdset); + FD_SET(obj->fd,&fdset); + err=select(obj->fd+1,&fdset,NULL,NULL,&tout); + if (err>0) return TRUE; + else return FALSE; +} + +int oss_card_read(OssCard *obj,char *buf,int size) +{ + int err; + gint bsize=SND_CARD(obj)->bsize; + if (sizereadpos,size); + if (obj->readpos==0){ + err=read(obj->fd,obj->readbuf,bsize); + if (err<0) { + g_warning("oss_card_read: read() failed:%s.",strerror(errno)); + return -1; + } + } + + memcpy(buf,&obj->readbuf[obj->readpos],canread); + obj->readpos+=canread; + if (obj->readpos>=bsize) obj->readpos=0; + return canread; + }else{ + err=read(obj->fd,buf,size); + if (err<0) { + g_warning("oss_card_read: read-2() failed:%s.",strerror(errno)); + } + return err; + } + +} + +int oss_card_write(OssCard *obj,char *buf,int size) +{ + int err; + gint bsize=SND_CARD(obj)->bsize; + + if (sizewritepos,size); + memcpy(&obj->writebuf[obj->writepos],buf,canwrite); + obj->writepos+=canwrite; + if (obj->writepos>=bsize){ + err=write(obj->fd,obj->writebuf,bsize); + obj->writepos=0; + } + return canwrite; + }else{ + return write(obj->fd,buf,bsize); + } +} + +void oss_card_set_level(OssCard *obj,gint way,gint a) +{ + int p,mix_fd; + int osscmd; + g_return_if_fail(obj->mixdev_name!=NULL); +#ifdef HAVE_SYS_SOUNDCARD_H + switch(way){ + case SND_CARD_LEVEL_GENERAL: + osscmd=SOUND_MIXER_VOLUME; + break; + case SND_CARD_LEVEL_INPUT: + osscmd=SOUND_MIXER_IGAIN; + break; + case SND_CARD_LEVEL_OUTPUT: + osscmd=SOUND_MIXER_PCM; + break; + default: + g_warning("oss_card_set_level: unsupported command."); + return; + } + p=(((int)a)<<8 | (int)a); + mix_fd = open(obj->mixdev_name, O_WRONLY); + ioctl(mix_fd,MIXER_WRITE(osscmd), &p); + close(mix_fd); +#endif +} + +gint oss_card_get_level(OssCard *obj,gint way) +{ + int p=0,mix_fd; + int osscmd; + g_return_if_fail(obj->mixdev_name!=NULL); +#ifdef HAVE_SYS_SOUNDCARD_H + switch(way){ + case SND_CARD_LEVEL_GENERAL: + osscmd=SOUND_MIXER_VOLUME; + break; + case SND_CARD_LEVEL_INPUT: + osscmd=SOUND_MIXER_IGAIN; + break; + case SND_CARD_LEVEL_OUTPUT: + osscmd=SOUND_MIXER_PCM; + break; + default: + g_warning("oss_card_get_level: unsupported command."); + return -1; + } + mix_fd = open(obj->mixdev_name, O_RDONLY); + ioctl(mix_fd,MIXER_READ(SOUND_MIXER_VOLUME), &p); + close(mix_fd); +#endif + return p>>8; +} + +void oss_card_set_source(OssCard *obj,int source) +{ + gint p=0; + gint mix_fd; + g_return_if_fail(obj->mixdev_name!=NULL); +#ifdef HAVE_SYS_SOUNDCARD_H + if (source == 'c') + p = 1 << SOUND_MIXER_CD; + if (source == 'l') + p = 1 << SOUND_MIXER_LINE; + if (source == 'm') + p = 1 << SOUND_MIXER_MIC; + + + mix_fd = open(obj->mixdev_name, O_WRONLY); + ioctl(mix_fd, SOUND_MIXER_WRITE_RECSRC, &p); + close(mix_fd); +#endif +} + +MSFilter *oss_card_create_read_filter(OssCard *card) +{ + MSFilter *f=ms_oss_read_new(); + ms_oss_read_set_device(MS_OSS_READ(f),SND_CARD(card)->index); + return f; +} + +MSFilter *oss_card_create_write_filter(OssCard *card) +{ + MSFilter *f=ms_oss_write_new(); + ms_oss_write_set_device(MS_OSS_WRITE(f),SND_CARD(card)->index); + return f; +} + + +SndCard * oss_card_new(char *devname, char *mixdev_name) +{ + OssCard * obj= g_new0(OssCard,1); + SndCard *base= SND_CARD(obj); + snd_card_init(base); + obj->dev_name=g_strdup(devname); + obj->mixdev_name=g_strdup( mixdev_name); +#ifdef HAVE_GLIB + base->card_name=g_strdup_printf("%s (Open Sound System)",devname); +#else + base->card_name=malloc(100); + snprintf(base->card_name, 100, "%s (Open Sound System)",devname); +#endif + base->_probe=(SndCardOpenFunc)oss_card_probe; + base->_open_r=(SndCardOpenFunc)oss_card_open; + base->_open_w=(SndCardOpenFunc)oss_card_open; + base->_can_read=(SndCardPollFunc)oss_card_can_read; + base->_read=(SndCardIOFunc)oss_card_read; + base->_write=(SndCardIOFunc)oss_card_write; + base->_close_r=(SndCardCloseFunc)oss_card_close; + base->_close_w=(SndCardCloseFunc)oss_card_close; + base->_set_rec_source=(SndCardMixerSetRecSourceFunc)oss_card_set_source; + base->_set_level=(SndCardMixerSetLevelFunc)oss_card_set_level; + base->_get_level=(SndCardMixerGetLevelFunc)oss_card_get_level; + base->_destroy=(SndCardDestroyFunc)oss_card_destroy; + base->_create_read_filter=(SndCardCreateFilterFunc)oss_card_create_read_filter; + base->_create_write_filter=(SndCardCreateFilterFunc)oss_card_create_write_filter; + return base; +} + +#define DSP_NAME "/dev/dsp" +#define MIXER_NAME "/dev/mixer" + +gint oss_card_manager_init(SndCardManager *manager, gint tabindex) +{ + gchar *devname; + gchar *mixername; + gint devindex=0; + gint found=0; + + /* search for /dev/dsp and /dev/mixer */ +#ifdef HAVE_GLIB + if (g_file_test(DSP_NAME,G_FILE_TEST_EXISTS)){ + tabindex++; + devindex++; + manager->cards[0]=oss_card_new(DSP_NAME,MIXER_NAME); + manager->cards[0]->index=0; + found++; + g_message("Found /dev/dsp."); + } + for (;tabindexcards[tabindex]=oss_card_new(devname,mixername); + manager->cards[tabindex]->index=tabindex; + tabindex++; + found++; + } + g_free(devname); + g_free(mixername); + } +#else + if (access(DSP_NAME,F_OK)==0){ + tabindex++; + devindex++; + manager->cards[0]=oss_card_new(DSP_NAME,MIXER_NAME); + manager->cards[0]->index=0; + found++; + g_message("Found /dev/dsp."); + } + for (;tabindexcards[tabindex]=oss_card_new(devname,mixername); + manager->cards[tabindex]->index=tabindex; + tabindex++; + found++; + } + g_free(devname); + g_free(mixername); + } +#endif + if (tabindex==0) g_warning("No sound cards found !"); + return found; +} + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/osscard.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/osscard.h new file mode 100644 index 00000000..30b96c23 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/osscard.h @@ -0,0 +1,47 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +/* An implementation of SndCard : the OssCard */ + +#ifndef OSS_CARD_H +#define OSS_CARD_H + +#include "sndcard.h" + +#define OSS_CARD_BUFFERS 3 +struct _OssCard +{ + SndCard parent; + gchar *dev_name; /* /dev/dsp0 for example */ + gchar *mixdev_name; /* /dev/mixer0 for example */ + gint fd; /* the file descriptor of the open soundcard, 0 if not open*/ + gint ref; + gchar *readbuf; + gint readpos; + gchar *writebuf; + gint writepos; +}; + +typedef struct _OssCard OssCard; + +SndCard * oss_card_new(char *devname, char *mixdev_name); + +typedef OssCard HpuxSndCard; + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/portaudiocard.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/portaudiocard.c new file mode 100644 index 00000000..9570b905 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/portaudiocard.c @@ -0,0 +1,315 @@ +/* + Copyright (C) 2005 Remko Troncon + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include + +#include "portaudiocard.h" +#include "msossread.h" +#include "msosswrite.h" + +// Settings +#define BUFFER_SIZE 2048 + +// PortAudio settings +#define FRAMES_PER_BUFFER 256 + + +// ----------------------------------------------------------------------------- + +int readBuffer(char* buffer, char** buffer_read_p, char*buffer_write, char* buffer_end, char* target_buffer, int target_len) +{ + char *end, *tmp, *buffer_read = *buffer_read_p; + size_t remaining, len; + int read = 0; + + // First phase + tmp = buffer_read + target_len; + if (buffer_write < buffer_read) { + if (tmp > buffer_end) { + end = buffer_end; + remaining = tmp - buffer_end; + } + else { + end = tmp; + remaining = 0; + } + } + else { + end = (tmp >= buffer_write ? buffer_write : tmp); + remaining = 0; + } + //printf("end: %p\n",end); + + // Copy the data + len = end - buffer_read; + memcpy(target_buffer, buffer_read, len); + buffer_read += len; + target_buffer += len; + read += len; + + // Second phase + if (remaining > 0) { + buffer_read = buffer; + tmp = buffer_read + remaining; + len = (tmp > buffer_write ? buffer_write : tmp) - buffer_read; + memcpy(target_buffer, buffer_read, len); + buffer_read += len; + read += len; + } + + // Finish up + *buffer_read_p = buffer_read; + + return read; +} + +int writeBuffer(char* buffer, char* buffer_read, char** buffer_write_p, char* buffer_end, char* source_buffer, int source_len) +{ + char *end, *tmp, *buffer_write = *buffer_write_p; + size_t remaining, len; + int written = 0; + + // First phase + tmp = buffer_write + source_len; + if (buffer_write >= buffer_read) { + if (tmp > buffer_end) { + end = buffer_end; + remaining = tmp - buffer_end; + } + else { + end = tmp; + remaining = 0; + } + } + else { + if (tmp > buffer_read) { + printf("Warning: Dropping frame(s) %p %p\n", tmp, buffer_read); + end = buffer_read; + remaining = 0; + } + else { + end = tmp; + remaining = 0; + } + } + + len = end - buffer_write; + memcpy(buffer_write, source_buffer, len); + buffer_write += len; + source_buffer += len; + written += len; + + // Second phase + if (remaining > 0) { + buffer_write = buffer; + tmp = buffer_write + remaining; + if (tmp > buffer_read) { + printf("Warning: Dropping frame(s) %p %p\n", tmp, buffer_read); + end = buffer_read; + } + else { + end = tmp; + } + + len = end - buffer_write; + memcpy(buffer_write, source_buffer, len); + buffer_write += len; + written += len; + } + + // Finish up + *buffer_write_p = buffer_write; + return written; +} + +// ----------------------------------------------------------------------------- + +static int portAudioCallback( void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, PaTimestamp outTime, void *card_p ) +{ + PortAudioCard* card = (PortAudioCard*) card_p; + + size_t len = framesPerBuffer * Pa_GetSampleSize(paInt16); + //printf("PA::readBuffer begin %p %p %p %p %d\n",card->out_buffer,card->out_buffer_read,card->out_buffer_write,card->out_buffer_end, len); + readBuffer(card->out_buffer,&card->out_buffer_read,card->out_buffer_write,card->out_buffer_end, outputBuffer, len); + //printf("PA::readBuffer end %p %p %p %p %d\n",card->out_buffer,card->out_buffer_read,card->out_buffer_write,card->out_buffer_end, len); + writeBuffer(card->in_buffer,card->in_buffer_read,&card->in_buffer_write,card->in_buffer_end, inputBuffer, len); + return 0; +} + +// ----------------------------------------------------------------------------- + +int portaudio_card_probe(PortAudioCard *obj, int bits, int stereo, int rate) +{ + return FRAMES_PER_BUFFER * (SND_CARD(obj)->stereo ? 2 : 1) * Pa_GetSampleSize(paInt16); +} + + +int portaudio_card_open_r(PortAudioCard *obj,int bits,int stereo,int rate) +{ + fprintf(stderr,"Opening PortAudio card\n"); + + int err; + err = Pa_OpenDefaultStream(&obj->stream, 1, 1, paInt16, rate, FRAMES_PER_BUFFER, 0, portAudioCallback, obj); + if (err != paNoError) { + fprintf(stderr, "Error creating a PortAudio stream: %s\n", Pa_GetErrorText(err)); + return -1; + } + + err = Pa_StartStream(obj->stream); + if (err != paNoError) { + fprintf(stderr, "Error starting PortAudio stream: %s\n", Pa_GetErrorText(err)); + Pa_CloseStream(obj->stream); + obj->stream = NULL; + return -1; + } + + SND_CARD(obj)->bits = 16; + SND_CARD(obj)->stereo = 0; + SND_CARD(obj)->rate = rate; + // Should this be multiplied by Pa_GetMinNumBuffers(FRAMES_PER_BUFFER,sampleRate) ? + SND_CARD(obj)->bsize = FRAMES_PER_BUFFER * (SND_CARD(obj)->stereo ? 2 : 1) * Pa_GetSampleSize(paInt16); + + return 0; + +} + +void portaudio_card_close_r(PortAudioCard *obj) +{ + fprintf(stderr, "Closing PortAudio card\n"); + if (obj->stream) { + Pa_StopStream(obj->stream); + Pa_CloseStream(obj->stream); + obj->stream = NULL; + } +} + +int portaudio_card_open_w(PortAudioCard *obj,int bits,int stereo,int rate) +{ +} + +void portaudio_card_close_w(PortAudioCard *obj) +{ +} + +void portaudio_card_destroy(PortAudioCard *obj) +{ + snd_card_uninit(SND_CARD(obj)); + free(obj->in_buffer); + free(obj->out_buffer); +} + +gboolean portaudio_card_can_read(PortAudioCard *obj) +{ + return obj->in_buffer_read != obj->in_buffer_write; +} + +int portaudio_card_read(PortAudioCard *obj,char *buf,int size) +{ + //printf("read begin %p %p %p %p %d\n",obj->in_buffer,obj->in_buffer_read,obj->in_buffer_write,obj->in_buffer_end, size); + return readBuffer(obj->in_buffer,&obj->in_buffer_read,obj->in_buffer_write,obj->in_buffer_end, buf, size); + //printf("read end %p %p %p %p %d\n",obj->in_buffer,obj->in_buffer_read,obj->in_buffer_write,obj->in_buffer_end, size); +} + +int portaudio_card_write(PortAudioCard *obj,char *buf,int size) +{ + //printf("writeBuffer begin %p %p %p %p %d\n",obj->out_buffer,obj->out_buffer_read,obj->out_buffer_write,obj->out_buffer_end, size); + return writeBuffer(obj->out_buffer,obj->out_buffer_read,&obj->out_buffer_write,obj->out_buffer_end, buf, size); + //printf("writeBuffer end %p %p %p %p %d\n",obj->out_buffer,obj->out_buffer_read,obj->out_buffer_write,obj->out_buffer_end, size); +} + +void portaudio_card_set_level(PortAudioCard *obj,gint way,gint a) +{ +} + +gint portaudio_card_get_level(PortAudioCard *obj,gint way) +{ + return 0; +} + +void portaudio_card_set_source(PortAudioCard *obj,int source) +{ +} + +MSFilter *portaudio_card_create_read_filter(PortAudioCard *card) +{ + MSFilter *f=ms_oss_read_new(); + ms_oss_read_set_device(MS_OSS_READ(f),SND_CARD(card)->index); + return f; +} + +MSFilter *portaudio_card_create_write_filter(PortAudioCard *card) +{ + MSFilter *f=ms_oss_write_new(); + ms_oss_write_set_device(MS_OSS_WRITE(f),SND_CARD(card)->index); + return f; +} + + +SndCard* portaudio_card_new() +{ + // Basic stuff + PortAudioCard* obj= g_new0(PortAudioCard,1); + SndCard* base= SND_CARD(obj); + snd_card_init(base); + base->card_name=g_strdup_printf("PortAudio Card"); + base->_probe=(SndCardOpenFunc)portaudio_card_probe; + base->_open_r=(SndCardOpenFunc)portaudio_card_open_r; + base->_open_w=(SndCardOpenFunc)portaudio_card_open_w; + base->_can_read=(SndCardPollFunc)portaudio_card_can_read; + base->_read=(SndCardIOFunc)portaudio_card_read; + base->_write=(SndCardIOFunc)portaudio_card_write; + base->_close_r=(SndCardCloseFunc)portaudio_card_close_r; + base->_close_w=(SndCardCloseFunc)portaudio_card_close_w; + base->_set_rec_source=(SndCardMixerSetRecSourceFunc)portaudio_card_set_source; + base->_set_level=(SndCardMixerSetLevelFunc)portaudio_card_set_level; + base->_get_level=(SndCardMixerGetLevelFunc)portaudio_card_get_level; + base->_destroy=(SndCardDestroyFunc)portaudio_card_destroy; + base->_create_read_filter=(SndCardCreateFilterFunc)portaudio_card_create_read_filter; + base->_create_write_filter=(SndCardCreateFilterFunc)portaudio_card_create_write_filter; + + // Initialize stream + obj->stream = NULL; + + // Initialize buffers + obj->out_buffer = (char*) malloc(sizeof(char)*BUFFER_SIZE); + obj->out_buffer_read = obj->out_buffer_write = obj->out_buffer; + obj->out_buffer_end = obj->out_buffer + BUFFER_SIZE; + obj->in_buffer = (char*) malloc(sizeof(char)*BUFFER_SIZE); + obj->in_buffer_read = obj->in_buffer_write = obj->in_buffer; + obj->in_buffer_end = obj->in_buffer + BUFFER_SIZE; + + return base; +} + +gint portaudio_card_manager_init(SndCardManager *manager, gint tabindex) +{ + // Initialize portaudio lib + int err = Pa_Initialize(); + if (err != paNoError) { + fprintf(stderr,"Error initializing PortAudio: %s\n",Pa_GetErrorText(err)); + return 0; + } + + // Create new card + manager->cards[0]=portaudio_card_new(); + manager->cards[0]->index=0; + + return 1; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/portaudiocard.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/portaudiocard.h new file mode 100644 index 00000000..cbaa7982 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/portaudiocard.h @@ -0,0 +1,35 @@ +/* + Copyright (C) 2005 Remko Troncon + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +/* An implementation of SndCard : the OssCard */ + +#ifndef PORTAUDIO_CARD_H +#define PORTAUDIO_CARD_H + +#include "sndcard.h" + +typedef struct _PortAudioCard +{ + SndCard parent; + PortAudioStream* stream; + char *out_buffer, *out_buffer_read, *out_buffer_write, *out_buffer_end; + char *in_buffer, *in_buffer_read, *in_buffer_write, *in_buffer_end; +} PortAudioCard; + +gint portaudio_card_manager_init(SndCardManager *manager, gint tabindex); + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/sndcard.c b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/sndcard.c new file mode 100644 index 00000000..3a0f5d9a --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/sndcard.c @@ -0,0 +1,209 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "sndcard.h" +#include "msfilter.h" + +void snd_card_init(SndCard *obj) +{ + memset(obj,0,sizeof(SndCard)); +} + +void snd_card_uninit(SndCard *obj) +{ + if (obj->card_name!=NULL) g_free(obj->card_name); +} + +const gchar *snd_card_get_identifier(SndCard *obj) +{ + return obj->card_name; +} + +int snd_card_open_r(SndCard *obj, int bits, int stereo, int rate) +{ + g_return_val_if_fail(obj->_open_r!=NULL,-1); + g_message("Opening sound card [%s] in capture mode with stereo=%i,rate=%i,bits=%i",obj->card_name,stereo,rate,bits); + return obj->_open_r(obj,bits,stereo,rate); +} +int snd_card_open_w(SndCard *obj, int bits, int stereo, int rate) +{ + g_return_val_if_fail(obj->_open_w!=NULL,-1); + g_message("Opening sound card [%s] in playback mode with stereo=%i,rate=%i,bits=%i",obj->card_name,stereo,rate,bits); + return obj->_open_w(obj,bits,stereo,rate); +} + +gboolean snd_card_can_read(SndCard *obj){ + g_return_val_if_fail(obj->_can_read!=NULL,-1); + return obj->_can_read(obj); +} + +void snd_card_set_blocking_mode(SndCard *obj,gboolean yesno){ + g_return_if_fail(obj->_set_blocking_mode!=NULL); + obj->_set_blocking_mode(obj,yesno); +} + +int snd_card_read(SndCard *obj,char *buffer,int size) +{ + g_return_val_if_fail(obj->_read!=NULL,-1); + return obj->_read(obj,buffer,size); +} +int snd_card_write(SndCard *obj,char *buffer,int size) +{ + g_return_val_if_fail(obj->_write!=NULL,-1); + return obj->_write(obj,buffer,size); +} + +int snd_card_get_bsize(SndCard *obj) +{ + if (obj->flags & SND_CARD_FLAGS_OPENED){ + return obj->bsize; + } + return -1; +} + +void snd_card_close_r(SndCard *obj) +{ + g_return_if_fail(obj->_close_r!=NULL); + g_message("Closing reading channel of soundcard."); + obj->_close_r(obj); +} + +void snd_card_close_w(SndCard *obj) +{ + g_return_if_fail(obj->_close_w!=NULL); + g_message("Closing writing channel of soundcard."); + obj->_close_w(obj); +} + +gint snd_card_probe(SndCard *obj,int bits, int stereo, int rate) +{ + g_return_val_if_fail(obj->_probe!=NULL,-1); + return obj->_probe(obj,bits,stereo,rate); +} + +void snd_card_set_rec_source(SndCard *obj, int source) +{ + g_return_if_fail(obj->_set_rec_source!=NULL); + obj->_set_rec_source(obj,source); +} + +void snd_card_set_level(SndCard *obj, int way, int level) +{ + g_return_if_fail(obj->_set_level!=NULL); + obj->_set_level(obj,way,level); +} + +gint snd_card_get_level(SndCard *obj,int way) +{ + g_return_val_if_fail(obj->_get_level!=NULL,-1); + return obj->_get_level(obj,way); +} + + +MSFilter * snd_card_create_read_filter(SndCard *obj) +{ + g_return_val_if_fail(obj->_create_read_filter!=NULL,NULL); + return obj->_create_read_filter(obj); +} +MSFilter * snd_card_create_write_filter(SndCard *obj) +{ + g_return_val_if_fail(obj->_create_write_filter!=NULL,NULL); + return obj->_create_write_filter(obj); +} + + +#ifdef HAVE_SYS_AUDIO_H +gint sys_audio_manager_init(SndCardManager *manager, gint index) +{ + /* this is a quick shortcut, as multiple soundcards on HPUX does not happen + very often... */ + manager->cards[index]=hpux_snd_card_new("/dev/audio","/dev/audio"); + return 1; +} + +#endif + +#include "osscard.h" +#include "alsacard.h" +#include "jackcard.h" + +void snd_card_manager_init(SndCardManager *manager) +{ + gint index=0; + gint tmp=0; + memset(manager,0,sizeof(SndCardManager)); + #ifdef HAVE_SYS_SOUNDCARD_H + tmp=oss_card_manager_init(manager,index); + index+=tmp; + if (index>=MAX_SND_CARDS) return; + #endif + #ifdef __ALSA_ENABLED__ + tmp=alsa_card_manager_init(manager,index); + index+=tmp; + if (index>=MAX_SND_CARDS) return; + #endif + #ifdef __JACK_ENABLED__ + tmp=jack_card_manager_init(manager,index); + index+=tmp; + if (index>=MAX_SND_CARDS) return; + #endif + #ifdef HAVE_PORTAUDIO + tmp=portaudio_card_manager_init(manager,index); + index+=tmp; + if (index>=MAX_SND_CARDS) return; + #endif + #ifdef HAVE_SYS_AUDIO_H + tmp=sys_audio_manager_init(manager,index); + index+=tmp; + #endif +} + + + + + +SndCard * snd_card_manager_get_card(SndCardManager *manager,int index) +{ + g_return_val_if_fail(index>=0,NULL); + g_return_val_if_fail(indexMAX_SND_CARDS) return NULL; + return manager->cards[index]; +} + +SndCard * snd_card_manager_get_card_with_string(SndCardManager *manager,const char *cardname,int *index) +{ + int i; + for (i=0;icards[i]==NULL) continue; + card_name=manager->cards[i]->card_name; + if (card_name==NULL) continue; + if (strcmp(card_name,cardname)==0){ + *index=i; + return manager->cards[i]; + } + } + g_warning("No card %s found.",cardname); + return NULL; +} + +SndCardManager _snd_card_manager; +SndCardManager *snd_card_manager=&_snd_card_manager; diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/sndcard.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/sndcard.h new file mode 100644 index 00000000..d84757fd --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/sndcard.h @@ -0,0 +1,143 @@ +/* + The mediastreamer library aims at providing modular media processing and I/O + for linphone, but also for any telephony application. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + + +#ifndef SNDCARD_H +#define SNDCARD_H + +#undef PACKAGE +#undef VERSION +#include +#undef PACKAGE +#undef VERSION + +#ifdef HAVE_GLIB +#include +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* the base class for all soundcards: SndCard */ +struct _SndCard; + +typedef int (*SndCardOpenFunc)(struct _SndCard*,int, int, int); +typedef void (*SndCardSetBlockingModeFunc)(struct _SndCard*, gboolean ); +typedef void (*SndCardCloseFunc)(struct _SndCard*); +typedef gint (*SndCardIOFunc)(struct _SndCard*,char *,int); +typedef void (*SndCardDestroyFunc)(struct _SndCard*); +typedef gboolean (*SndCardPollFunc)(struct _SndCard*); +typedef gint (*SndCardMixerGetLevelFunc)(struct _SndCard*,gint); +typedef void (*SndCardMixerSetRecSourceFunc)(struct _SndCard*,gint); +typedef void (*SndCardMixerSetLevelFunc)(struct _SndCard*,gint ,gint); +typedef struct _MSFilter * (*SndCardCreateFilterFunc)(struct _SndCard *); + +struct _SndCard +{ + gchar *card_name; /* SB16 PCI for example */ + gint index; + gint bsize; + gint rate; + gint stereo; + gint bits; + gint flags; +#define SND_CARD_FLAGS_OPENED 1 + SndCardOpenFunc _probe; + SndCardOpenFunc _open_r; + SndCardOpenFunc _open_w; + SndCardSetBlockingModeFunc _set_blocking_mode; + SndCardPollFunc _can_read; + SndCardIOFunc _read; + SndCardIOFunc _write; + SndCardCloseFunc _close_r; + SndCardCloseFunc _close_w; + SndCardMixerGetLevelFunc _get_level; + SndCardMixerSetLevelFunc _set_level; + SndCardMixerSetRecSourceFunc _set_rec_source; + SndCardCreateFilterFunc _create_read_filter; + SndCardCreateFilterFunc _create_write_filter; + SndCardDestroyFunc _destroy; +}; + + +typedef struct _SndCard SndCard; + +void snd_card_init(SndCard *obj); +void snd_card_uninit(SndCard *obj); +gint snd_card_probe(SndCard *obj, int bits, int stereo, int rate); +int snd_card_open_r(SndCard *obj, int bits, int stereo, int rate); +int snd_card_open_w(SndCard *obj, int bits, int stereo, int rate); +int snd_card_get_bsize(SndCard *obj); +gboolean snd_card_can_read(SndCard *obj); +int snd_card_read(SndCard *obj,char *buffer,int size); +int snd_card_write(SndCard *obj,char *buffer,int size); +void snd_card_set_blocking_mode(SndCard *obj,gboolean yesno); +void snd_card_close_r(SndCard *obj); +void snd_card_close_w(SndCard *obj); + +void snd_card_set_rec_source(SndCard *obj, int source); /* source='l' or 'm'*/ +void snd_card_set_level(SndCard *obj, int way, int level); +gint snd_card_get_level(SndCard *obj,int way); + +const gchar *snd_card_get_identifier(SndCard *obj); + +struct _MSFilter * snd_card_create_read_filter(SndCard *sndcard); +struct _MSFilter * snd_card_create_write_filter(SndCard *sndcard); + + +#define SND_CARD_LEVEL_GENERAL 1 +#define SND_CARD_LEVEL_INPUT 2 +#define SND_CARD_LEVEL_OUTPUT 3 + + +int snd_card_destroy(SndCard *obj); + +#define SND_CARD(obj) ((SndCard*)(obj)) + + + + +/* SndCardManager */ + +#define MAX_SND_CARDS 20 + + +struct _SndCardManager +{ + SndCard *cards[MAX_SND_CARDS]; +}; + +typedef struct _SndCardManager SndCardManager; + +void snd_card_manager_init(SndCardManager *manager); +SndCard * snd_card_manager_get_card(SndCardManager *manager,int index); +SndCard * snd_card_manager_get_card_with_string(SndCardManager *manager,const char *cardname,int *index); + +extern SndCardManager *snd_card_manager; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/waveheader.h b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/waveheader.h new file mode 100644 index 00000000..6768d8f8 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/third_party/mediastreamer/waveheader.h @@ -0,0 +1,111 @@ +/* +linphone +Copyright (C) 2000 Simon MORLAT (simon.morlat@free.fr) + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +/* the following code was taken from a free software utility that I don't remember the name. */ +/* sorry */ + + + +#include +#ifndef waveheader_h +#define waveheader_h + +typedef struct uint16scheme +{ + unsigned char lo_byte; + unsigned char hi_byte; +} uint16scheme_t; + +typedef struct uint32scheme +{ + guint16 lo_int; + guint16 hi_int; +} uint32scheme_t; + + +/* all integer in wav header must be read in least endian order */ +inline guint16 _readuint16(guint16 a) +{ + guint16 res; + uint16scheme_t *tmp1=(uint16scheme_t*)&a; + + ((uint16scheme_t *)(&res))->lo_byte=tmp1->hi_byte; + ((uint16scheme_t *)(&res))->hi_byte=tmp1->lo_byte; + return res; +} + +inline guint32 _readuint32(guint32 a) +{ + guint32 res; + uint32scheme_t *tmp1=(uint32scheme_t*)&a; + + ((uint32scheme_t *)(&res))->lo_int=_readuint16(tmp1->hi_int); + ((uint32scheme_t *)(&res))->hi_int=_readuint16(tmp1->lo_int); + return res; +} + +#ifdef WORDS_BIGENDIAN +#define le_uint32(a) (_readuint32((a))) +#define le_uint16(a) (_readuint16((a))) +#define le_int16(a) ( (gint16) _readuint16((guint16)((a))) ) +#else +#define le_uint32(a) (a) +#define le_uint16(a) (a) +#define le_int16(a) (a) +#endif + +typedef struct _riff_t { + char riff[4] ; /* "RIFF" (ASCII characters) */ + guint32 len ; /* Length of package (binary, little endian) */ + char wave[4] ; /* "WAVE" (ASCII characters) */ +} riff_t; + +/* The FORMAT chunk */ + +typedef struct _format_t { + char fmt[4] ; /* "fmt_" (ASCII characters) */ + guint32 len ; /* length of FORMAT chunk (always 0x10) */ + guint16 que ; /* Always 0x01 */ + guint16 channel ; /* Channel numbers (0x01 = mono, 0x02 = stereo) */ + guint32 rate ; /* Sample rate (binary, in Hz) */ + guint32 bps ; /* Bytes Per Second */ + guint16 bpsmpl ; /* bytes per sample: 1 = 8 bit Mono, + 2 = 8 bit Stereo/16 bit Mono, + 4 = 16 bit Stereo */ + guint16 bitpspl ; /* bits per sample */ +} format_t; + +/* The DATA chunk */ + +typedef struct _data_t { + char data[4] ; /* "data" (ASCII characters) */ + int len ; /* length of data */ +} data_t; + +typedef struct _wave_header_t +{ + riff_t riff_chunk; + format_t format_chunk; + data_t data_chunk; +} wave_header_t; + +#define wave_header_get_rate(header) le_uint32((header)->format_chunk.rate) +#define wave_header_get_channel(header) le_uint16((header)->format_chunk.channel) + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/Makefile.am new file mode 100644 index 00000000..1e7abcfd --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/Makefile.am @@ -0,0 +1,18 @@ +libcricketxmllite_la_SOURCES = qname.cc \ + xmlbuilder.cc \ + xmlconstants.cc \ + xmlelement.cc \ + xmlnsstack.cc \ + xmlparser.cc \ + xmlprinter.cc + +noinst_HEADERS = qname.h \ + xmlbuilder.h \ + xmlconstants.h \ + xmlelement.h \ + xmlnsstack.h \ + xmlparser.h \ + xmlprinter.h +AM_CPPFLAGS = -DPOSIX -I$(srcdir)/../.. + +noinst_LTLIBRARIES = libcricketxmllite.la diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/qname.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/qname.cc new file mode 100644 index 00000000..626cfa96 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/qname.cc @@ -0,0 +1,167 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include +#include "talk/base/common.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/qname.h" +#include "talk/xmllite/xmlconstants.h" + +//#define new TRACK_NEW + +namespace buzz { + +static int QName_Hash(const std::string & ns, const char * local) { + int result = ns.size() * 101; + while (*local) { + result *= 19; + result += *local; + local += 1; + } + return result; +} + +static const int bits = 9; +static QName::Data * get_qname_table() { + static QName::Data qname_table[1 << bits]; + return qname_table; +} + +static QName::Data * +AllocateOrFind(const std::string & ns, const char * local) { + int index = QName_Hash(ns, local); + int increment = index >> (bits - 1) | 1; + QName::Data * qname_table = get_qname_table(); + for (;;) { + index &= ((1 << bits) - 1); + if (!qname_table[index].Occupied()) { + return new QName::Data(ns, local); + } + if (qname_table[index].localPart_ == local && + qname_table[index].namespace_ == ns) { + qname_table[index].AddRef(); + return qname_table + index; + } + index += increment; + } +} + +static QName::Data * +Add(const std::string & ns, const char * local) { + int index = QName_Hash(ns, local); + int increment = index >> (bits - 1) | 1; + QName::Data * qname_table = get_qname_table(); + for (;;) { + index &= ((1 << bits) - 1); + if (!qname_table[index].Occupied()) { + qname_table[index].namespace_ = ns; + qname_table[index].localPart_ = local; + qname_table[index].AddRef(); // AddRef twice so it's never deleted + qname_table[index].AddRef(); + return qname_table + index; + } + if (qname_table[index].localPart_ == local && + qname_table[index].namespace_ == ns) { + qname_table[index].AddRef(); + return qname_table + index; + } + index += increment; + } +} + +QName::~QName() { + data_->Release(); +} + +QName::QName() : data_(QN_EMPTY.data_) { + data_->AddRef(); +} + +QName::QName(bool add, const std::string & ns, const char * local) : + data_(add ? Add(ns, local) : AllocateOrFind(ns, local)) {} + +QName::QName(bool add, const std::string & ns, const std::string & local) : + data_(add ? Add(ns, local.c_str()) : AllocateOrFind(ns, local.c_str())) {} + +QName::QName(const std::string & ns, const char * local) : + data_(AllocateOrFind(ns, local)) {} + +static std::string +QName_LocalPart(const std::string & name) { + size_t i = name.rfind(':'); + if (i == std::string::npos) + return name; + return name.substr(i + 1); +} + +static std::string +QName_Namespace(const std::string & name) { + size_t i = name.rfind(':'); + if (i == std::string::npos) + return STR_EMPTY; + return name.substr(0, i); +} + +QName::QName(const std::string & mergedOrLocal) : + data_(AllocateOrFind(QName_Namespace(mergedOrLocal), + QName_LocalPart(mergedOrLocal).c_str())) {} + +std::string +QName::Merged() const { + if (data_->namespace_ == STR_EMPTY) + return data_->localPart_; + + std::string result(data_->namespace_); + result.reserve(result.length() + 1 + data_->localPart_.length()); + result += ':'; + result += data_->localPart_; + return result; +} + +bool +QName::operator==(const QName & other) const { + return other.data_ == data_ || + data_->localPart_ == other.data_->localPart_ && + data_->namespace_ == other.data_->namespace_; +} + +int +QName::Compare(const QName & other) const { + if (data_ == other.data_) + return 0; + + int result = data_->localPart_.compare(other.data_->localPart_); + if (result) + return result; + + return data_->namespace_.compare(other.data_->namespace_); +} + +} + + + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/qname.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/qname.h new file mode 100644 index 00000000..b1bcec61 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/qname.h @@ -0,0 +1,87 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _qname_h_ +#define _qname_h_ + +#include + +namespace buzz { + + +class QName +{ +public: + explicit QName(); + QName(const QName & qname) : data_(qname.data_) { data_->AddRef(); } + explicit QName(bool add, const std::string & ns, const char * local); + explicit QName(bool add, const std::string & ns, const std::string & local); + explicit QName(const std::string & ns, const char * local); + explicit QName(const std::string & mergedOrLocal); + QName & operator=(const QName & qn) { + qn.data_->AddRef(); + data_->Release(); + data_ = qn.data_; + return *this; + } + ~QName(); + + const std::string & Namespace() const { return data_->namespace_; } + const std::string & LocalPart() const { return data_->localPart_; } + std::string Merged() const; + int Compare(const QName & other) const; + bool operator==(const QName & other) const; + bool operator!=(const QName & other) const { return !operator==(other); } + bool operator<(const QName & other) const { return Compare(other) < 0; } + + class Data { + public: + Data(const std::string & ns, const std::string & local) : + refcount_(1), + namespace_(ns), + localPart_(local) {} + + Data() : refcount_(0) {} + + std::string namespace_; + std::string localPart_; + void AddRef() { refcount_++; } + void Release() { if (!--refcount_) { delete this; } } + bool Occupied() { return !!refcount_; } + + private: + int refcount_; + }; + +private: + Data * data_; +}; + + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlbuilder.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlbuilder.cc new file mode 100644 index 00000000..313c4013 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlbuilder.cc @@ -0,0 +1,151 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/base/stl_decl.h" +#include +#include +#include +#include "talk/base/common.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/xmlbuilder.h" + +#define new TRACK_NEW + +namespace buzz { + +XmlBuilder::XmlBuilder() : + pelCurrent_(NULL), + pelRoot_(NULL), + pvParents_(new std::vector()) { +} + +void +XmlBuilder::Reset() { + pelRoot_.reset(); + pelCurrent_ = NULL; + pvParents_->clear(); +} + +XmlElement * +XmlBuilder::BuildElement(XmlParseContext * pctx, + const char * name, const char ** atts) { + QName tagName(pctx->ResolveQName(name, false)); + if (tagName == QN_EMPTY) + return NULL; + + XmlElement * pelNew = new XmlElement(tagName); + + if (!*atts) + return pelNew; + + std::set seenNonlocalAtts; + + while (*atts) { + QName attName(pctx->ResolveQName(*atts, true)); + if (attName == QN_EMPTY) { + delete pelNew; + return NULL; + } + + // verify that namespaced names are unique + if (!attName.Namespace().empty()) { + if (seenNonlocalAtts.count(attName)) { + delete pelNew; + return NULL; + } + seenNonlocalAtts.insert(attName); + } + + pelNew->AddAttr(attName, std::string(*(atts + 1))); + atts += 2; + } + + return pelNew; +} + +void +XmlBuilder::StartElement(XmlParseContext * pctx, + const char * name, const char ** atts) { + XmlElement * pelNew = BuildElement(pctx, name, atts); + if (pelNew == NULL) { + pctx->RaiseError(XML_ERROR_SYNTAX); + return; + } + + if (!pelCurrent_) { + pelCurrent_ = pelNew; + pelRoot_.reset(pelNew); + pvParents_->push_back(NULL); + } else { + pelCurrent_->AddElement(pelNew); + pvParents_->push_back(pelCurrent_); + pelCurrent_ = pelNew; + } +} + +void +XmlBuilder::EndElement(XmlParseContext * pctx, const char * name) { + UNUSED(pctx); + UNUSED(name); + pelCurrent_ = pvParents_->back(); + pvParents_->pop_back(); +} + +void +XmlBuilder::CharacterData(XmlParseContext * pctx, + const char * text, int len) { + UNUSED(pctx); + if (pelCurrent_) { + pelCurrent_->AddParsedText(text, len); + } +} + +void +XmlBuilder::Error(XmlParseContext * pctx, XML_Error err) { + UNUSED(pctx); + UNUSED(err); + pelRoot_.reset(NULL); + pelCurrent_ = NULL; + pvParents_->clear(); +} + +XmlElement * +XmlBuilder::CreateElement() { + return pelRoot_.release(); +} + +XmlElement * +XmlBuilder::BuiltElement() { + return pelRoot_.get(); +} + +XmlBuilder::~XmlBuilder() { +} + + + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlbuilder.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlbuilder.h new file mode 100644 index 00000000..b5b1be59 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlbuilder.h @@ -0,0 +1,79 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _xmlbuilder_h_ +#define _xmlbuilder_h_ + +#include +#include "talk/base/scoped_ptr.h" +#include "talk/base/stl_decl.h" +#include "talk/xmllite/xmlparser.h" + +#ifdef OSX +#include "talk/third_party/expat/expat.h" +#else +#include +#endif + +namespace buzz { + +class XmlElement; +class XmlParseContext; + + +class XmlBuilder : public XmlParseHandler { +public: + XmlBuilder(); + + static XmlElement * BuildElement(XmlParseContext * pctx, + const char * name, const char ** atts); + virtual void StartElement(XmlParseContext * pctx, + const char * name, const char ** atts); + virtual void EndElement(XmlParseContext * pctx, const char * name); + virtual void CharacterData(XmlParseContext * pctx, + const char * text, int len); + virtual void Error(XmlParseContext * pctx, XML_Error); + virtual ~XmlBuilder(); + + void Reset(); + + // Take ownership of the built element; second call returns NULL + XmlElement * CreateElement(); + + // Peek at the built element without taking ownership + XmlElement * BuiltElement(); + +private: + XmlElement * pelCurrent_; + scoped_ptr pelRoot_; + scoped_ptr > > + pvParents_; +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlconstants.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlconstants.cc new file mode 100644 index 00000000..503f832f --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlconstants.cc @@ -0,0 +1,65 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "xmlconstants.h" + +using namespace buzz; + +const std::string & XmlConstants::str_empty() { + static const std::string str_empty_; + return str_empty_; +} + +const std::string & XmlConstants::ns_xml() { + static const std::string ns_xml_("http://www.w3.org/XML/1998/namespace"); + return ns_xml_; +} + +const std::string & XmlConstants::ns_xmlns() { + static const std::string ns_xmlns_("http://www.w3.org/2000/xmlns/"); + return ns_xmlns_; +} + +const std::string & XmlConstants::str_xmlns() { + static const std::string str_xmlns_("xmlns"); + return str_xmlns_; +} + +const std::string & XmlConstants::str_xml() { + static const std::string str_xml_("xml"); + return str_xml_; +} + +const std::string & XmlConstants::str_version() { + static const std::string str_version_("version"); + return str_version_; +} + +const std::string & XmlConstants::str_encoding() { + static const std::string str_encoding_("encoding"); + return str_encoding_; +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlconstants.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlconstants.h new file mode 100644 index 00000000..8514d6f4 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlconstants.h @@ -0,0 +1,61 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +// Because global constant initialization order is undefined +// globals cannot depend on other objects to be instantiated. +// This class creates string objects within static methods +// such that globals may refer to these constants by the +// accessor function and they are guaranteed to be initialized. + +#ifndef TALK_XMLLITE_CONSTANTS_H_ +#define TALK_XMLLITE_CONSTANTS_H_ + +#include + +#define STR_EMPTY XmlConstants::str_empty() +#define NS_XML XmlConstants::ns_xml() +#define NS_XMLNS XmlConstants::ns_xmlns() +#define STR_XMLNS XmlConstants::str_xmlns() +#define STR_XML XmlConstants::str_xml() +#define STR_VERSION XmlConstants::str_version() +#define STR_ENCODING XmlConstants::str_encoding() +namespace buzz { + +class XmlConstants { + public: + static const std::string & str_empty(); + static const std::string & ns_xml(); + static const std::string & ns_xmlns(); + static const std::string & str_xmlns(); + static const std::string & str_xml(); + static const std::string & str_version(); + static const std::string & str_encoding(); +}; + +} + +#endif // TALK_XMLLITE_CONSTANTS_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlelement.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlelement.cc new file mode 100644 index 00000000..d3619a92 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlelement.cc @@ -0,0 +1,491 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include +#include +#include +#include + +#include "talk/base/common.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/qname.h" +#include "talk/xmllite/xmlparser.h" +#include "talk/xmllite/xmlbuilder.h" +#include "talk/xmllite/xmlprinter.h" +#include "talk/xmllite/xmlconstants.h" + +#define new TRACK_NEW + +namespace buzz { + +const QName QN_EMPTY(true, STR_EMPTY, STR_EMPTY); +const QName QN_XMLNS(true, STR_EMPTY, STR_XMLNS); + + +XmlChild::~XmlChild() { +} + +bool +XmlText::IsTextImpl() const { + return true; +} + +XmlElement * +XmlText::AsElementImpl() const { + return NULL; +} + +XmlText * +XmlText::AsTextImpl() const { + return const_cast(this); +} + +void +XmlText::SetText(const std::string & text) { + text_ = text; +} + +void +XmlText::AddParsedText(const char * buf, int len) { + text_.append(buf, len); +} + +void +XmlText::AddText(const std::string & text) { + text_ += text; +} + +XmlText::~XmlText() { +} + +XmlElement::XmlElement(const QName & name) : + name_(name), + pFirstAttr_(NULL), + pLastAttr_(NULL), + pFirstChild_(NULL), + pLastChild_(NULL) { +} + +XmlElement::XmlElement(const XmlElement & elt) : + XmlChild(), + name_(elt.name_), + pFirstAttr_(NULL), + pLastAttr_(NULL), + pFirstChild_(NULL), + pLastChild_(NULL) { + + // copy attributes + XmlAttr * pAttr; + XmlAttr ** ppLastAttr = &pFirstAttr_; + XmlAttr * newAttr = NULL; + for (pAttr = elt.pFirstAttr_; pAttr; pAttr = pAttr->NextAttr()) { + newAttr = new XmlAttr(*pAttr); + *ppLastAttr = newAttr; + ppLastAttr = &(newAttr->pNextAttr_); + } + pLastAttr_ = newAttr; + + // copy children + XmlChild * pChild; + XmlChild ** ppLast = &pFirstChild_; + XmlChild * newChild = NULL; + + for (pChild = elt.pFirstChild_; pChild; pChild = pChild->NextChild()) { + if (pChild->IsText()) { + newChild = new XmlText(*(pChild->AsText())); + } else { + newChild = new XmlElement(*(pChild->AsElement())); + } + *ppLast = newChild; + ppLast = &(newChild->pNextChild_); + } + pLastChild_ = newChild; + +} + +XmlElement::XmlElement(const QName & name, bool useDefaultNs) : + name_(name), + pFirstAttr_(useDefaultNs ? new XmlAttr(QN_XMLNS, name.Namespace()) : NULL), + pLastAttr_(pFirstAttr_), + pFirstChild_(NULL), + pLastChild_(NULL) { +} + +bool +XmlElement::IsTextImpl() const { + return false; +} + +XmlElement * +XmlElement::AsElementImpl() const { + return const_cast(this); +} + +XmlText * +XmlElement::AsTextImpl() const { + return NULL; +} + +const std::string & +XmlElement::BodyText() const { + if (pFirstChild_ && pFirstChild_->IsText() && pLastChild_ == pFirstChild_) { + return pFirstChild_->AsText()->Text(); + } + + return STR_EMPTY; +} + +void +XmlElement::SetBodyText(const std::string & text) { + if (text == STR_EMPTY) { + ClearChildren(); + } else if (pFirstChild_ == NULL) { + AddText(text); + } else if (pFirstChild_->IsText() && pLastChild_ == pFirstChild_) { + pFirstChild_->AsText()->SetText(text); + } else { + ClearChildren(); + AddText(text); + } +} + +const QName & +XmlElement::FirstElementName() const { + const XmlElement * element = FirstElement(); + if (element == NULL) + return QN_EMPTY; + return element->Name(); +} + +XmlAttr * +XmlElement::FirstAttr() { + return pFirstAttr_; +} + +const std::string & +XmlElement::Attr(const QName & name) const { + XmlAttr * pattr; + for (pattr = pFirstAttr_; pattr; pattr = pattr->pNextAttr_) { + if (pattr->name_ == name) + return pattr->value_; + } + return STR_EMPTY; +} + +bool +XmlElement::HasAttr(const QName & name) const { + XmlAttr * pattr; + for (pattr = pFirstAttr_; pattr; pattr = pattr->pNextAttr_) { + if (pattr->name_ == name) + return true; + } + return false; +} + +void +XmlElement::SetAttr(const QName & name, const std::string & value) { + XmlAttr * pattr; + for (pattr = pFirstAttr_; pattr; pattr = pattr->pNextAttr_) { + if (pattr->name_ == name) + break; + } + if (!pattr) { + pattr = new XmlAttr(name, value); + if (pLastAttr_) + pLastAttr_->pNextAttr_ = pattr; + else + pFirstAttr_ = pattr; + pLastAttr_ = pattr; + return; + } + pattr->value_ = value; +} + +void +XmlElement::ClearAttr(const QName & name) { + XmlAttr * pattr; + XmlAttr *pLastAttr = NULL; + for (pattr = pFirstAttr_; pattr; pattr = pattr->pNextAttr_) { + if (pattr->name_ == name) + break; + pLastAttr = pattr; + } + if (!pattr) + return; + if (!pLastAttr) + pFirstAttr_ = pattr->pNextAttr_; + else + pLastAttr->pNextAttr_ = pattr->pNextAttr_; + if (pLastAttr_ == pattr) + pLastAttr_ = pLastAttr; + delete pattr; +} + +XmlChild * +XmlElement::FirstChild() { + return pFirstChild_; +} + +XmlElement * +XmlElement::FirstElement() { + XmlChild * pChild; + for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) { + if (!pChild->IsText()) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement * +XmlElement::NextElement() { + XmlChild * pChild; + for (pChild = pNextChild_; pChild; pChild = pChild->pNextChild_) { + if (!pChild->IsText()) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement * +XmlElement::FirstWithNamespace(const std::string & ns) { + XmlChild * pChild; + for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) { + if (!pChild->IsText() && pChild->AsElement()->Name().Namespace() == ns) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement * +XmlElement::NextWithNamespace(const std::string & ns) { + XmlChild * pChild; + for (pChild = pNextChild_; pChild; pChild = pChild->pNextChild_) { + if (!pChild->IsText() && pChild->AsElement()->Name().Namespace() == ns) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement * +XmlElement::FirstNamed(const QName & name) { + XmlChild * pChild; + for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) { + if (!pChild->IsText() && pChild->AsElement()->Name() == name) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement * +XmlElement::NextNamed(const QName & name) { + XmlChild * pChild; + for (pChild = pNextChild_; pChild; pChild = pChild->pNextChild_) { + if (!pChild->IsText() && pChild->AsElement()->Name() == name) + return pChild->AsElement(); + } + return NULL; +} + +const std::string & +XmlElement::TextNamed(const QName & name) const { + XmlChild * pChild; + for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) { + if (!pChild->IsText() && pChild->AsElement()->Name() == name) + return pChild->AsElement()->BodyText(); + } + return STR_EMPTY; +} + +void +XmlElement::InsertChildAfter(XmlChild * pPredecessor, XmlChild * pNext) { + if (pPredecessor == NULL) { + pNext->pNextChild_ = pFirstChild_; + pFirstChild_ = pNext; + } + else { + pNext->pNextChild_ = pPredecessor->pNextChild_; + pPredecessor->pNextChild_ = pNext; + } +} + +void +XmlElement::RemoveChildAfter(XmlChild * pPredecessor) { + XmlChild * pNext; + + if (pPredecessor == NULL) { + pNext = pFirstChild_; + pFirstChild_ = pNext->pNextChild_; + } + else { + pNext = pPredecessor->pNextChild_; + pPredecessor->pNextChild_ = pNext->pNextChild_; + } + + if (pLastChild_ == pNext) + pLastChild_ = pPredecessor; + + delete pNext; +} + +void +XmlElement::AddAttr(const QName & name, const std::string & value) { + ASSERT(!HasAttr(name)); + + XmlAttr ** pprev = pLastAttr_ ? &(pLastAttr_->pNextAttr_) : &pFirstAttr_; + pLastAttr_ = (*pprev = new XmlAttr(name, value)); +} + +void +XmlElement::AddAttr(const QName & name, const std::string & value, + int depth) { + XmlElement * element = this; + while (depth--) { + element = element->pLastChild_->AsElement(); + } + element->AddAttr(name, value); +} + +void +XmlElement::AddParsedText(const char * cstr, int len) { + if (len == 0) + return; + + if (pLastChild_ && pLastChild_->IsText()) { + pLastChild_->AsText()->AddParsedText(cstr, len); + return; + } + XmlChild ** pprev = pLastChild_ ? &(pLastChild_->pNextChild_) : &pFirstChild_; + pLastChild_ = *pprev = new XmlText(cstr, len); +} + +void +XmlElement::AddText(const std::string & text) { + if (text == STR_EMPTY) + return; + + if (pLastChild_ && pLastChild_->IsText()) { + pLastChild_->AsText()->AddText(text); + return; + } + XmlChild ** pprev = pLastChild_ ? &(pLastChild_->pNextChild_) : &pFirstChild_; + pLastChild_ = *pprev = new XmlText(text); +} + +void +XmlElement::AddText(const std::string & text, int depth) { + // note: the first syntax is ambigious for msvc 6 + // XmlElement * pel(this); + XmlElement * element = this; + while (depth--) { + element = element->pLastChild_->AsElement(); + } + element->AddText(text); +} + +void +XmlElement::AddElement(XmlElement *pelChild) { + if (pelChild == NULL) + return; + + XmlChild ** pprev = pLastChild_ ? &(pLastChild_->pNextChild_) : &pFirstChild_; + pLastChild_ = *pprev = pelChild; + pelChild->pNextChild_ = NULL; +} + +void +XmlElement::AddElement(XmlElement *pelChild, int depth) { + XmlElement * element = this; + while (depth--) { + element = element->pLastChild_->AsElement(); + } + element->AddElement(pelChild); +} + +void +XmlElement::ClearNamedChildren(const QName & name) { + XmlChild * prev_child = NULL; + XmlChild * next_child; + XmlChild * child; + for (child = FirstChild(); child; child = next_child) { + next_child = child->NextChild(); + if (!child->IsText() && child->AsElement()->Name() == name) + { + RemoveChildAfter(prev_child); + continue; + } + prev_child = child; + } +} + +void +XmlElement::ClearChildren() { + XmlChild * pchild; + for (pchild = pFirstChild_; pchild; ) { + XmlChild * pToDelete = pchild; + pchild = pchild->pNextChild_; + delete pToDelete; + } + pFirstChild_ = pLastChild_ = NULL; +} + +std::string +XmlElement::Str() const { + std::stringstream ss; + Print(&ss, NULL, 0); + return ss.str(); +} + +XmlElement * +XmlElement::ForStr(const std::string & str) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, str); + return builder.CreateElement(); +} + +void +XmlElement::Print( + std::ostream * pout, std::string xmlns[], int xmlnsCount) const { + XmlPrinter::PrintXml(pout, this, xmlns, xmlnsCount); +} + +XmlElement::~XmlElement() { + XmlAttr * pattr; + for (pattr = pFirstAttr_; pattr; ) { + XmlAttr * pToDelete = pattr; + pattr = pattr->pNextAttr_; + delete pToDelete; + } + + XmlChild * pchild; + for (pchild = pFirstChild_; pchild; ) { + XmlChild * pToDelete = pchild; + pchild = pchild->pNextChild_; + delete pToDelete; + } +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlelement.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlelement.h new file mode 100644 index 00000000..06545d89 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlelement.h @@ -0,0 +1,231 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _xmlelement_h_ +#define _xmlelement_h_ + +#include +#include +#include "talk/base/scoped_ptr.h" +#include "talk/xmllite/qname.h" + +namespace buzz { + +extern const QName QN_EMPTY; +extern const QName QN_XMLNS; + + +class XmlChild; +class XmlText; +class XmlElement; +class XmlAttr; + +class XmlChild { +friend class XmlElement; + +public: + + XmlChild * NextChild() { return pNextChild_; } + const XmlChild * NextChild() const { return pNextChild_; } + + bool IsText() const { return IsTextImpl(); } + + XmlElement * AsElement() { return AsElementImpl(); } + const XmlElement * AsElement() const { return AsElementImpl(); } + + XmlText * AsText() { return AsTextImpl(); } + const XmlText * AsText() const { return AsTextImpl(); } + + +protected: + + XmlChild() : + pNextChild_(NULL) { + } + + virtual bool IsTextImpl() const = 0; + virtual XmlElement * AsElementImpl() const = 0; + virtual XmlText * AsTextImpl() const = 0; + + + virtual ~XmlChild(); + +private: + XmlChild(const XmlChild & noimpl); + + XmlChild * pNextChild_; + +}; + +class XmlText : public XmlChild { +public: + explicit XmlText(const std::string & text) : + XmlChild(), + text_(text) { + } + explicit XmlText(const XmlText & t) : + XmlChild(), + text_(t.text_) { + } + explicit XmlText(const char * cstr, size_t len) : + XmlChild(), + text_(cstr, len) { + } + virtual ~XmlText(); + + const std::string & Text() const { return text_; } + void SetText(const std::string & text); + void AddParsedText(const char * buf, int len); + void AddText(const std::string & text); + +protected: + virtual bool IsTextImpl() const; + virtual XmlElement * AsElementImpl() const; + virtual XmlText * AsTextImpl() const; + +private: + std::string text_; +}; + +class XmlAttr { +friend class XmlElement; + +public: + XmlAttr * NextAttr() const { return pNextAttr_; } + const QName & Name() const { return name_; } + const std::string & Value() const { return value_; } + +private: + explicit XmlAttr(const QName & name, const std::string & value) : + pNextAttr_(NULL), + name_(name), + value_(value) { + } + explicit XmlAttr(const XmlAttr & att) : + pNextAttr_(NULL), + name_(att.name_), + value_(att.value_) { + } + + XmlAttr * pNextAttr_; + QName name_; + std::string value_; +}; + +class XmlElement : public XmlChild { +public: + explicit XmlElement(const QName & name); + explicit XmlElement(const QName & name, bool useDefaultNs); + explicit XmlElement(const XmlElement & elt); + + virtual ~XmlElement(); + + const QName & Name() const { return name_; } + + const std::string & BodyText() const; + void SetBodyText(const std::string & text); + + const QName & FirstElementName() const; + + XmlAttr * FirstAttr(); + const XmlAttr * FirstAttr() const + { return const_cast(this)->FirstAttr(); } + + //! Attr will return STR_EMPTY if the attribute isn't there: + //! use HasAttr to test presence of an attribute. + const std::string & Attr(const QName & name) const; + bool HasAttr(const QName & name) const; + void SetAttr(const QName & name, const std::string & value); + void ClearAttr(const QName & name); + + XmlChild * FirstChild(); + const XmlChild * FirstChild() const + { return const_cast(this)->FirstChild(); } + + XmlElement * FirstElement(); + const XmlElement * FirstElement() const + { return const_cast(this)->FirstElement(); } + + XmlElement * NextElement(); + const XmlElement * NextElement() const + { return const_cast(this)->NextElement(); } + + XmlElement * FirstWithNamespace(const std::string & ns); + const XmlElement * FirstWithNamespace(const std::string & ns) const + { return const_cast(this)->FirstWithNamespace(ns); } + + XmlElement * NextWithNamespace(const std::string & ns); + const XmlElement * NextWithNamespace(const std::string & ns) const + { return const_cast(this)->NextWithNamespace(ns); } + + XmlElement * FirstNamed(const QName & name); + const XmlElement * FirstNamed(const QName & name) const + { return const_cast(this)->FirstNamed(name); } + + XmlElement * NextNamed(const QName & name); + const XmlElement * NextNamed(const QName & name) const + { return const_cast(this)->NextNamed(name); } + + const std::string & TextNamed(const QName & name) const; + + void InsertChildAfter(XmlChild * pPredecessor, XmlChild * pNewChild); + void RemoveChildAfter(XmlChild * pPredecessor); + + void AddParsedText(const char * buf, int len); + void AddText(const std::string & text); + void AddText(const std::string & text, int depth); + void AddElement(XmlElement * pelChild); + void AddElement(XmlElement * pelChild, int depth); + void AddAttr(const QName & name, const std::string & value); + void AddAttr(const QName & name, const std::string & value, int depth); + void ClearNamedChildren(const QName & name); + void ClearChildren(); + + static XmlElement * ForStr(const std::string & str); + std::string Str() const; + + void Print(std::ostream * pout, std::string xmlns[], int xmlnsCount) const; + +protected: + virtual bool IsTextImpl() const; + virtual XmlElement * AsElementImpl() const; + virtual XmlText * AsTextImpl() const; + +private: + QName name_; + XmlAttr * pFirstAttr_; + XmlAttr * pLastAttr_; + XmlChild * pFirstChild_; + XmlChild * pLastChild_; + +}; + + + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlnsstack.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlnsstack.cc new file mode 100644 index 00000000..4dcb6490 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlnsstack.cc @@ -0,0 +1,205 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/base/stl_decl.h" +#include +#include +#include +#include +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/xmlnsstack.h" +#include "talk/xmllite/xmlconstants.h" + +namespace buzz { + +XmlnsStack::XmlnsStack() : + pxmlnsStack_(new std::vector), + pxmlnsDepthStack_(new std::vector) { +} + +XmlnsStack::~XmlnsStack() {} + +void +XmlnsStack::PushFrame() { + pxmlnsDepthStack_->push_back(pxmlnsStack_->size()); +} + +void +XmlnsStack::PopFrame() { + size_t prev_size = pxmlnsDepthStack_->back(); + pxmlnsDepthStack_->pop_back(); + if (prev_size < pxmlnsStack_->size()) { + pxmlnsStack_->erase(pxmlnsStack_->begin() + prev_size, + pxmlnsStack_->end()); + } +} +const std::pair NS_NOT_FOUND(STR_EMPTY, false); +const std::pair EMPTY_NS_FOUND(STR_EMPTY, true); +const std::pair XMLNS_DEFINITION_FOUND(NS_XMLNS, true); + +const std::string * +XmlnsStack::NsForPrefix(const std::string & prefix) { + if (prefix.length() >= 3 && + (prefix[0] == 'x' || prefix[0] == 'X') && + (prefix[1] == 'm' || prefix[1] == 'M') && + (prefix[2] == 'l' || prefix[2] == 'L')) { + if (prefix == "xml") + return &(NS_XML); + if (prefix == "xmlns") + return &(NS_XMLNS); + return NULL; + } + + std::vector::iterator pos; + for (pos = pxmlnsStack_->end(); pos > pxmlnsStack_->begin(); ) { + pos -= 2; + if (*pos == prefix) + return &(*(pos + 1)); + } + + if (prefix == STR_EMPTY) + return &(STR_EMPTY); // default namespace + + return NULL; // none found +} + +bool +XmlnsStack::PrefixMatchesNs(const std::string & prefix, const std::string & ns) { + const std::string * match = NsForPrefix(prefix); + if (match == NULL) + return false; + return (*match == ns); +} + +std::pair +XmlnsStack::PrefixForNs(const std::string & ns, bool isattr) { + if (ns == NS_XML) + return std::make_pair(std::string("xml"), true); + if (ns == NS_XMLNS) + return std::make_pair(std::string("xmlns"), true); + if (isattr ? ns == STR_EMPTY : PrefixMatchesNs(STR_EMPTY, ns)) + return std::make_pair(STR_EMPTY, true); + + std::vector::iterator pos; + for (pos = pxmlnsStack_->end(); pos > pxmlnsStack_->begin(); ) { + pos -= 2; + if (*(pos + 1) == ns && + (!isattr || !pos->empty()) && PrefixMatchesNs(*pos, ns)) + return std::make_pair(*pos, true); + } + + return std::make_pair(STR_EMPTY, false); // none found +} + +std::string +XmlnsStack::FormatQName(const QName & name, bool isAttr) { + std::string prefix(PrefixForNs(name.Namespace(), isAttr).first); + if (prefix == STR_EMPTY) + return name.LocalPart(); + else + return prefix + ':' + name.LocalPart(); +} + +void +XmlnsStack::AddXmlns(const std::string & prefix, const std::string & ns) { + pxmlnsStack_->push_back(prefix); + pxmlnsStack_->push_back(ns); +} + +void +XmlnsStack::RemoveXmlns() { + pxmlnsStack_->pop_back(); + pxmlnsStack_->pop_back(); +} + +static bool IsAsciiLetter(char ch) { + return ((ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z')); +} + +static std::string AsciiLower(const std::string & s) { + std::string result(s); + size_t i; + for (i = 0; i < result.length(); i++) { + if (result[i] >= 'A' && result[i] <= 'Z') + result[i] += 'a' - 'A'; + } + return result; +} + +static std::string SuggestPrefix(const std::string & ns) { + size_t len = ns.length(); + size_t i = ns.find_last_of('.'); + if (i != std::string::npos && len - i <= 4 + 1) + len = i; // chop off ".html" or ".xsd" or ".?{0,4}" + size_t last = len; + while (last > 0) { + last -= 1; + if (IsAsciiLetter(ns[last])) { + size_t first = last; + last += 1; + while (first > 0) { + if (!IsAsciiLetter(ns[first - 1])) + break; + first -= 1; + } + if (last - first > 4) + last = first + 3; + std::string candidate(AsciiLower(ns.substr(first, last - first))); + if (candidate.find("xml") != 0) + return candidate; + break; + } + } + return "ns"; +} + + +std::pair +XmlnsStack::AddNewPrefix(const std::string & ns, bool isAttr) { + if (PrefixForNs(ns, isAttr).second) + return std::make_pair(STR_EMPTY, false); + + std::string base(SuggestPrefix(ns)); + std::string result(base); + int i = 2; + while (NsForPrefix(result) != NULL) { + std::stringstream ss; + ss << base; + ss << (i++); + ss >> result; + } + AddXmlns(result, ns); + return std::make_pair(result, true); +} + +void XmlnsStack::Reset() { + pxmlnsStack_->clear(); + pxmlnsDepthStack_->clear(); +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlnsstack.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlnsstack.h new file mode 100644 index 00000000..299ec1ce --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlnsstack.h @@ -0,0 +1,62 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _xmlnsstack_h_ +#define _xmlnsstack_h_ + +#include +#include "talk/base/scoped_ptr.h" +#include "talk/base/stl_decl.h" +#include "talk/xmllite/qname.h" + +namespace buzz { + +class XmlnsStack { +public: + XmlnsStack(); + ~XmlnsStack(); + + void AddXmlns(const std::string & prefix, const std::string & ns); + void RemoveXmlns(); + void PushFrame(); + void PopFrame(); + void Reset(); + + const std::string * NsForPrefix(const std::string & prefix); + bool PrefixMatchesNs(const std::string & prefix, const std::string & ns); + std::pair PrefixForNs(const std::string & ns, bool isAttr); + std::pair AddNewPrefix(const std::string & ns, bool isAttr); + std::string FormatQName(const QName & name, bool isAttr); + +private: + + scoped_ptr > > pxmlnsStack_; + scoped_ptr > > pxmlnsDepthStack_; +}; +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlparser.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlparser.cc new file mode 100644 index 00000000..f2b56778 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlparser.cc @@ -0,0 +1,250 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/base/stl_decl.h" +#include +#include +#include +#include +#include "talk/xmllite/xmlelement.h" +#include "talk/base/common.h" +#include "talk/xmllite/xmlparser.h" +#include "talk/xmllite/xmlnsstack.h" +#include "talk/xmllite/xmlconstants.h" + +#include + +#define new TRACK_NEW + +namespace buzz { + + +static void +StartElementCallback(void * userData, const char *name, const char **atts) { + (static_cast(userData))->ExpatStartElement(name, atts); +} + +static void +EndElementCallback(void * userData, const char *name) { + (static_cast(userData))->ExpatEndElement(name); +} + +static void +CharacterDataCallback(void * userData, const char *text, int len) { + (static_cast(userData))->ExpatCharacterData(text, len); +} + +static void +XmlDeclCallback(void * userData, const char * ver, const char * enc, int st) { + (static_cast(userData))->ExpatXmlDecl(ver, enc, st); +} + +XmlParser::XmlParser(XmlParseHandler *pxph) : + context_(this), pxph_(pxph), sentError_(false) { + expat_ = XML_ParserCreate(NULL); + XML_SetUserData(expat_, this); + XML_SetElementHandler(expat_, StartElementCallback, EndElementCallback); + XML_SetCharacterDataHandler(expat_, CharacterDataCallback); + XML_SetXmlDeclHandler(expat_, XmlDeclCallback); +} + +void +XmlParser::Reset() { + if (!XML_ParserReset(expat_, NULL)) { + XML_ParserFree(expat_); + expat_ = XML_ParserCreate(NULL); + } + XML_SetUserData(expat_, this); + XML_SetElementHandler(expat_, StartElementCallback, EndElementCallback); + XML_SetCharacterDataHandler(expat_, CharacterDataCallback); + XML_SetXmlDeclHandler(expat_, XmlDeclCallback); + context_.Reset(); + sentError_ = false; +} + +static bool +XmlParser_StartsWithXmlns(const char *name) { + return name[0] == 'x' && + name[1] == 'm' && + name[2] == 'l' && + name[3] == 'n' && + name[4] == 's'; +} + +void +XmlParser::ExpatStartElement(const char *name, const char **atts) { + if (context_.RaisedError() != XML_ERROR_NONE) + return; + const char **att; + context_.StartElement(); + for (att = atts; *att; att += 2) { + if (XmlParser_StartsWithXmlns(*att)) { + if ((*att)[5] == '\0') { + context_.StartNamespace("", *(att + 1)); + } + else if ((*att)[5] == ':') { + if (**(att + 1) == '\0') { + // In XML 1.0 empty namespace illegal with prefix (not in 1.1) + context_.RaiseError(XML_ERROR_SYNTAX); + return; + } + context_.StartNamespace((*att) + 6, *(att + 1)); + } + } + } + pxph_->StartElement(&context_, name, atts); +} + +void +XmlParser::ExpatEndElement(const char *name) { + if (context_.RaisedError() != XML_ERROR_NONE) + return; + context_.EndElement(); + pxph_->EndElement(&context_, name); +} + +void +XmlParser::ExpatCharacterData(const char *text, int len) { + if (context_.RaisedError() != XML_ERROR_NONE) + return; + pxph_->CharacterData(&context_, text, len); +} + +void +XmlParser::ExpatXmlDecl(const char * ver, const char * enc, int standalone) { + if (context_.RaisedError() != XML_ERROR_NONE) + return; + + if (ver && std::string("1.0") != ver) { + context_.RaiseError(XML_ERROR_SYNTAX); + return; + } + + if (standalone == 0) { + context_.RaiseError(XML_ERROR_SYNTAX); + return; + } + + if (enc && !((enc[0] == 'U' || enc[0] == 'u') && + (enc[1] == 'T' || enc[1] == 't') && + (enc[2] == 'F' || enc[2] == 'f') && + enc[3] == '-' && enc[4] =='8')) { + context_.RaiseError(XML_ERROR_INCORRECT_ENCODING); + return; + } + +} + +bool +XmlParser::Parse(const char *data, size_t len, bool isFinal) { + if (sentError_) + return false; + + if (XML_Parse(expat_, data, static_cast(len), isFinal) != XML_STATUS_OK) + context_.RaiseError(XML_GetErrorCode(expat_)); + + if (context_.RaisedError() != XML_ERROR_NONE) { + sentError_ = true; + pxph_->Error(&context_, context_.RaisedError()); + return false; + } + + return true; +} + +XmlParser::~XmlParser() { + XML_ParserFree(expat_); +} + +void +XmlParser::ParseXml(XmlParseHandler *pxph, std::string text) { + XmlParser parser(pxph); + parser.Parse(text.c_str(), text.length(), true); +} + +XmlParser::ParseContext::ParseContext(XmlParser *parser) : + parser_(parser), + xmlnsstack_(), + raised_(XML_ERROR_NONE) { +} + +void +XmlParser::ParseContext::StartNamespace(const char *prefix, const char *ns) { + xmlnsstack_.AddXmlns( + *prefix ? std::string(prefix) : STR_EMPTY, +// ns == NS_CLIENT ? NS_CLIENT : +// ns == NS_ROSTER ? NS_ROSTER : +// ns == NS_GR ? NS_GR : + std::string(ns)); +} + +void +XmlParser::ParseContext::StartElement() { + xmlnsstack_.PushFrame(); +} + +void +XmlParser::ParseContext::EndElement() { + xmlnsstack_.PopFrame(); +} + +QName +XmlParser::ParseContext::ResolveQName(const char *qname, bool isAttr) { + const char *c; + for (c = qname; *c; ++c) { + if (*c == ':') { + const std::string * result; + result = xmlnsstack_.NsForPrefix(std::string(qname, c - qname)); + if (result == NULL) + return QN_EMPTY; + const char * localname = c + 1; + return QName(*result, localname); + } + } + if (isAttr) { + return QName(STR_EMPTY, qname); + } + + const std::string * result; + result = xmlnsstack_.NsForPrefix(STR_EMPTY); + if (result == NULL) + return QN_EMPTY; + + return QName(*result, qname); +} + +void +XmlParser::ParseContext::Reset() { + xmlnsstack_.Reset(); + raised_ = XML_ERROR_NONE; +} + +XmlParser::ParseContext::~ParseContext() { +} + +} + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlparser.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlparser.h new file mode 100644 index 00000000..760802e4 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlparser.h @@ -0,0 +1,108 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _xmlparser_h_ +#define _xmlparser_h_ + +#include +#include "talk/xmllite/xmlnsstack.h" +#include + +struct XML_ParserStruct; +typedef struct XML_ParserStruct * XML_Parser; + +namespace buzz { + +class XmlParseHandler; +class XmlParseContext; +class XmlParser; + +class XmlParseContext { +public: + virtual QName ResolveQName(const char * qname, bool isAttr) = 0; + virtual void RaiseError(XML_Error err) = 0; +}; + +class XmlParseHandler { +public: + virtual void StartElement(XmlParseContext * pctx, + const char * name, const char ** atts) = 0; + virtual void EndElement(XmlParseContext * pctx, + const char * name) = 0; + virtual void CharacterData(XmlParseContext * pctx, + const char * text, int len) = 0; + virtual void Error(XmlParseContext * pctx, + XML_Error errorCode) = 0; +}; + +class XmlParser { +public: + static void ParseXml(XmlParseHandler * pxph, std::string text); + + explicit XmlParser(XmlParseHandler * pxph); + bool Parse(const char * data, size_t len, bool isFinal); + void Reset(); + virtual ~XmlParser(); + + // expat callbacks + void ExpatStartElement(const char * name, const char ** atts); + void ExpatEndElement(const char * name); + void ExpatCharacterData(const char * text, int len); + void ExpatXmlDecl(const char * ver, const char * enc, int standalone); + +private: + + class ParseContext : public XmlParseContext { + public: + ParseContext(XmlParser * parser); + virtual ~ParseContext(); + virtual QName ResolveQName(const char * qname, bool isAttr); + virtual void RaiseError(XML_Error err) { if (!raised_) raised_ = err; } + XML_Error RaisedError() { return raised_; } + void Reset(); + + void StartElement(); + void EndElement(); + void StartNamespace(const char * prefix, const char * ns); + + private: + const XmlParser * parser_; + XmlnsStack xmlnsstack_; + XML_Error raised_; + }; + + ParseContext context_; + XML_Parser expat_; + XmlParseHandler * pxph_; + bool sentError_; + + +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlprinter.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlprinter.cc new file mode 100644 index 00000000..892e2ebb --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlprinter.cc @@ -0,0 +1,190 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/base/stl_decl.h" +#include +#include +#include +#include +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/xmlprinter.h" +#include "talk/xmllite/xmlnsstack.h" +#include "talk/xmllite/xmlconstants.h" + +namespace buzz { + +class XmlPrinterImpl { +public: + XmlPrinterImpl(std::ostream * pout, + const std::string * const xmlns, int xmlnsCount); + void PrintElement(const XmlElement * element); + void PrintQuotedValue(const std::string & text); + void PrintBodyText(const std::string & text); + +private: + std::ostream *pout_; + XmlnsStack xmlnsStack_; +}; + +void +XmlPrinter::PrintXml(std::ostream * pout, const XmlElement * element) { + PrintXml(pout, element, NULL, 0); +} + +void +XmlPrinter::PrintXml(std::ostream * pout, const XmlElement * element, + const std::string * const xmlns, int xmlnsCount) { + XmlPrinterImpl printer(pout, xmlns, xmlnsCount); + printer.PrintElement(element); +} + +XmlPrinterImpl::XmlPrinterImpl(std::ostream * pout, + const std::string * const xmlns, int xmlnsCount) : + pout_(pout), + xmlnsStack_() { + int i; + for (i = 0; i < xmlnsCount; i += 2) { + xmlnsStack_.AddXmlns(xmlns[i], xmlns[i + 1]); + } +} + +void +XmlPrinterImpl::PrintElement(const XmlElement * element) { + xmlnsStack_.PushFrame(); + + // first go through attrs of pel to add xmlns definitions + const XmlAttr * pattr; + for (pattr = element->FirstAttr(); pattr; pattr = pattr->NextAttr()) { + if (pattr->Name() == QN_XMLNS) + xmlnsStack_.AddXmlns(STR_EMPTY, pattr->Value()); + else if (pattr->Name().Namespace() == NS_XMLNS) + xmlnsStack_.AddXmlns(pattr->Name().LocalPart(), + pattr->Value()); + } + + // then go through qnames to make sure needed xmlns definitons are added + std::vector newXmlns; + std::pair prefix; + prefix = xmlnsStack_.AddNewPrefix(element->Name().Namespace(), false); + if (prefix.second) { + newXmlns.push_back(prefix.first); + newXmlns.push_back(element->Name().Namespace()); + } + + for (pattr = element->FirstAttr(); pattr; pattr = pattr->NextAttr()) { + prefix = xmlnsStack_.AddNewPrefix(pattr->Name().Namespace(), true); + if (prefix.second) { + newXmlns.push_back(prefix.first); + newXmlns.push_back(element->Name().Namespace()); + } + } + + // print the element name + *pout_ << '<' << xmlnsStack_.FormatQName(element->Name(), false); + + // and the attributes + for (pattr = element->FirstAttr(); pattr; pattr = pattr->NextAttr()) { + *pout_ << ' ' << xmlnsStack_.FormatQName(pattr->Name(), true) << "=\""; + PrintQuotedValue(pattr->Value()); + *pout_ << '"'; + } + + // and the extra xmlns declarations + std::vector::iterator i(newXmlns.begin()); + while (i < newXmlns.end()) { + if (*i == STR_EMPTY) + *pout_ << " xmlns=\"" << *(i + 1) << '"'; + else + *pout_ << " xmlns:" << *i << "=\"" << *(i + 1) << '"'; + i += 2; + } + + // now the children + const XmlChild * pchild = element->FirstChild(); + + if (pchild == NULL) + *pout_ << "/>"; + else { + *pout_ << '>'; + while (pchild) { + if (pchild->IsText()) + PrintBodyText(pchild->AsText()->Text()); + else + PrintElement(pchild->AsElement()); + pchild = pchild->NextChild(); + } + *pout_ << "Name(), false) << '>'; + } + + xmlnsStack_.PopFrame(); +} + +void +XmlPrinterImpl::PrintQuotedValue(const std::string & text) { + size_t safe = 0; + for (;;) { + size_t unsafe = text.find_first_of("<>&\"", safe); + if (unsafe == std::string::npos) + unsafe = text.length(); + *pout_ << text.substr(safe, unsafe - safe); + if (unsafe == text.length()) + return; + switch (text[unsafe]) { + case '<': *pout_ << "<"; break; + case '>': *pout_ << ">"; break; + case '&': *pout_ << "&"; break; + case '"': *pout_ << """; break; + } + safe = unsafe + 1; + if (safe == text.length()) + return; + } +} + +void +XmlPrinterImpl::PrintBodyText(const std::string & text) { + size_t safe = 0; + for (;;) { + size_t unsafe = text.find_first_of("<>&", safe); + if (unsafe == std::string::npos) + unsafe = text.length(); + *pout_ << text.substr(safe, unsafe - safe); + if (unsafe == text.length()) + return; + switch (text[unsafe]) { + case '<': *pout_ << "<"; break; + case '>': *pout_ << ">"; break; + case '&': *pout_ << "&"; break; + } + safe = unsafe + 1; + if (safe == text.length()) + return; + } +} + + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlprinter.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlprinter.h new file mode 100644 index 00000000..96900d0d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmllite/xmlprinter.h @@ -0,0 +1,49 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _xmlprinter_h_ +#define _xmlprinter_h_ + +#include +#include +#include "talk/base/scoped_ptr.h" + +namespace buzz { + +class XmlElement; + +class XmlPrinter { +public: + static void PrintXml(std::ostream * pout, const XmlElement * pelt); + + static void PrintXml(std::ostream * pout, const XmlElement * pelt, + const std::string * const xmlns, int xmlnsCount); +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/Makefile.am b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/Makefile.am new file mode 100644 index 00000000..527f7053 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/Makefile.am @@ -0,0 +1,34 @@ +## Does not compile with final +KDE_OPTIONS = nofinal + +libcricketxmpp_la_SOURCES = constants.cc \ + jid.cc \ + saslmechanism.cc \ + xmppclient.cc \ + xmppengineimpl.cc \ + xmppengineimpl_iq.cc \ + xmpplogintask.cc \ + xmppstanzaparser.cc \ + xmpptask.cc + +noinst_HEADERS = asyncsocket.h \ + prexmppauth.h \ + saslhandler.h \ + xmpplogintask.h \ + jid.h \ + saslmechanism.h \ + xmppclient.h \ + xmpppassword.h \ + constants.h \ + saslplainmechanism.h \ + xmppclientsettings.h \ + xmppstanzaparser.h \ + xmppengine.h \ + xmpptask.h \ + plainsaslhandler.h \ + saslcookiemechanism.h \ + xmppengineimpl.h + + +AM_CPPFLAGS = -DPOSIX -I$(srcdir)/../.. +noinst_LTLIBRARIES = libcricketxmpp.la diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/asyncsocket.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/asyncsocket.h new file mode 100644 index 00000000..fd91929b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/asyncsocket.h @@ -0,0 +1,85 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _ASYNCSOCKET_H_ +#define _ASYNCSOCKET_H_ + +#include "talk/base/sigslot.h" + +namespace cricket { + class SocketAddress; +} + +namespace buzz { + +class AsyncSocket { +public: + enum State { + STATE_CLOSED = 0, //!< Socket is not open. + STATE_CLOSING, //!< Socket is closing but can have buffered data + STATE_CONNECTING, //!< In the process of + STATE_OPEN, //!< Socket is connected +#if defined(FEATURE_ENABLE_SSL) + STATE_TLS_CONNECTING, //!< Establishing TLS connection + STATE_TLS_OPEN, //!< TLS connected +#endif + }; + + enum Error { + ERROR_NONE = 0, //!< No error + ERROR_WINSOCK, //!< Winsock error + ERROR_DNS, //!< Couldn't resolve host name + ERROR_WRONGSTATE, //!< Call made while socket is in the wrong state +#if defined(FEATURE_ENABLE_SSL) + ERROR_SSL, //!< Something went wrong with OpenSSL +#endif + }; + + virtual ~AsyncSocket() {} + virtual State state() = 0; + virtual Error error() = 0; + + virtual bool Connect(const cricket::SocketAddress& addr) = 0; + virtual bool Read(char * data, size_t len, size_t* len_read) = 0; + virtual bool Write(const char * data, size_t len) = 0; + virtual bool Close() = 0; +#if defined(FEATURE_ENABLE_SSL) + // We allow matching any passed domain. + // If both names are passed as empty, we do not require a match. + virtual bool StartTls(const std::string & domainname) = 0; +#endif + + sigslot::signal0<> SignalConnected; + sigslot::signal0<> SignalSSLConnected; + sigslot::signal0<> SignalClosed; + sigslot::signal0<> SignalRead; + sigslot::signal0<> SignalError; +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/constants.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/constants.cc new file mode 100644 index 00000000..b2c833f7 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/constants.cc @@ -0,0 +1,331 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include +#include "talk/base/basicdefs.h" +#include "talk/xmllite/xmlconstants.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/qname.h" +#include "talk/xmpp/jid.h" +#include "talk/xmpp/constants.h" +namespace buzz { + +const Jid JID_EMPTY(STR_EMPTY); + +const std::string & Constants::ns_client() { + static const std::string ns_client_("jabber:client"); + return ns_client_; +} + +const std::string & Constants::ns_server() { + static const std::string ns_server_("jabber:server"); + return ns_server_; +} + +const std::string & Constants::ns_stream() { + static const std::string ns_stream_("http://etherx.jabber.org/streams"); + return ns_stream_; +} + +const std::string & Constants::ns_xstream() { + static const std::string ns_xstream_("urn:ietf:params:xml:ns:xmpp-streams"); + return ns_xstream_; +} + +const std::string & Constants::ns_tls() { + static const std::string ns_tls_("urn:ietf:params:xml:ns:xmpp-tls"); + return ns_tls_; +} + +const std::string & Constants::ns_sasl() { + static const std::string ns_sasl_("urn:ietf:params:xml:ns:xmpp-sasl"); + return ns_sasl_; +} + +const std::string & Constants::ns_bind() { + static const std::string ns_bind_("urn:ietf:params:xml:ns:xmpp-bind"); + return ns_bind_; +} + +const std::string & Constants::ns_dialback() { + static const std::string ns_dialback_("jabber:server:dialback"); + return ns_dialback_; +} + +const std::string & Constants::ns_session() { + static const std::string ns_session_("urn:ietf:params:xml:ns:xmpp-session"); + return ns_session_; +} + +const std::string & Constants::ns_stanza() { + static const std::string ns_stanza_("urn:ietf:params:xml:ns:xmpp-stanzas"); + return ns_stanza_; +} + +const std::string & Constants::ns_privacy() { + static const std::string ns_privacy_("jabber:iq:privacy"); + return ns_privacy_; +} + +const std::string & Constants::ns_roster() { + static const std::string ns_roster_("jabber:iq:roster"); + return ns_roster_; +} + +const std::string & Constants::ns_vcard() { + static const std::string ns_vcard_("vcard-temp"); + return ns_vcard_; +} + +const std::string & Constants::str_client() { + static const std::string str_client_("client"); + return str_client_; +} + +const std::string & Constants::str_server() { + static const std::string str_server_("server"); + return str_server_; +} + +const std::string & Constants::str_stream() { + static const std::string str_stream_("stream"); + return str_stream_; +} + +const std::string STR_GET("get"); +const std::string STR_SET("set"); +const std::string STR_RESULT("result"); +const std::string STR_ERROR("error"); + +const std::string STR_FROM("from"); +const std::string STR_TO("to"); +const std::string STR_BOTH("both"); +const std::string STR_REMOVE("remove"); + +const std::string STR_UNAVAILABLE("unavailable"); +const std::string STR_INVISIBLE("invisible"); + +const std::string STR_GOOGLE_COM("google.com"); +const std::string STR_GMAIL_COM("gmail.com"); +const std::string STR_GOOGLEMAIL_COM("googlemail.com"); +const std::string STR_DEFAULT_DOMAIN("default.talk.google.com"); +const std::string STR_X("x"); + +const QName QN_STREAM_STREAM(true, NS_STREAM, STR_STREAM); +const QName QN_STREAM_FEATURES(true, NS_STREAM, "features"); +const QName QN_STREAM_ERROR(true, NS_STREAM, "error"); + +const QName QN_XSTREAM_BAD_FORMAT(true, NS_XSTREAM, "bad-format"); +const QName QN_XSTREAM_BAD_NAMESPACE_PREFIX(true, NS_XSTREAM, "bad-namespace-prefix"); +const QName QN_XSTREAM_CONFLICT(true, NS_XSTREAM, "conflict"); +const QName QN_XSTREAM_CONNECTION_TIMEOUT(true, NS_XSTREAM, "connection-timeout"); +const QName QN_XSTREAM_HOST_GONE(true, NS_XSTREAM, "host-gone"); +const QName QN_XSTREAM_HOST_UNKNOWN(true, NS_XSTREAM, "host-unknown"); +const QName QN_XSTREAM_IMPROPER_ADDRESSIING(true, NS_XSTREAM, "improper-addressing"); +const QName QN_XSTREAM_INTERNAL_SERVER_ERROR(true, NS_XSTREAM, "internal-server-error"); +const QName QN_XSTREAM_INVALID_FROM(true, NS_XSTREAM, "invalid-from"); +const QName QN_XSTREAM_INVALID_ID(true, NS_XSTREAM, "invalid-id"); +const QName QN_XSTREAM_INVALID_NAMESPACE(true, NS_XSTREAM, "invalid-namespace"); +const QName QN_XSTREAM_INVALID_XML(true, NS_XSTREAM, "invalid-xml"); +const QName QN_XSTREAM_NOT_AUTHORIZED(true, NS_XSTREAM, "not-authorized"); +const QName QN_XSTREAM_POLICY_VIOLATION(true, NS_XSTREAM, "policy-violation"); +const QName QN_XSTREAM_REMOTE_CONNECTION_FAILED(true, NS_XSTREAM, "remote-connection-failed"); +const QName QN_XSTREAM_RESOURCE_CONSTRAINT(true, NS_XSTREAM, "resource-constraint"); +const QName QN_XSTREAM_RESTRICTED_XML(true, NS_XSTREAM, "restricted-xml"); +const QName QN_XSTREAM_SEE_OTHER_HOST(true, NS_XSTREAM, "see-other-host"); +const QName QN_XSTREAM_SYSTEM_SHUTDOWN(true, NS_XSTREAM, "system-shutdown"); +const QName QN_XSTREAM_UNDEFINED_CONDITION(true, NS_XSTREAM, "undefined-condition"); +const QName QN_XSTREAM_UNSUPPORTED_ENCODING(true, NS_XSTREAM, "unsupported-encoding"); +const QName QN_XSTREAM_UNSUPPORTED_STANZA_TYPE(true, NS_XSTREAM, "unsupported-stanza-type"); +const QName QN_XSTREAM_UNSUPPORTED_VERSION(true, NS_XSTREAM, "unsupported-version"); +const QName QN_XSTREAM_XML_NOT_WELL_FORMED(true, NS_XSTREAM, "xml-not-well-formed"); +const QName QN_XSTREAM_TEXT(true, NS_XSTREAM, "text"); + +const QName QN_TLS_STARTTLS(true, NS_TLS, "starttls"); +const QName QN_TLS_REQUIRED(true, NS_TLS, "required"); +const QName QN_TLS_PROCEED(true, NS_TLS, "proceed"); +const QName QN_TLS_FAILURE(true, NS_TLS, "failure"); + +const QName QN_SASL_MECHANISMS(true, NS_SASL, "mechanisms"); +const QName QN_SASL_MECHANISM(true, NS_SASL, "mechanism"); +const QName QN_SASL_AUTH(true, NS_SASL, "auth"); +const QName QN_SASL_CHALLENGE(true, NS_SASL, "challenge"); +const QName QN_SASL_RESPONSE(true, NS_SASL, "response"); +const QName QN_SASL_ABORT(true, NS_SASL, "abort"); +const QName QN_SASL_SUCCESS(true, NS_SASL, "success"); +const QName QN_SASL_FAILURE(true, NS_SASL, "failure"); +const QName QN_SASL_ABORTED(true, NS_SASL, "aborted"); +const QName QN_SASL_INCORRECT_ENCODING(true, NS_SASL, "incorrect-encoding"); +const QName QN_SASL_INVALID_AUTHZID(true, NS_SASL, "invalid-authzid"); +const QName QN_SASL_INVALID_MECHANISM(true, NS_SASL, "invalid-mechanism"); +const QName QN_SASL_MECHANISM_TOO_WEAK(true, NS_SASL, "mechanism-too-weak"); +const QName QN_SASL_NOT_AUTHORIZED(true, NS_SASL, "not-authorized"); +const QName QN_SASL_TEMPORARY_AUTH_FAILURE(true, NS_SASL, "temporary-auth-failure"); + +const QName QN_DIALBACK_RESULT(true, NS_DIALBACK, "result"); +const QName QN_DIALBACK_VERIFY(true, NS_DIALBACK, "verify"); + +const QName QN_STANZA_BAD_REQUEST(true, NS_STANZA, "bad-request"); +const QName QN_STANZA_CONFLICT(true, NS_STANZA, "conflict"); +const QName QN_STANZA_FEATURE_NOT_IMPLEMENTED(true, NS_STANZA, "feature-not-implemented"); +const QName QN_STANZA_FORBIDDEN(true, NS_STANZA, "forbidden"); +const QName QN_STANZA_GONE(true, NS_STANZA, "gone"); +const QName QN_STANZA_INTERNAL_SERVER_ERROR(true, NS_STANZA, "internal-server-error"); +const QName QN_STANZA_ITEM_NOT_FOUND(true, NS_STANZA, "item-not-found"); +const QName QN_STANZA_JID_MALFORMED(true, NS_STANZA, "jid-malformed"); +const QName QN_STANZA_NOT_ACCEPTABLE(true, NS_STANZA, "not-acceptable"); +const QName QN_STANZA_NOT_ALLOWED(true, NS_STANZA, "not-allowed"); +const QName QN_STANZA_PAYMENT_REQUIRED(true, NS_STANZA, "payment-required"); +const QName QN_STANZA_RECIPIENT_UNAVAILABLE(true, NS_STANZA, "recipient-unavailable"); +const QName QN_STANZA_REDIRECT(true, NS_STANZA, "redirect"); +const QName QN_STANZA_REGISTRATION_REQUIRED(true, NS_STANZA, "registration-required"); +const QName QN_STANZA_REMOTE_SERVER_NOT_FOUND(true, NS_STANZA, "remote-server-not-found"); +const QName QN_STANZA_REMOTE_SERVER_TIMEOUT(true, NS_STANZA, "remote-server-timeout"); +const QName QN_STANZA_RESOURCE_CONSTRAINT(true, NS_STANZA, "resource-constraint"); +const QName QN_STANZA_SERVICE_UNAVAILABLE(true, NS_STANZA, "service-unavailable"); +const QName QN_STANZA_SUBSCRIPTION_REQUIRED(true, NS_STANZA, "subscription-required"); +const QName QN_STANZA_UNDEFINED_CONDITION(true, NS_STANZA, "undefined-condition"); +const QName QN_STANZA_UNEXPECTED_REQUEST(true, NS_STANZA, "unexpected-request"); +const QName QN_STANZA_TEXT(true, NS_STANZA, "text"); + +const QName QN_BIND_BIND(true, NS_BIND, "bind"); +const QName QN_BIND_RESOURCE(true, NS_BIND, "resource"); +const QName QN_BIND_JID(true, NS_BIND, "jid"); + +const QName QN_MESSAGE(true, NS_CLIENT, "message"); +const QName QN_BODY(true, NS_CLIENT, "body"); +const QName QN_SUBJECT(true, NS_CLIENT, "subject"); +const QName QN_THREAD(true, NS_CLIENT, "thread"); +const QName QN_PRESENCE(true, NS_CLIENT, "presence"); +const QName QN_SHOW(true, NS_CLIENT, "show"); +const QName QN_STATUS(true, NS_CLIENT, "status"); +const QName QN_LANG(true, NS_CLIENT, "lang"); +const QName QN_PRIORITY(true, NS_CLIENT, "priority"); +const QName QN_IQ(true, NS_CLIENT, "iq"); +const QName QN_ERROR(true, NS_CLIENT, "error"); + +const QName QN_SERVER_MESSAGE(true, NS_SERVER, "message"); +const QName QN_SERVER_BODY(true, NS_SERVER, "body"); +const QName QN_SERVER_SUBJECT(true, NS_SERVER, "subject"); +const QName QN_SERVER_THREAD(true, NS_SERVER, "thread"); +const QName QN_SERVER_PRESENCE(true, NS_SERVER, "presence"); +const QName QN_SERVER_SHOW(true, NS_SERVER, "show"); +const QName QN_SERVER_STATUS(true, NS_SERVER, "status"); +const QName QN_SERVER_LANG(true, NS_SERVER, "lang"); +const QName QN_SERVER_PRIORITY(true, NS_SERVER, "priority"); +const QName QN_SERVER_IQ(true, NS_SERVER, "iq"); +const QName QN_SERVER_ERROR(true, NS_SERVER, "error"); + +const QName QN_SESSION_SESSION(true, NS_SESSION, "session"); + +const QName QN_PRIVACY_QUERY(true, NS_PRIVACY, "query"); +const QName QN_PRIVACY_ACTIVE(true, NS_PRIVACY, "active"); +const QName QN_PRIVACY_DEFAULT(true, NS_PRIVACY, "default"); +const QName QN_PRIVACY_LIST(true, NS_PRIVACY, "list"); +const QName QN_PRIVACY_ITEM(true, NS_PRIVACY, "item"); +const QName QN_PRIVACY_IQ(true, NS_PRIVACY, "iq"); +const QName QN_PRIVACY_MESSAGE(true, NS_PRIVACY, "message"); +const QName QN_PRIVACY_PRESENCE_IN(true, NS_PRIVACY, "presence-in"); +const QName QN_PRIVACY_PRESENCE_OUT(true, NS_PRIVACY, "presence-out"); + +const QName QN_ROSTER_QUERY(true, NS_ROSTER, "query"); +const QName QN_ROSTER_ITEM(true, NS_ROSTER, "item"); +const QName QN_ROSTER_GROUP(true, NS_ROSTER, "group"); + +const QName QN_VCARD_QUERY(true, NS_VCARD, "vCard"); +const QName QN_VCARD_FN(true, NS_VCARD, "FN"); + +const QName QN_XML_LANG(true, NS_XML, "lang"); + +const std::string STR_TYPE("type"); +const std::string STR_ID("id"); +const std::string STR_NAME("name"); +const std::string STR_JID("jid"); +const std::string STR_SUBSCRIPTION("subscription"); +const std::string STR_ASK("ask"); + +const QName QN_ENCODING(true, STR_EMPTY, STR_ENCODING); +const QName QN_VERSION(true, STR_EMPTY, STR_VERSION); +const QName QN_TO(true, STR_EMPTY, "to"); +const QName QN_FROM(true, STR_EMPTY, "from"); +const QName QN_TYPE(true, STR_EMPTY, "type"); +const QName QN_ID(true, STR_EMPTY, "id"); +const QName QN_CODE(true, STR_EMPTY, "code"); +const QName QN_NAME(true, STR_EMPTY, "name"); +const QName QN_VALUE(true, STR_EMPTY, "value"); +const QName QN_ACTION(true, STR_EMPTY, "action"); +const QName QN_ORDER(true, STR_EMPTY, "order"); +const QName QN_MECHANISM(true, STR_EMPTY, "mechanism"); +const QName QN_ASK(true, STR_EMPTY, "ask"); +const QName QN_JID(true, STR_EMPTY, "jid"); +const QName QN_SUBSCRIPTION(true, STR_EMPTY, "subscription"); +const QName QN_SOURCE(true, STR_EMPTY, "source"); + +const QName QN_XMLNS_CLIENT(true, NS_XMLNS, STR_CLIENT); +const QName QN_XMLNS_SERVER(true, NS_XMLNS, STR_SERVER); +const QName QN_XMLNS_STREAM(true, NS_XMLNS, STR_STREAM); + +// Presence +const std::string STR_SHOW_AWAY("away"); +const std::string STR_SHOW_CHAT("chat"); +const std::string STR_SHOW_DND("dnd"); +const std::string STR_SHOW_XA("xa"); + +// Subscription +const std::string STR_SUBSCRIBE("subscribe"); +const std::string STR_SUBSCRIBED("subscribed"); +const std::string STR_UNSUBSCRIBE("unsubscribe"); +const std::string STR_UNSUBSCRIBED("unsubscribed"); + + +// JEP 0030 +const QName QN_NODE(true, STR_EMPTY, "node"); +const QName QN_CATEGORY(true, STR_EMPTY, "category"); +const QName QN_VAR(true, STR_EMPTY, "var"); +const std::string NS_DISCO_INFO("http://jabber.org/protocol/disco#info"); +const std::string NS_DISCO_ITEMS("http://jabber.org/protocol/disco#items"); +const QName QN_DISCO_INFO_QUERY(true, NS_DISCO_INFO, "query"); +const QName QN_DISCO_IDENTITY(true, NS_DISCO_INFO, "identity"); +const QName QN_DISCO_FEATURE(true, NS_DISCO_INFO, "feature"); + +const QName QN_DISCO_ITEMS_QUERY(true, NS_DISCO_ITEMS, "query"); +const QName QN_DISCO_ITEM(true, NS_DISCO_ITEMS, "item"); + + +// JEP 0115 +const std::string NS_CAPS("http://jabber.org/protocol/caps"); +const QName QN_CAPS_C(true, NS_CAPS, "c"); +const QName QN_VER(true, STR_EMPTY, "ver"); +const QName QN_EXT(true, STR_EMPTY, "ext"); + +// JEP 0091 Delayed Delivery +const std::string kNSDelay("jabber:x:delay"); +const QName kQnDelayX(true, kNSDelay, "x"); +const QName kQnStamp(true, STR_EMPTY, "stamp"); + + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/constants.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/constants.h new file mode 100644 index 00000000..b05af965 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/constants.h @@ -0,0 +1,300 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _CRICKET_XMPP_XMPPLIB_BUZZ_CONSTANTS_H_ +#define _CRICKET_XMPP_XMPPLIB_BUZZ_CONSTANTS_H_ + +#include +#include "talk/xmllite/qname.h" +#include "talk/xmpp/jid.h" + + +#define NS_CLIENT Constants::ns_client() +#define NS_SERVER Constants::ns_server() +#define NS_STREAM Constants::ns_stream() +#define NS_XSTREAM Constants::ns_xstream() +#define NS_TLS Constants::ns_tls() +#define NS_SASL Constants::ns_sasl() +#define NS_BIND Constants::ns_bind() +#define NS_DIALBACK Constants::ns_dialback() +#define NS_SESSION Constants::ns_session() +#define NS_STANZA Constants::ns_stanza() +#define NS_PRIVACY Constants::ns_privacy() +#define NS_ROSTER Constants::ns_roster() +#define NS_VCARD Constants::ns_vcard() +#define STR_CLIENT Constants::str_client() +#define STR_SERVER Constants::str_server() +#define STR_STREAM Constants::str_stream() + +namespace buzz { + +extern const Jid JID_EMPTY; + +class Constants { + public: + static const std::string & ns_client(); + static const std::string & ns_server(); + static const std::string & ns_stream(); + static const std::string & ns_xstream(); + static const std::string & ns_tls(); + static const std::string & ns_sasl(); + static const std::string & ns_bind(); + static const std::string & ns_dialback(); + static const std::string & ns_session(); + static const std::string & ns_stanza(); + static const std::string & ns_privacy(); + static const std::string & ns_roster(); + static const std::string & ns_vcard(); + + static const std::string & str_client(); + static const std::string & str_server(); + static const std::string & str_stream(); +}; + +extern const std::string STR_GET; +extern const std::string STR_SET; +extern const std::string STR_RESULT; +extern const std::string STR_ERROR; + +extern const std::string STR_FROM; +extern const std::string STR_TO; +extern const std::string STR_BOTH; +extern const std::string STR_REMOVE; + +extern const std::string STR_MESSAGE; +extern const std::string STR_BODY; +extern const std::string STR_PRESENCE; +extern const std::string STR_STATUS; +extern const std::string STR_SHOW; +extern const std::string STR_PRIOIRTY; +extern const std::string STR_IQ; + +extern const std::string STR_TYPE; +extern const std::string STR_NAME; +extern const std::string STR_ID; +extern const std::string STR_JID; +extern const std::string STR_SUBSCRIPTION; +extern const std::string STR_ASK; +extern const std::string STR_X; +extern const std::string STR_GOOGLE_COM; +extern const std::string STR_GMAIL_COM; +extern const std::string STR_GOOGLEMAIL_COM; +extern const std::string STR_DEFAULT_DOMAIN; + +extern const std::string STR_UNAVAILABLE; +extern const std::string STR_INVISIBLE; + +extern const QName QN_STREAM_STREAM; +extern const QName QN_STREAM_FEATURES; +extern const QName QN_STREAM_ERROR; + +extern const QName QN_XSTREAM_BAD_FORMAT; +extern const QName QN_XSTREAM_BAD_NAMESPACE_PREFIX; +extern const QName QN_XSTREAM_CONFLICT; +extern const QName QN_XSTREAM_CONNECTION_TIMEOUT; +extern const QName QN_XSTREAM_HOST_GONE; +extern const QName QN_XSTREAM_HOST_UNKNOWN; +extern const QName QN_XSTREAM_IMPROPER_ADDRESSIING; +extern const QName QN_XSTREAM_INTERNAL_SERVER_ERROR; +extern const QName QN_XSTREAM_INVALID_FROM; +extern const QName QN_XSTREAM_INVALID_ID; +extern const QName QN_XSTREAM_INVALID_NAMESPACE; +extern const QName QN_XSTREAM_INVALID_XML; +extern const QName QN_XSTREAM_NOT_AUTHORIZED; +extern const QName QN_XSTREAM_POLICY_VIOLATION; +extern const QName QN_XSTREAM_REMOTE_CONNECTION_FAILED; +extern const QName QN_XSTREAM_RESOURCE_CONSTRAINT; +extern const QName QN_XSTREAM_RESTRICTED_XML; +extern const QName QN_XSTREAM_SEE_OTHER_HOST; +extern const QName QN_XSTREAM_SYSTEM_SHUTDOWN; +extern const QName QN_XSTREAM_UNDEFINED_CONDITION; +extern const QName QN_XSTREAM_UNSUPPORTED_ENCODING; +extern const QName QN_XSTREAM_UNSUPPORTED_STANZA_TYPE; +extern const QName QN_XSTREAM_UNSUPPORTED_VERSION; +extern const QName QN_XSTREAM_XML_NOT_WELL_FORMED; +extern const QName QN_XSTREAM_TEXT; + +extern const QName QN_TLS_STARTTLS; +extern const QName QN_TLS_REQUIRED; +extern const QName QN_TLS_PROCEED; +extern const QName QN_TLS_FAILURE; + +extern const QName QN_SASL_MECHANISMS; +extern const QName QN_SASL_MECHANISM; +extern const QName QN_SASL_AUTH; +extern const QName QN_SASL_CHALLENGE; +extern const QName QN_SASL_RESPONSE; +extern const QName QN_SASL_ABORT; +extern const QName QN_SASL_SUCCESS; +extern const QName QN_SASL_FAILURE; +extern const QName QN_SASL_ABORTED; +extern const QName QN_SASL_INCORRECT_ENCODING; +extern const QName QN_SASL_INVALID_AUTHZID; +extern const QName QN_SASL_INVALID_MECHANISM; +extern const QName QN_SASL_MECHANISM_TOO_WEAK; +extern const QName QN_SASL_NOT_AUTHORIZED; +extern const QName QN_SASL_TEMPORARY_AUTH_FAILURE; + +extern const QName QN_DIALBACK_RESULT; +extern const QName QN_DIALBACK_VERIFY; + +extern const QName QN_STANZA_BAD_REQUEST; +extern const QName QN_STANZA_CONFLICT; +extern const QName QN_STANZA_FEATURE_NOT_IMPLEMENTED; +extern const QName QN_STANZA_FORBIDDEN; +extern const QName QN_STANZA_GONE; +extern const QName QN_STANZA_INTERNAL_SERVER_ERROR; +extern const QName QN_STANZA_ITEM_NOT_FOUND; +extern const QName QN_STANZA_JID_MALFORMED; +extern const QName QN_STANZA_NOT_ACCEPTABLE; +extern const QName QN_STANZA_NOT_ALLOWED; +extern const QName QN_STANZA_PAYMENT_REQUIRED; +extern const QName QN_STANZA_RECIPIENT_UNAVAILABLE; +extern const QName QN_STANZA_REDIRECT; +extern const QName QN_STANZA_REGISTRATION_REQUIRED; +extern const QName QN_STANZA_REMOTE_SERVER_NOT_FOUND; +extern const QName QN_STANZA_REMOTE_SERVER_TIMEOUT; +extern const QName QN_STANZA_RESOURCE_CONSTRAINT; +extern const QName QN_STANZA_SERVICE_UNAVAILABLE; +extern const QName QN_STANZA_SUBSCRIPTION_REQUIRED; +extern const QName QN_STANZA_UNDEFINED_CONDITION; +extern const QName QN_STANZA_UNEXPECTED_REQUEST; +extern const QName QN_STANZA_TEXT; + +extern const QName QN_BIND_BIND; +extern const QName QN_BIND_RESOURCE; +extern const QName QN_BIND_JID; + +extern const QName QN_MESSAGE; +extern const QName QN_BODY; +extern const QName QN_SUBJECT; +extern const QName QN_THREAD; +extern const QName QN_PRESENCE; +extern const QName QN_SHOW; +extern const QName QN_STATUS; +extern const QName QN_LANG; +extern const QName QN_PRIORITY; +extern const QName QN_IQ; +extern const QName QN_ERROR; + +extern const QName QN_SERVER_MESSAGE; +extern const QName QN_SERVER_BODY; +extern const QName QN_SERVER_SUBJECT; +extern const QName QN_SERVER_THREAD; +extern const QName QN_SERVER_PRESENCE; +extern const QName QN_SERVER_SHOW; +extern const QName QN_SERVER_STATUS; +extern const QName QN_SERVER_LANG; +extern const QName QN_SERVER_PRIORITY; +extern const QName QN_SERVER_IQ; +extern const QName QN_SERVER_ERROR; + +extern const QName QN_SESSION_SESSION; + +extern const QName QN_PRIVACY_QUERY; +extern const QName QN_PRIVACY_ACTIVE; +extern const QName QN_PRIVACY_DEFAULT; +extern const QName QN_PRIVACY_LIST; +extern const QName QN_PRIVACY_ITEM; +extern const QName QN_PRIVACY_IQ; +extern const QName QN_PRIVACY_MESSAGE; +extern const QName QN_PRIVACY_PRESENCE_IN; +extern const QName QN_PRIVACY_PRESENCE_OUT; + +extern const QName QN_ROSTER_QUERY; +extern const QName QN_ROSTER_ITEM; +extern const QName QN_ROSTER_GROUP; + +extern const QName QN_VCARD_QUERY; +extern const QName QN_VCARD_FN; + +extern const QName QN_XML_LANG; + +extern const QName QN_ENCODING; +extern const QName QN_VERSION; +extern const QName QN_TO; +extern const QName QN_FROM; +extern const QName QN_TYPE; +extern const QName QN_ID; +extern const QName QN_CODE; +extern const QName QN_NAME; +extern const QName QN_VALUE; +extern const QName QN_ACTION; +extern const QName QN_ORDER; +extern const QName QN_MECHANISM; +extern const QName QN_ASK; +extern const QName QN_JID; +extern const QName QN_SUBSCRIPTION; + + +extern const QName QN_XMLNS_CLIENT; +extern const QName QN_XMLNS_SERVER; +extern const QName QN_XMLNS_STREAM; + +// Presence +extern const std::string STR_SHOW_AWAY; +extern const std::string STR_SHOW_CHAT; +extern const std::string STR_SHOW_DND; +extern const std::string STR_SHOW_XA; + +// Subscription +extern const std::string STR_SUBSCRIBE; +extern const std::string STR_SUBSCRIBED; +extern const std::string STR_UNSUBSCRIBE; +extern const std::string STR_UNSUBSCRIBED; + + +// JEP 0030 +extern const QName QN_NODE; +extern const QName QN_CATEGORY; +extern const QName QN_VAR; +extern const std::string NS_DISCO_INFO; +extern const std::string NS_DISCO_ITEMS; + +extern const QName QN_DISCO_INFO_QUERY; +extern const QName QN_DISCO_IDENTITY; +extern const QName QN_DISCO_FEATURE; + +extern const QName QN_DISCO_ITEMS_QUERY; +extern const QName QN_DISCO_ITEM; + + +// JEP 0115 +extern const std::string NS_CAPS; +extern const QName QN_CAPS_C; +extern const QName QN_VER; +extern const QName QN_EXT; + + +// JEP 0091 Delayed Delivery +extern const std::string kNSDelay; +extern const QName kQnDelayX; +extern const QName kQnStamp; + +} + +#endif // _CRICKET_XMPP_XMPPLIB_BUZZ_CONSTANTS_H_ diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/jid.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/jid.cc new file mode 100644 index 00000000..b742e03a --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/jid.cc @@ -0,0 +1,477 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +extern "C" { +#include +} +#include +#include "talk/xmpp/jid.h" +#include "talk/xmpp/constants.h" +#include "talk/base/common.h" +#include + +#define new TRACK_NEW + +namespace buzz { + +static int AsciiToLower(int x) { + return (x <= 'Z' && x >= 'A') ? (x + ('a' - 'A')) : x; +} + +Jid::Jid() : data_(NULL) { +} + +Jid::Jid(bool is_special, const std::string & special) { + data_ = is_special ? new Data(special, STR_EMPTY, STR_EMPTY) : NULL; +} + +Jid::Jid(const std::string & jid_string) { + if (jid_string == STR_EMPTY) { + data_ = NULL; + return; + } + + // First find the slash and slice of that part + size_t slash = jid_string.find('/'); + std::string resource_name = (slash == std::string::npos ? STR_EMPTY : + jid_string.substr(slash + 1)); + + // Now look for the node + std::string node_name; + size_t at = jid_string.find('@'); + size_t domain_begin; + if (at < slash && at != std::string::npos) { + node_name = jid_string.substr(0, at); + domain_begin = at + 1; + } else { + domain_begin = 0; + } + + // Now take what is left as the domain + size_t domain_length = + ( slash == std::string::npos + ? jid_string.length() - domain_begin + : slash - domain_begin); + + // avoid allocating these constants repeatedly + std::string domain_name; + + if (domain_length == 9 && jid_string.find("gmail.com", domain_begin) == domain_begin) { + domain_name = STR_GMAIL_COM; + } + else if (domain_length == 14 && jid_string.find("googlemail.com", domain_begin) == domain_begin) { + domain_name = STR_GOOGLEMAIL_COM; + } + else if (domain_length == 10 && jid_string.find("google.com", domain_begin) == domain_begin) { + domain_name = STR_GOOGLE_COM; + } + else { + domain_name = jid_string.substr(domain_begin, domain_length); + } + + // If the domain is empty we have a non-valid jid and we should empty + // everything else out + if (domain_name.empty()) { + data_ = NULL; + return; + } + + bool valid_node; + std::string validated_node = prepNode(node_name, + node_name.begin(), node_name.end(), &valid_node); + bool valid_domain; + std::string validated_domain = prepDomain(domain_name, + domain_name.begin(), domain_name.end(), &valid_domain); + bool valid_resource; + std::string validated_resource = prepResource(resource_name, + resource_name.begin(), resource_name.end(), &valid_resource); + + if (!valid_node || !valid_domain || !valid_resource) { + data_ = NULL; + return; + } + + data_ = new Data(validated_node, validated_domain, validated_resource); +} + +Jid::Jid(const std::string & node_name, + const std::string & domain_name, + const std::string & resource_name) { + if (domain_name.empty()) { + data_ = NULL; + return; + } + + bool valid_node; + std::string validated_node = prepNode(node_name, + node_name.begin(), node_name.end(), &valid_node); + bool valid_domain; + std::string validated_domain = prepDomain(domain_name, + domain_name.begin(), domain_name.end(), &valid_domain); + bool valid_resource; + std::string validated_resource = prepResource(resource_name, + resource_name.begin(), resource_name.end(), &valid_resource); + + if (!valid_node || !valid_domain || !valid_resource) { + data_ = NULL; + return; + } + + data_ = new Data(validated_node, validated_domain, validated_resource); +} + +std::string Jid::Str() const { + if (!IsValid()) + return STR_EMPTY; + + std::string ret; + + if (!data_->node_name_.empty()) + ret = data_->node_name_ + "@"; + + ASSERT(data_->domain_name_ != STR_EMPTY); + ret += data_->domain_name_; + + if (!data_->resource_name_.empty()) + ret += "/" + data_->resource_name_; + + return ret; +} + +bool +Jid::IsValid() const { + return data_ != NULL && !data_->domain_name_.empty(); +} + +bool +Jid::IsBare() const { + return IsValid() && + data_->resource_name_.empty(); +} + +bool +Jid::IsFull() const { + return IsValid() && + !data_->resource_name_.empty(); +} + +Jid +Jid::BareJid() const { + if (!IsValid()) + return Jid(); + if (!IsFull()) + return *this; + return Jid(data_->node_name_, data_->domain_name_, STR_EMPTY); +} + +#if 0 +void +Jid::set_node(const std::string & node_name) { + data_->node_name_ = node_name; +} +void +Jid::set_domain(const std::string & domain_name) { + data_->domain_name_ = domain_name; +} +void +Jid::set_resource(const std::string & res_name) { + data_->resource_name_ = res_name; +} +#endif + +bool +Jid::BareEquals(const Jid & other) const { + return (other.data_ == data_ || + data_ != NULL && + other.data_ != NULL && + other.data_->node_name_ == data_->node_name_ && + other.data_->domain_name_ == data_->domain_name_); +} + +bool +Jid::operator==(const Jid & other) const { + return (other.data_ == data_ || + data_ != NULL && + other.data_ != NULL && + other.data_->node_name_ == data_->node_name_ && + other.data_->domain_name_ == data_->domain_name_ && + other.data_->resource_name_ == data_->resource_name_); +} + +int +Jid::Compare(const Jid & other) const { + if (other.data_ == data_) + return 0; + if (data_ == NULL) + return -1; + if (other.data_ == NULL) + return 1; + + int compare_result; + compare_result = data_->node_name_.compare(other.data_->node_name_); + if (0 != compare_result) + return compare_result; + compare_result = data_->domain_name_.compare(other.data_->domain_name_); + if (0 != compare_result) + return compare_result; + compare_result = data_->resource_name_.compare(other.data_->resource_name_); + return compare_result; +} + + +// --- JID parsing code: --- + +// Checks and normalizes the node part of a JID. +std::string +Jid::prepNode(const std::string str, std::string::const_iterator start, + std::string::const_iterator end, bool *valid) { + *valid = false; + std::string result; + + for (std::string::const_iterator i = start; i < end; i++) { + bool char_valid = true; + char ch = *i; + if (ch <= 0x7F) { + result += prepNodeAscii(ch, &char_valid); + } + else { + // TODO: implement the correct stringprep protocol for these + result += tolower(ch); + } + if (!char_valid) { + return STR_EMPTY; + } + } + + if (result.length() > 1023) { + return STR_EMPTY; + } + *valid = true; + return result; +} + + +// Returns the appropriate mapping for an ASCII character in a node. +char +Jid::prepNodeAscii(char ch, bool *valid) { + *valid = true; + switch (ch) { + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + return (char)(ch + ('a' - 'A')); + + case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: + case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: + case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11: + case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: + case ' ': case '&': case '/': case ':': case '<': case '>': case '@': + case '\"': case '\'': + case 0x7F: + *valid = false; + return 0; + + default: + return ch; + } +} + + +// Checks and normalizes the resource part of a JID. +std::string +Jid::prepResource(const std::string str, std::string::const_iterator start, + std::string::const_iterator end, bool *valid) { + *valid = false; + std::string result; + + for (std::string::const_iterator i = start; i < end; i++) { + bool char_valid = true; + char ch = *i; + if (ch <= 0x7F) { + result += prepResourceAscii(ch, &char_valid); + } + else { + // TODO: implement the correct stringprep protocol for these + result += ch; + } + } + + if (result.length() > 1023) { + return STR_EMPTY; + } + *valid = true; + return result; +} + +// Returns the appropriate mapping for an ASCII character in a resource. +char +Jid::prepResourceAscii(char ch, bool *valid) { + *valid = true; + switch (ch) { + case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: + case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: + case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11: + case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: + case 0x7F: + *valid = false; + return 0; + + default: + return ch; + } +} + +// Checks and normalizes the domain part of a JID. +std::string +Jid::prepDomain(const std::string str, std::string::const_iterator start, + std::string::const_iterator end, bool *valid) { + *valid = false; + std::string result; + + // TODO: if the domain contains a ':', then we should parse it + // as an IPv6 address rather than giving an error about illegal domain. + prepDomain(str, start, end, &result, valid); + if (!*valid) { + return STR_EMPTY; + } + + if (result.length() > 1023) { + return STR_EMPTY; + } + *valid = true; + return result; +} + + +// Checks and normalizes an IDNA domain. +void +Jid::prepDomain(const std::string str, std::string::const_iterator start, + std::string::const_iterator end, std::string *buf, bool *valid) { + *valid = false; + std::string::const_iterator last = start; + for (std::string::const_iterator i = start; i < end; i++) { + bool label_valid = true; + char ch = *i; + switch (ch) { + case 0x002E: +#if 0 // FIX: This isn't UTF-8-aware. + case 0x3002: + case 0xFF0E: + case 0xFF61: +#endif + prepDomainLabel(str, last, i, buf, &label_valid); + *buf += '.'; + last = i + 1; + break; + } + if (!label_valid) { + return; + } + } + prepDomainLabel(str, last, end, buf, valid); +} + +// Checks and normalizes a domain label. +void +Jid::prepDomainLabel(const std::string str, std::string::const_iterator start, + std::string::const_iterator end, std::string *buf, bool *valid) { + *valid = false; + + int startLen = buf->length(); + for (std::string::const_iterator i = start; i < end; i++) { + bool char_valid = true; + char ch = *i; + if (ch <= 0x7F) { + *buf += prepDomainLabelAscii(ch, &char_valid); + } + else { + // TODO: implement ToASCII for these + *buf += ch; + } + if (!char_valid) { + return; + } + } + + int count = buf->length() - startLen; + if (count == 0) { + return; + } + else if (count > 63) { + return; + } + + // Is this check needed? See comment in prepDomainLabelAscii. + if ((*buf)[startLen] == '-') { + return; + } + if ((*buf)[buf->length() - 1] == '-') { + return; + } + *valid = true; +} + + +// Returns the appropriate mapping for an ASCII character in a domain label. +char +Jid::prepDomainLabelAscii(char ch, bool *valid) { + *valid = true; + // TODO: A literal reading of the spec seems to say that we do + // not need to check for these illegal characters (an "internationalized + // domain label" runs ToASCII with UseSTD3... set to false). But that + // can't be right. We should at least be checking that there are no '/' + // or '@' characters in the domain. Perhaps we should see what others + // do in this case. + + switch (ch) { + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + return (char)(ch + ('a' - 'A')); + + case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: + case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: + case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11: + case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: + case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: + case 0x1E: case 0x1F: case 0x20: case 0x21: case 0x22: case 0x23: + case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: + case 0x2A: case 0x2B: case 0x2C: case 0x2E: case 0x2F: case 0x3A: + case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: case 0x40: + case 0x5B: case 0x5C: case 0x5D: case 0x5E: case 0x5F: case 0x60: + case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F: + *valid = false; + return 0; + + default: + return ch; + } +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/jid.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/jid.h new file mode 100644 index 00000000..ae7944bf --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/jid.h @@ -0,0 +1,144 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ +#ifndef _jid_h_ +#define _jid_h_ + +#include +#include "talk/xmllite/xmlconstants.h" + +namespace buzz { + +//! The Jid class encapsulates and provides parsing help for Jids +//! A Jid consists of three parts. The node, the domain and the resource. +//! +//! node@domain/resource +//! +//! The node and resource are both optional. A valid jid is defined to have +//! a domain. A bare jid is defined to not have a resource and a full jid +//! *does* have a resource. +class Jid { +public: + explicit Jid(); + explicit Jid(const std::string & jid_string); + explicit Jid(const std::string & node_name, + const std::string & domain_name, + const std::string & resource_name); + explicit Jid(bool special, const std::string & special_string); + Jid(const Jid & jid) : data_(jid.data_) { + if (data_ != NULL) { + data_->AddRef(); + } + } + Jid & operator=(const Jid & jid) { + if (jid.data_ != NULL) { + jid.data_->AddRef(); + } + if (data_ != NULL) { + data_->Release(); + } + data_ = jid.data_; + return *this; + } + ~Jid() { + if (data_ != NULL) { + data_->Release(); + } + } + + + const std::string & node() const { return !data_ ? STR_EMPTY : data_->node_name_; } + // void set_node(const std::string & node_name); + const std::string & domain() const { return !data_ ? STR_EMPTY : data_->domain_name_; } + // void set_domain(const std::string & domain_name); + const std::string & resource() const { return !data_ ? STR_EMPTY : data_->resource_name_; } + // void set_resource(const std::string & res_name); + + std::string Str() const; + Jid BareJid() const; + + bool IsValid() const; + bool IsBare() const; + bool IsFull() const; + + bool BareEquals(const Jid & other) const; + + bool operator==(const Jid & other) const; + bool operator!=(const Jid & other) const { return !operator==(other); } + + bool operator<(const Jid & other) const { return Compare(other) < 0; }; + bool operator>(const Jid & other) const { return Compare(other) > 0; }; + + int Compare(const Jid & other) const; + + +private: + + static std::string prepNode(const std::string str, + std::string::const_iterator start, std::string::const_iterator end, + bool *valid); + static char prepNodeAscii(char ch, bool *valid); + static std::string prepResource(const std::string str, + std::string::const_iterator start, std::string::const_iterator end, + bool *valid); + static char prepResourceAscii(char ch, bool *valid); + static std::string prepDomain(const std::string str, + std::string::const_iterator start, std::string::const_iterator end, + bool *valid); + static void prepDomain(const std::string str, + std::string::const_iterator start, std::string::const_iterator end, + std::string *buf, bool *valid); + static void prepDomainLabel(const std::string str, + std::string::const_iterator start, std::string::const_iterator end, + std::string *buf, bool *valid); + static char prepDomainLabelAscii(char ch, bool *valid); + + class Data { + public: + Data() : refcount_(1) {} + Data(const std::string & node, const std::string &domain, const std::string & resource) : + node_name_(node), + domain_name_(domain), + resource_name_(resource), + refcount_(1) {} + const std::string node_name_; + const std::string domain_name_; + const std::string resource_name_; + + void AddRef() { refcount_++; } + void Release() { if (!--refcount_) delete this; } + private: + int refcount_; + }; + + Data * data_; +}; + +} + + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/plainsaslhandler.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/plainsaslhandler.h new file mode 100644 index 00000000..659820f5 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/plainsaslhandler.h @@ -0,0 +1,80 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _PLAINSASLHANDLER_H_ +#define _PLAINSASLHANDLER_H_ + +#include "talk/xmpp/saslhandler.h" +#include + +namespace buzz { + +class PlainSaslHandler : public SaslHandler { +public: + PlainSaslHandler(const Jid & jid, const XmppPassword & password) : + jid_(jid), password_(password) {} + + virtual ~PlainSaslHandler() {} + + // Should pick the best method according to this handler + // returns the empty string if none are suitable + virtual std::string ChooseBestSaslMechanism(const std::vector & mechanisms, bool encrypted) { + + // Do not send @google.com passwords unencrypted + if (!encrypted && jid_.domain() == "google.com") { + return ""; + } + + std::vector::const_iterator it = std::find(mechanisms.begin(), mechanisms.end(), "PLAIN"); + if (it == mechanisms.end()) { + return ""; + } + else { + return "PLAIN"; + } + } + + // Creates a SaslMechanism for the given mechanism name (you own it + // once you get it). If not handled, return NULL. + virtual SaslMechanism * CreateSaslMechanism(const std::string & mechanism) { + if (mechanism == "PLAIN") { + return new SaslPlainMechanism(jid_, password_); + } + return NULL; + } + +private: + Jid jid_; + XmppPassword password_; + +}; + + +} + +#endif + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/prexmppauth.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/prexmppauth.h new file mode 100644 index 00000000..8d2aa9d4 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/prexmppauth.h @@ -0,0 +1,85 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _PREXMPPAUTH_H_ +#define _PREXMPPAUTH_H_ + +#include "talk/base/sigslot.h" +#include "talk/xmpp/saslhandler.h" +#include "talk/xmpp/xmpppassword.h" + +namespace cricket { + class SocketAddress; +} + +namespace buzz { + +class Jid; +class SaslMechanism; + +class CaptchaChallenge { + public: + CaptchaChallenge() : captcha_needed_(false) {} + CaptchaChallenge(const std::string& token, const std::string& url) + : captcha_needed_(true), captcha_token_(token), captcha_image_url_(url) { + } + + bool captcha_needed() const { return captcha_needed_; } + const std::string& captcha_token() const { return captcha_token_; } + + // This url is relative to the gaia server. Once we have better tools + // for cracking URLs, we should probably make this a full URL + const std::string& captcha_image_url() const { return captcha_image_url_; } + + private: + bool captcha_needed_; + std::string captcha_token_; + std::string captcha_image_url_; +}; + +class PreXmppAuth : public SaslHandler { +public: + virtual ~PreXmppAuth() {} + + virtual void StartPreXmppAuth( + const Jid & jid, + const cricket::SocketAddress & server, + const XmppPassword & pass, + const std::string & auth_cookie) = 0; + + sigslot::signal0<> SignalAuthDone; + + virtual bool IsAuthDone() = 0; + virtual bool IsAuthorized() = 0; + virtual bool HadError() = 0; + virtual CaptchaChallenge GetCaptchaChallenge() = 0; + virtual std::string GetAuthCookie() = 0; +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslcookiemechanism.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslcookiemechanism.h new file mode 100644 index 00000000..a6630d90 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslcookiemechanism.h @@ -0,0 +1,67 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _SASLCOOKIEMECHANISM_H_ +#define _SASLCOOKIEMECHANISM_H_ + +#include "talk/xmpp/saslmechanism.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmpp/constants.h" + +namespace buzz { + +class SaslCookieMechanism : public SaslMechanism { + +public: + SaslCookieMechanism(const std::string & mechanism, const std::string & username, const std::string & cookie) : + mechanism_(mechanism), username_(username), cookie_(cookie) {} + + virtual std::string GetMechanismName() { return mechanism_; } + + virtual XmlElement * StartSaslAuth() { + // send initial request + XmlElement * el = new XmlElement(QN_SASL_AUTH, true); + el->AddAttr(QN_MECHANISM, mechanism_); + + std::string credential; + credential.append("\0", 1); + credential.append(username_); + credential.append("\0", 1); + credential.append(cookie_); + el->AddText(Base64Encode(credential)); + return el; + } + +private: + std::string mechanism_; + std::string username_; + std::string cookie_; +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslhandler.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslhandler.h new file mode 100644 index 00000000..b57d3baf --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslhandler.h @@ -0,0 +1,59 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _SASLHANDLER_H_ +#define _SASLHANDLER_H_ + +#include + +namespace buzz { + +class XmlElement; +class SaslMechanism; + +// Creates mechanisms to deal with a given mechanism +class SaslHandler { + +public: + + // Intended to be subclassed + virtual ~SaslHandler() {} + + // Should pick the best method according to this handler + // returns the empty string if none are suitable + virtual std::string ChooseBestSaslMechanism(const std::vector & mechanisms, bool encrypted) = 0; + + // Creates a SaslMechanism for the given mechanism name (you own it + // once you get it). + // If not handled, return NULL. + virtual SaslMechanism * CreateSaslMechanism(const std::string & mechanism) = 0; +}; + +} + +#endif + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslmechanism.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslmechanism.cc new file mode 100644 index 00000000..092df104 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslmechanism.cc @@ -0,0 +1,68 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/base/base64.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmpp/constants.h" +#include "talk/xmpp/saslmechanism.h" + +namespace buzz { + +XmlElement * +SaslMechanism::StartSaslAuth() { + return new XmlElement(QN_SASL_AUTH, true); +} + +XmlElement * +SaslMechanism::HandleSaslChallenge(const XmlElement * challenge) { + return new XmlElement(QN_SASL_ABORT, true); +} + +void +SaslMechanism::HandleSaslSuccess(const XmlElement * success) { +} + +void +SaslMechanism::HandleSaslFailure(const XmlElement * failure) { +} + +std::string +SaslMechanism::Base64Encode(const std::string & plain) { + return Base64::encode(plain); +} + +std::string +SaslMechanism::Base64Decode(const std::string & encoded) { + return Base64::decode(encoded); +} + +std::string +SaslMechanism::Base64EncodeFromArray(const char * plain, size_t length) { + return Base64::encodeFromArray(plain, length); +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslmechanism.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslmechanism.h new file mode 100644 index 00000000..f2e5adce --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslmechanism.h @@ -0,0 +1,74 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _SASLMECHANISM_H_ +#define _SASLMECHANISM_H_ + +#include + +namespace buzz { + +class XmlElement; + + +// Defines a mechnanism to do SASL authentication. +// Subclass instances should have a self-contained way to present +// credentials. +class SaslMechanism { + +public: + + // Intended to be subclassed + virtual ~SaslMechanism() {} + + // Should return the name of the SASL mechanism, e.g., "PLAIN" + virtual std::string GetMechanismName() = 0; + + // Should generate the initial "auth" request. Default is just . + virtual XmlElement * StartSaslAuth(); + + // Should respond to a SASL "" request. Default is + // to abort (for mechanisms that do not do challenge-response) + virtual XmlElement * HandleSaslChallenge(const XmlElement * challenge); + + // Notification of a SASL "". Sometimes information + // is passed on success. + virtual void HandleSaslSuccess(const XmlElement * success); + + // Notification of a SASL "". Sometimes information + // for the user is passed on failure. + virtual void HandleSaslFailure(const XmlElement * failure); + +protected: + static std::string Base64Encode(const std::string & plain); + static std::string Base64Decode(const std::string & encoded); + static std::string Base64EncodeFromArray(const char * plain, size_t length); +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslplainmechanism.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslplainmechanism.h new file mode 100644 index 00000000..7e0b0562 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/saslplainmechanism.h @@ -0,0 +1,65 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _SASLPLAINMECHANISM_H_ +#define _SASLPLAINMECHANISM_H_ + +#include "talk/xmpp/saslmechanism.h" +#include "talk/xmpp/xmpppassword.h" + +namespace buzz { + +class SaslPlainMechanism : public SaslMechanism { + +public: + SaslPlainMechanism(const buzz::Jid user_jid, const XmppPassword & password) : + user_jid_(user_jid), password_(password) {} + + virtual std::string GetMechanismName() { return "PLAIN"; } + + virtual XmlElement * StartSaslAuth() { + // send initial request + XmlElement * el = new XmlElement(QN_SASL_AUTH, true); + el->AddAttr(QN_MECHANISM, "PLAIN"); + + FormatXmppPassword credential; + credential.Append("\0", 1); + credential.Append(user_jid_.Str()); + credential.Append("\0", 1); + credential.Append(&password_); + el->AddText(Base64EncodeFromArray(credential.GetData(), credential.GetLength())); + return el; + } + +private: + Jid user_jid_; + XmppPassword password_; +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppclient.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppclient.cc new file mode 100644 index 00000000..959b6f88 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppclient.cc @@ -0,0 +1,372 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "xmppclient.h" +#include "xmpptask.h" +#include "talk/xmpp/constants.h" +#include "talk/base/sigslot.h" +#include "talk/xmpp/saslplainmechanism.h" +#include "talk/xmpp/prexmppauth.h" +#include "talk/base/scoped_ptr.h" +#include "talk/xmpp/plainsaslhandler.h" + +namespace buzz { + +Task * +XmppClient::GetParent(int code) { + if (code == XMPP_CLIENT_TASK_CODE) + return this; + else + return Task::GetParent(code); +} + +class XmppClient::Private : + public sigslot::has_slots<>, + public XmppSessionHandler, + public XmppOutputHandler { +public: + + Private(XmppClient * client) : + client_(client), + socket_(NULL), + engine_(NULL), + proxy_port_(0), + pre_engine_error_(XmppEngine::ERROR_NONE), + signal_closed_(false) {} + + // the owner + XmppClient * const client_; + + // the two main objects + scoped_ptr socket_; + scoped_ptr engine_; + scoped_ptr pre_auth_; + XmppPassword pass_; + std::string auth_cookie_; + cricket::SocketAddress server_; + std::string proxy_host_; + int proxy_port_; + XmppEngine::Error pre_engine_error_; + CaptchaChallenge captcha_challenge_; + bool signal_closed_; + + // implementations of interfaces + void OnStateChange(int state); + void WriteOutput(const char * bytes, size_t len); + void StartTls(const std::string & domainname); + void CloseConnection(); + + // slots for socket signals + void OnSocketConnected(); + void OnSocketRead(); + void OnSocketClosed(); +}; + +XmppReturnStatus +XmppClient::Connect(const XmppClientSettings & settings, AsyncSocket * socket, PreXmppAuth * pre_auth) { + if (socket == NULL) + return XMPP_RETURN_BADARGUMENT; + if (d_->socket_.get() != NULL) + return XMPP_RETURN_BADSTATE; + + d_->socket_.reset(socket); + + d_->socket_->SignalConnected.connect(d_.get(), &Private::OnSocketConnected); + d_->socket_->SignalRead.connect(d_.get(), &Private::OnSocketRead); + d_->socket_->SignalClosed.connect(d_.get(), &Private::OnSocketClosed); + + d_->engine_.reset(XmppEngine::Create()); + d_->engine_->SetSessionHandler(d_.get()); + d_->engine_->SetOutputHandler(d_.get()); + d_->engine_->SetUser(buzz::Jid(settings.user(), settings.host(), STR_EMPTY)); + if (!settings.resource().empty()) { + d_->engine_->SetRequestedResource(settings.resource()); + } + d_->engine_->SetUseTls(settings.use_tls()); + + + d_->pass_ = settings.pass(); + d_->auth_cookie_ = settings.auth_cookie(); + d_->server_ = settings.server(); + d_->proxy_host_ = settings.proxy_host(); + d_->proxy_port_ = settings.proxy_port(); + d_->pre_auth_.reset(pre_auth); + + return XMPP_RETURN_OK; +} + +XmppEngine::State +XmppClient::GetState() { + if (d_->engine_.get() == NULL) + return XmppEngine::STATE_NONE; + return d_->engine_->GetState(); +} + +XmppEngine::Error +XmppClient::GetError() { + if (d_->engine_.get() == NULL) + return XmppEngine::ERROR_NONE; + if (d_->pre_engine_error_ != XmppEngine::ERROR_NONE) + return d_->pre_engine_error_; + return d_->engine_->GetError(); +} + +CaptchaChallenge XmppClient::GetCaptchaChallenge() { + if (d_->engine_.get() == NULL) + return CaptchaChallenge(); + return d_->captcha_challenge_; +} + +std::string +XmppClient::GetAuthCookie() { + if (d_->engine_.get() == NULL) + return ""; + return d_->auth_cookie_; +} + +static void +ForgetPassword(std::string & to_erase) { + size_t len = to_erase.size(); + for (size_t i = 0; i < len; i++) { + // get rid of characters + to_erase[i] = 'x'; + } + // get rid of length + to_erase.erase(); +} + +int +XmppClient::ProcessStart() { + if (d_->pre_auth_.get()) { + d_->pre_auth_->SignalAuthDone.connect(this, &XmppClient::OnAuthDone); + d_->pre_auth_->StartPreXmppAuth( + d_->engine_->GetUser(), d_->server_, d_->pass_, d_->auth_cookie_); + d_->pass_.Clear(); // done with this; + return STATE_PRE_XMPP_LOGIN; + } + else { + d_->engine_->SetSaslHandler(new PlainSaslHandler( + d_->engine_->GetUser(), d_->pass_)); + d_->pass_.Clear(); // done with this; + return STATE_START_XMPP_LOGIN; + } +} + +void +XmppClient::OnAuthDone() { + Wake(); +} + +int +XmppClient::ProcessCookieLogin() { + // Don't know how this could happen, but crash reports show it as NULL + if (!d_->pre_auth_.get()) { + d_->pre_engine_error_ = XmppEngine::ERROR_AUTH; + EnsureClosed(); + return STATE_ERROR; + } + + // Wait until pre authentication is done is done + if (!d_->pre_auth_->IsAuthDone()) + return STATE_BLOCKED; + + if (!d_->pre_auth_->IsAuthorized()) { + // maybe split out a case when gaia is down? + if (d_->pre_auth_->HadError()) { + d_->pre_engine_error_ = XmppEngine::ERROR_AUTH; + } + else { + d_->pre_engine_error_ = XmppEngine::ERROR_UNAUTHORIZED; + d_->captcha_challenge_ = d_->pre_auth_->GetCaptchaChallenge(); + } + d_->pre_auth_.reset(NULL); // done with this + EnsureClosed(); + return STATE_ERROR; + } + + // Save auth cookie as a result + d_->auth_cookie_ = d_->pre_auth_->GetAuthCookie(); + + // transfer ownership of pre_auth_ to engine + d_->engine_->SetSaslHandler(d_->pre_auth_.release()); + + return STATE_START_XMPP_LOGIN; +} + +int +XmppClient::ProcessStartXmppLogin() { + // Done with pre-connect tasks - connect! + if (!d_->socket_->Connect(d_->server_)) { + EnsureClosed(); + return STATE_ERROR; + } + + return STATE_RESPONSE; +} + +int +XmppClient::ProcessResponse() { + // Hang around while we are connected. + if (!delivering_signal_ && (d_->engine_.get() == NULL || + d_->engine_->GetState() == XmppEngine::STATE_CLOSED)) + return STATE_DONE; + return STATE_BLOCKED; +} + +XmppReturnStatus +XmppClient::Disconnect() { + if (d_->socket_.get() == NULL) + return XMPP_RETURN_BADSTATE; + d_->engine_->Disconnect(); + return XMPP_RETURN_OK; +} + +XmppClient::XmppClient(Task * parent) : Task(parent), + delivering_signal_(false) { + d_.reset(new Private(this)); +} + +XmppClient::~XmppClient() {} + +const Jid & +XmppClient::jid() { + return d_->engine_->FullJid(); +} + + +std::string +XmppClient::NextId() { + return d_->engine_->NextId(); +} + +XmppReturnStatus +XmppClient::SendStanza(const XmlElement * stanza) { + return d_->engine_->SendStanza(stanza); +} + +XmppReturnStatus +XmppClient::SendStanzaError(const XmlElement * old_stanza, XmppStanzaError xse, const std::string & message) { + return d_->engine_->SendStanzaError(old_stanza, xse, message); +} + +XmppReturnStatus +XmppClient::SendRaw(const std::string & text) { + return d_->engine_->SendRaw(text); +} + +XmppEngine* +XmppClient::engine() { + return d_->engine_.get(); +} + +void +XmppClient::Private::OnSocketConnected() { + engine_->Connect(); +} + +void +XmppClient::Private::OnSocketRead() { + char bytes[4096]; + size_t bytes_read; + for (;;) { + if (!socket_->Read(bytes, sizeof(bytes), &bytes_read)) { + // TODO: deal with error information + return; + } + + if (bytes_read == 0) + return; + +//#ifdef _DEBUG + client_->SignalLogInput(bytes, bytes_read); +//#endif + + engine_->HandleInput(bytes, bytes_read); + } +} + +void +XmppClient::Private::OnSocketClosed() { + engine_->ConnectionClosed(); +} + +void +XmppClient::Private::OnStateChange(int state) { + if (state == XmppEngine::STATE_CLOSED) { + client_->EnsureClosed(); + } + else { + client_->SignalStateChange((XmppEngine::State)state); + } + client_->Wake(); +} + +void +XmppClient::Private::WriteOutput(const char * bytes, size_t len) { + +//#ifdef _DEBUG + client_->SignalLogOutput(bytes, len); +//#endif + + socket_->Write(bytes, len); + // TODO: deal with error information +} + +void +XmppClient::Private::StartTls(const std::string & domain) { +#if defined(FEATURE_ENABLE_SSL) + socket_->StartTls(domain); +#endif +} + +void +XmppClient::Private::CloseConnection() { + socket_->Close(); +} + +void +XmppClient::AddXmppTask(XmppTask * task, XmppEngine::HandlerLevel level) { + d_->engine_->AddStanzaHandler(task, level); +} + +void +XmppClient::RemoveXmppTask(XmppTask * task) { + d_->engine_->RemoveStanzaHandler(task); +} + +void +XmppClient::EnsureClosed() { + if (!d_->signal_closed_) { + d_->signal_closed_ = true; + delivering_signal_ = true; + SignalStateChange(XmppEngine::STATE_CLOSED); + delivering_signal_ = false; + } +} + + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppclient.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppclient.h new file mode 100644 index 00000000..f8b4798c --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppclient.h @@ -0,0 +1,157 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _XMPPCLIENT_H_ +#define _XMPPCLIENT_H_ + +#include +#include "talk/base/basicdefs.h" +#include "talk/base/sigslot.h" +#include "talk/xmpp/xmppengine.h" +#include "talk/xmpp/asyncsocket.h" +#include "talk/xmpp/xmppclientsettings.h" +#include "talk/base/task.h" + +namespace buzz { + +class XmppTask; +class PreXmppAuth; +class CaptchaChallenge; + +// Just some non-colliding number. Could have picked "1". +#define XMPP_CLIENT_TASK_CODE 0x366c1e47 + +///////////////////////////////////////////////////////////////////// +// +// XMPPCLIENT +// +///////////////////////////////////////////////////////////////////// +// +// See Task first. XmppClient is a parent task for XmppTasks. +// +// XmppClient is a task which is designed to be the parent task for +// all tasks that depend on a single Xmpp connection. If you want to, +// for example, listen for subscription requests forever, then your +// listener should be a task that is a child of the XmppClient that owns +// the connection you are using. XmppClient has all the utility methods +// that basically drill through to XmppEngine. +// +// XmppClient is just a wrapper for XmppEngine, and if I were writing it +// all over again, I would make XmppClient == XmppEngine. Why? +// XmppEngine needs tasks too, for example it has an XmppLoginTask which +// should just be the same kind of Task instead of an XmppEngine specific +// thing. It would help do certain things like GAIA auth cleaner. +// +///////////////////////////////////////////////////////////////////// + +class XmppClient : public Task, public sigslot::has_slots<> +{ +public: + XmppClient(Task * parent); + ~XmppClient(); + + XmppReturnStatus Connect(const XmppClientSettings & settings, + AsyncSocket * socket, + PreXmppAuth * preauth); + + virtual Task * GetParent(int code); + virtual int ProcessStart(); + virtual int ProcessResponse(); + XmppReturnStatus Disconnect(); + const Jid & jid(); + + sigslot::signal1 SignalStateChange; + XmppEngine::State GetState(); + XmppEngine::Error GetError(); + + // When there is an authentication error, we may have captcha info + // that the user can use to unlock their account + CaptchaChallenge GetCaptchaChallenge(); + + // When authentication is successful, this returns the service cookie + // (if we used GAIA authentication) + std::string GetAuthCookie(); + + std::string NextId(); + XmppReturnStatus SendStanza(const XmlElement *stanza); + XmppReturnStatus SendRaw(const std::string & text); + XmppReturnStatus SendStanzaError(const XmlElement * pelOriginal, + XmppStanzaError code, + const std::string & text); + + XmppEngine* engine(); + + sigslot::signal2 SignalLogInput; + sigslot::signal2 SignalLogOutput; + +private: + friend class XmppTask; + + void OnAuthDone(); + + // managed tasks and dispatching + void AddXmppTask(XmppTask *, XmppEngine::HandlerLevel); + void RemoveXmppTask(XmppTask *); + + sigslot::signal0<> SignalDisconnected; + +private: + // Internal state management + enum { + STATE_PRE_XMPP_LOGIN = STATE_NEXT, + STATE_START_XMPP_LOGIN = STATE_NEXT + 1, + }; + int Process(int state) { + switch (state) { + case STATE_PRE_XMPP_LOGIN: return ProcessCookieLogin(); + case STATE_START_XMPP_LOGIN: return ProcessStartXmppLogin(); + default: return Task::Process(state); + } + } + + std::string GetStateName(int state) const { + switch (state) { + case STATE_PRE_XMPP_LOGIN: return "PRE_XMPP_LOGIN"; + case STATE_START_XMPP_LOGIN: return "START_XMPP_LOGIN"; + default: return Task::GetStateName(state); + } + } + + int ProcessCookieLogin(); + int ProcessStartXmppLogin(); + void EnsureClosed(); + + class Private; + friend class Private; + scoped_ptr d_; + + bool delivering_signal_; +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppclientsettings.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppclientsettings.h new file mode 100644 index 00000000..9795682b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppclientsettings.h @@ -0,0 +1,94 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _XMPPCLIENTSETTINGS_H_ +#define _XMPPCLIENTSETTINGS_H_ + +#include "talk/p2p/base/port.h" +#include "talk/xmpp/xmpppassword.h" + +namespace buzz { + +class XmppClientSettings { +public: + XmppClientSettings() : + use_tls_(false), use_cookie_auth_(false), protocol_(cricket::PROTO_TCP), + proxy_(cricket::PROXY_NONE), proxy_port_(80), use_proxy_auth_(false) {} + + void set_user(const std::string & user) { user_ = user; } + void set_host(const std::string & host) { host_ = host; } + void set_pass(const XmppPassword & pass) { pass_ = pass; } + void set_auth_cookie(const std::string & cookie) { auth_cookie_ = cookie; } + void set_resource(const std::string & resource) { resource_ = resource; } + void set_use_tls(bool use_tls) { use_tls_ = use_tls; } + void set_server(const cricket::SocketAddress & server) { + server_ = server; + } + void set_protocol(cricket::ProtocolType protocol) { protocol_ = protocol; } + void set_proxy(cricket::ProxyType f) { proxy_ = f; } + void set_proxy_host(const std::string & host) { proxy_host_ = host; } + void set_proxy_port(int port) { proxy_port_ = port; }; + void set_use_proxy_auth(bool f) { use_proxy_auth_ = f; } + void set_proxy_user(const std::string & user) { proxy_user_ = user; } + void set_proxy_pass(const XmppPassword & pass) { proxy_pass_ = pass; } + + const std::string & user() const { return user_; } + const std::string & host() const { return host_; } + const XmppPassword & pass() const { return pass_; } + const std::string & auth_cookie() const { return auth_cookie_; } + const std::string & resource() const { return resource_; } + bool use_tls() const { return use_tls_; } + const cricket::SocketAddress & server() const { return server_; } + cricket::ProtocolType protocol() const { return protocol_; } + cricket::ProxyType proxy() const { return proxy_; } + const std::string & proxy_host() const { return proxy_host_; } + int proxy_port() const { return proxy_port_; } + bool use_proxy_auth() const { return use_proxy_auth_; } + const std::string & proxy_user() const { return proxy_user_; } + const XmppPassword & proxy_pass() const { return proxy_pass_; } + +private: + std::string user_; + std::string host_; + XmppPassword pass_; + std::string auth_cookie_; + std::string resource_; + bool use_tls_; + bool use_cookie_auth_; + cricket::SocketAddress server_; + cricket::ProtocolType protocol_; + cricket::ProxyType proxy_; + std::string proxy_host_; + int proxy_port_; + bool use_proxy_auth_; + std::string proxy_user_; + XmppPassword proxy_pass_; +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengine.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengine.h new file mode 100644 index 00000000..ef8f2ea8 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengine.h @@ -0,0 +1,332 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _xmppengine_h_ +#define _xmppengine_h_ + +// also part of the API +#include "talk/xmpp/jid.h" +#include "talk/xmllite/qname.h" +#include "talk/xmllite/xmlelement.h" + + +namespace buzz { + +class XmppEngine; +class SaslHandler; +typedef void * XmppIqCookie; + +//! XMPP stanza error codes. +//! Used in XmppEngine.SendStanzaError(). +enum XmppStanzaError { + XSE_BAD_REQUEST, + XSE_CONFLICT, + XSE_FEATURE_NOT_IMPLEMENTED, + XSE_FORBIDDEN, + XSE_GONE, + XSE_INTERNAL_SERVER_ERROR, + XSE_ITEM_NOT_FOUND, + XSE_JID_MALFORMED, + XSE_NOT_ACCEPTABLE, + XSE_NOT_ALLOWED, + XSE_PAYMENT_REQUIRED, + XSE_RECIPIENT_UNAVAILABLE, + XSE_REDIRECT, + XSE_REGISTRATION_REQUIRED, + XSE_SERVER_NOT_FOUND, + XSE_SERVER_TIMEOUT, + XSE_RESOURCE_CONSTRAINT, + XSE_SERVICE_UNAVAILABLE, + XSE_SUBSCRIPTION_REQUIRED, + XSE_UNDEFINED_CONDITION, + XSE_UNEXPECTED_REQUEST, +}; + +// XmppReturnStatus +// This is used by API functions to synchronously return status. +enum XmppReturnStatus { + XMPP_RETURN_OK, + XMPP_RETURN_BADARGUMENT, + XMPP_RETURN_BADSTATE, + XMPP_RETURN_PENDING, + XMPP_RETURN_UNEXPECTED, + XMPP_RETURN_NOTYETIMPLEMENTED, +}; + +//! Callback for socket output for an XmppEngine connection. +//! Register via XmppEngine.SetOutputHandler. An XmppEngine +//! can call back to this handler while it is processing +//! Connect, SendStanza, SendIq, Disconnect, or HandleInput. +class XmppOutputHandler { +public: + + //! Deliver the specified bytes to the XMPP socket. + virtual void WriteOutput(const char * bytes, size_t len) = 0; + + //! Initiate TLS encryption on the socket. + //! The implementation must verify that the SSL + //! certificate matches the given domainname. + virtual void StartTls(const std::string & domainname) = 0; + + //! Called when engine wants the connecton closed. + virtual void CloseConnection() = 0; +}; + +//! Callback to deliver engine state change notifications +//! to the object managing the engine. +class XmppSessionHandler { +public: + //! Called when engine changes state. Argument is new state. + virtual void OnStateChange(int state) = 0; +}; + +//! Callback to deliver stanzas to an Xmpp application module. +//! Register via XmppEngine.SetDefaultSessionHandler or via +//! XmppEngine.AddSessionHAndler. +class XmppStanzaHandler { +public: + + //! Process the given stanza. + //! The handler must return true if it has handled the stanza. + //! A false return value causes the stanza to be passed on to + //! the next registered handler. + virtual bool HandleStanza(const XmlElement * stanza) = 0; +}; + +//! Callback to deliver iq responses (results and errors). +//! Register while sending an iq via XmppEngine.SendIq. +//! Iq responses are routed to matching XmppIqHandlers in preference +//! to sending to any registered SessionHandlers. +class XmppIqHandler { +public: + //! Called to handle the iq response. + //! The response may be either a result or an error, and will have + //! an 'id' that matches the request and a 'from' that matches the + //! 'to' of the request. Called no more than once; once this is + //! called, the handler is automatically unregistered. + virtual void IqResponse(XmppIqCookie cookie, const XmlElement * pelStanza) = 0; +}; + +//! The XMPP connection engine. +//! This engine implements the client side of the 'core' XMPP protocol. +//! To use it, register an XmppOutputHandler to handle socket output +//! and pass socket input to HandleInput. Then application code can +//! set up the connection with a user, password, and other settings, +//! and then call Connect() to initiate the connection. +//! An application can listen for events and receive stanzas by +//! registering an XmppStanzaHandler via AddStanzaHandler(). +class XmppEngine { +public: + static XmppEngine * Create(); + virtual ~XmppEngine() {} + + //! Error codes. See GetError(). + enum Error { + ERROR_NONE = 0, //!< No error + ERROR_XML, //!< Malformed XML or encoding error + ERROR_STREAM, //!< XMPP stream error - see GetStreamError() + ERROR_VERSION, //!< XMPP version error + ERROR_UNAUTHORIZED, //!< User is not authorized (rejected credentials) + ERROR_TLS, //!< TLS could not be negotiated + ERROR_AUTH, //!< Authentication could not be negotiated + ERROR_BIND, //!< Resource or session binding could not be negotiated + ERROR_CONNECTION_CLOSED,//!< Connection closed by output handler. + ERROR_DOCUMENT_CLOSED, //!< Closed by + ERROR_SOCKET, //!< Socket error + }; + + //! States. See GetState(). + enum State { + STATE_NONE = 0, //!< Nonexistent state + STATE_START, //!< Initial state. + STATE_OPENING, //!< Exchanging stream headers, authenticating and so on. + STATE_OPEN, //!< Authenticated and bound. + STATE_CLOSED, //!< Session closed, possibly due to error. + }; + + // SOCKET INPUT AND OUTPUT ------------------------------------------------ + + //! Registers the handler for socket output + virtual XmppReturnStatus SetOutputHandler(XmppOutputHandler *pxoh) = 0; + + //! Provides socket input to the engine + virtual XmppReturnStatus HandleInput(const char * bytes, size_t len) = 0; + + //! Advises the engine that the socket has closed + virtual XmppReturnStatus ConnectionClosed() = 0; + + // SESSION SETUP --------------------------------------------------------- + + //! Indicates the (bare) JID for the user to use. + virtual XmppReturnStatus SetUser(const Jid & jid)= 0; + + //! Get the login (bare) JID. + virtual const Jid & GetUser() = 0; + + //! Provides different methods for credentials for login. + //! Takes ownership of this object; deletes when login is done + virtual XmppReturnStatus SetSaslHandler(SaslHandler * h) = 0; + + //! Sets whether TLS will be used within the connection (default true). + virtual XmppReturnStatus SetUseTls(bool useTls) = 0; + + //! Sets an alternate domain from which we allows TLS certificates. + //! This is for use in the case where a we want to allow a proxy to + //! serve up its own certificate rather than one owned by the underlying + //! domain. + virtual XmppReturnStatus SetTlsServerDomain(const std::string & proxy_domain) = 0; + + //! Gets whether TLS will be used within the connection. + virtual bool GetUseTls() = 0; + + //! Sets the request resource name, if any (optional). + //! Note that the resource name may be overridden by the server; after + //! binding, the actual resource name is available as part of FullJid(). + virtual XmppReturnStatus SetRequestedResource(const std::string& resource) = 0; + + //! Gets the request resource name. + virtual const std::string & GetRequestedResource() = 0; + + // SESSION MANAGEMENT --------------------------------------------------- + + //! Set callback for state changes. + virtual XmppReturnStatus SetSessionHandler(XmppSessionHandler* handler) = 0; + + //! Initiates the XMPP connection. + //! After supplying connection settings, call this once to initiate, + //! (optionally) encrypt, authenticate, and bind the connection. + virtual XmppReturnStatus Connect() = 0; + + //! The current engine state. + virtual State GetState() = 0; + + //! Returns true if the connection is encrypted (under TLS) + virtual bool IsEncrypted() = 0; + + //! The error code. + //! Consult this after XmppOutputHandler.OnClose(). + virtual Error GetError() = 0; + + //! The stream:error stanza, when the error is XMPP_ERROR_STREAM. + //! Notice the stanza returned is owned by the XmppEngine and + //! is deleted when the engine is destroyed. + virtual const XmlElement * GetStreamError() = 0; + + //! Closes down the connection. + //! Sends CloseConnection to output, and disconnects and registered + //! session handlers. After Disconnect completes, it is guaranteed + //! that no further callbacks will be made. + virtual XmppReturnStatus Disconnect() = 0; + + // APPLICATION USE ------------------------------------------------------- + + enum HandlerLevel { + HL_NONE = 0, + HL_PEEK, //!< Sees messages before all other processing; cannot abort + HL_SINGLE, //!< Watches for a single message, e.g., by id and sender + HL_SENDER, //!< Watches for a type of message from a specific sender + HL_TYPE, //!< Watches a type of message, e.g., all groupchat msgs + HL_ALL, //!< Watches all messages - gets last shot + HL_COUNT, //!< Count of handler levels + }; + + //! Adds a listener for session events. + //! Stanza delivery is chained to session handlers; the first to + //! return 'true' is the last to get each stanza. + virtual XmppReturnStatus AddStanzaHandler(XmppStanzaHandler* handler, HandlerLevel level = HL_PEEK) = 0; + + //! Removes a listener for session events. + virtual XmppReturnStatus RemoveStanzaHandler(XmppStanzaHandler* handler) = 0; + + //! Sends a stanza to the server. + virtual XmppReturnStatus SendStanza(const XmlElement * pelStanza) = 0; + + //! Sends raw text to the server + virtual XmppReturnStatus SendRaw(const std::string & text) = 0; + + //! Sends an iq to the server, and registers a callback for the result. + //! Returns the cookie passed to the result handler. + virtual XmppReturnStatus SendIq(const XmlElement* pelStanza, + XmppIqHandler* iq_handler, + XmppIqCookie* cookie) = 0; + + //! Unregisters an iq callback handler given its cookie. + //! No callback will come to this handler after it's unregistered. + virtual XmppReturnStatus RemoveIqHandler(XmppIqCookie cookie, + XmppIqHandler** iq_handler) = 0; + + + //! Forms and sends an error in response to the given stanza. + //! Swaps to and from, sets type to "error", and adds error information + //! based on the passed code. Text is optional and may be STR_EMPTY. + virtual XmppReturnStatus SendStanzaError(const XmlElement * pelOriginal, + XmppStanzaError code, + const std::string & text) = 0; + + //! The fullly bound JID. + //! This JID is only valid after binding has succeeded. If the value + //! is JID_NULL, the binding has not succeeded. + virtual const Jid & FullJid() = 0; + + //! The next unused iq id for this connection. + //! Call this when building iq stanzas, to ensure that each iq + //! gets its own unique id. + virtual std::string NextId() = 0; + +}; + +} + + +// Move these to a better location + +#define XMPP_FAILED(x) \ + ( (x) == buzz::XMPP_RETURN_OK ? false : true) \ + + +#define XMPP_SUCCEEDED(x) \ + ( (x) == buzz::XMPP_RETURN_OK ? true : false) \ + +#define IFR(x) \ + do { \ + xmpp_status = (x); \ + if (XMPP_FAILED(xmpp_status)) { \ + return xmpp_status; \ + } \ + } while (false) \ + + +#define IFC(x) \ + do { \ + xmpp_status = (x); \ + if (XMPP_FAILED(xmpp_status)) { \ + goto Cleanup; \ + } \ + } while (false) \ + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengineimpl.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengineimpl.cc new file mode 100644 index 00000000..173d711b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengineimpl.cc @@ -0,0 +1,480 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#define TRACK_ARRAY_ALLOC_PROBLEM + +#include +#include +#include +#include "talk/xmllite/xmlelement.h" +#include "talk/base/common.h" +#include "talk/xmpp/xmppengineimpl.h" +#include "talk/xmpp/xmpplogintask.h" +#include "talk/xmpp/constants.h" +#include "talk/xmllite/xmlprinter.h" +#include "talk/xmpp/saslhandler.h" +// #include "buzz/saslmechanism.h" + +#define new TRACK_NEW + +namespace buzz { + +static const std::string XMPP_CLIENT_NAMESPACES[] = { + "stream", "http://etherx.jabber.org/streams", + "", "jabber:client", +}; + +static const size_t XMPP_CLIENT_NAMESPACES_LEN = 4; + +XmppEngine * XmppEngine::Create() { + return new XmppEngineImpl(); +} + + +XmppEngineImpl::XmppEngineImpl() : + stanzaParseHandler_(this), + stanzaParser_(&stanzaParseHandler_), + engine_entered_(0), + user_jid_(JID_EMPTY), + password_(), + requested_resource_(STR_EMPTY), + tls_needed_(true), + login_task_(new XmppLoginTask(this)), + next_id_(0), + bound_jid_(JID_EMPTY), + state_(STATE_START), + encrypted_(false), + error_code_(ERROR_NONE), + stream_error_(NULL), + raised_reset_(false), + output_handler_(NULL), + session_handler_(NULL), + iq_entries_(new IqEntryVector()), + output_(new std::stringstream()), + sasl_handler_(NULL) { + for (int i = 0; i < HL_COUNT; i+= 1) { + stanza_handlers_[i].reset(new StanzaHandlerVector()); + } +} + +XmppEngineImpl::~XmppEngineImpl() { + DeleteIqCookies(); +} + +XmppReturnStatus +XmppEngineImpl::SetOutputHandler(XmppOutputHandler* output_handler) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + output_handler_ = output_handler; + + return XMPP_RETURN_OK; +} + +XmppReturnStatus +XmppEngineImpl::SetSessionHandler(XmppSessionHandler* session_handler) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + session_handler_ = session_handler; + + return XMPP_RETURN_OK; +} + +XmppReturnStatus +XmppEngineImpl::HandleInput(const char * bytes, size_t len) { + if (state_ < STATE_OPENING || state_ > STATE_OPEN) + return XMPP_RETURN_BADSTATE; + + EnterExit ee(this); + + stanzaParser_.Parse(bytes, len, false); + + return XMPP_RETURN_OK; +} + +XmppReturnStatus +XmppEngineImpl::ConnectionClosed() { + if (state_ != STATE_CLOSED) { + EnterExit ee(this); + // If told that connection closed and not already closed, + // then connection was unpexectedly dropped. + SignalError(ERROR_CONNECTION_CLOSED); + } + return XMPP_RETURN_OK; +} + +XmppReturnStatus +XmppEngineImpl::SetUseTls(bool useTls) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + tls_needed_ = useTls; + + return XMPP_RETURN_OK; +} + +XmppReturnStatus +XmppEngineImpl::SetTlsServerDomain(const std::string & tls_server_domain) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + tls_server_domain_= tls_server_domain; + + return XMPP_RETURN_OK; +} + +bool +XmppEngineImpl::GetUseTls() { + return tls_needed_; +} + +XmppReturnStatus +XmppEngineImpl::SetUser(const Jid & jid) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + user_jid_ = jid; + + return XMPP_RETURN_OK; +} + +const Jid & +XmppEngineImpl::GetUser() { + return user_jid_; +} + +XmppReturnStatus +XmppEngineImpl::SetSaslHandler(SaslHandler * sasl_handler) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + sasl_handler_.reset(sasl_handler); + return XMPP_RETURN_OK; +} + +XmppReturnStatus +XmppEngineImpl::SetRequestedResource(const std::string & resource) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + requested_resource_ = resource; + + return XMPP_RETURN_OK; +} + +const std::string & +XmppEngineImpl::GetRequestedResource() { + return requested_resource_; +} + +XmppReturnStatus +XmppEngineImpl::AddStanzaHandler(XmppStanzaHandler * stanza_handler, + XmppEngine::HandlerLevel level) { + if (state_ == STATE_CLOSED) + return XMPP_RETURN_BADSTATE; + + stanza_handlers_[level]->push_back(stanza_handler); + + return XMPP_RETURN_OK; +} + +XmppReturnStatus +XmppEngineImpl::RemoveStanzaHandler(XmppStanzaHandler * stanza_handler) { + + bool found = false; + + for (int level = 0; level < HL_COUNT; level += 1) { + StanzaHandlerVector::iterator new_end = + std::remove(stanza_handlers_[level]->begin(), + stanza_handlers_[level]->end(), + stanza_handler); + + if (new_end != stanza_handlers_[level]->end()) { + stanza_handlers_[level]->erase(new_end, stanza_handlers_[level]->end()); + found = true; + } + } + + if (!found) { + return XMPP_RETURN_BADARGUMENT; + } + + return XMPP_RETURN_OK; +} + +XmppReturnStatus +XmppEngineImpl::Connect() { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + EnterExit ee(this); + + // get the login task started + state_ = STATE_OPENING; + if (login_task_.get()) { + login_task_->IncomingStanza(NULL, false); + if (login_task_->IsDone()) + login_task_.reset(); + } + + return XMPP_RETURN_OK; +} + +XmppReturnStatus +XmppEngineImpl::SendStanza(const XmlElement * element) { + if (state_ == STATE_CLOSED) + return XMPP_RETURN_BADSTATE; + + EnterExit ee(this); + + if (login_task_.get()) { + // still handshaking - then outbound stanzas are queued + login_task_->OutgoingStanza(element); + } else { + // handshake done - send straight through + InternalSendStanza(element); + } + + return XMPP_RETURN_OK; +} + +XmppReturnStatus +XmppEngineImpl::SendRaw(const std::string & text) { + if (state_ == STATE_CLOSED || login_task_.get()) + return XMPP_RETURN_BADSTATE; + + EnterExit ee(this); + + (*output_) << text; + + return XMPP_RETURN_OK; +} + +std::string +XmppEngineImpl::NextId() { + std::stringstream ss; + ss << next_id_++; + return ss.str(); +} + +XmppReturnStatus +XmppEngineImpl::Disconnect() { + + if (state_ != STATE_CLOSED) { + EnterExit ee(this); + if (state_ == STATE_OPEN) + *output_ << ""; + state_ = STATE_CLOSED; + } + + return XMPP_RETURN_OK; +} + +void +XmppEngineImpl::IncomingStart(const XmlElement * pelStart) { + if (HasError() || raised_reset_) + return; + + if (login_task_.get()) { + // start-stream should go to login task + login_task_->IncomingStanza(pelStart, true); + if (login_task_->IsDone()) + login_task_.reset(); + } + else { + // if not logging in, it's an error to see a start + SignalError(ERROR_XML); + } +} + +void +XmppEngineImpl::IncomingStanza(const XmlElement * stanza) { + if (HasError() || raised_reset_) + return; + + if (stanza->Name() == QN_STREAM_ERROR) { + // Explicit XMPP stream error + SignalStreamError(stanza); + } else if (login_task_.get()) { + // Handle login handshake + login_task_->IncomingStanza(stanza, false); + if (login_task_->IsDone()) + login_task_.reset(); + } else if (HandleIqResponse(stanza)) { + // iq is handled by above call + } else { + // give every "peek" handler a shot at all stanzas + for (size_t i = 0; i < stanza_handlers_[HL_PEEK]->size(); i += 1) { + (*stanza_handlers_[HL_PEEK])[i]->HandleStanza(stanza); + } + + // give other handlers a shot in precedence order, stopping after handled + for (int level = HL_SINGLE; level <= HL_ALL; level += 1) { + for (size_t i = 0; i < stanza_handlers_[level]->size(); i += 1) { + if ((*stanza_handlers_[level])[i]->HandleStanza(stanza)) + goto Handled; + } + } + + // If nobody wants to handle a stanza then send back an error. + // Only do this for IQ stanzas as messages should probably just be dropped + // and presence stanzas should certainly be dropped. + std::string type = stanza->Attr(QN_TYPE); + if (stanza->Name() == QN_IQ && + !(type == "error" || type == "result")) { + SendStanzaError(stanza, XSE_FEATURE_NOT_IMPLEMENTED, STR_EMPTY); + } + } + Handled: + ; // handled - we're done +} + +void +XmppEngineImpl::IncomingEnd(bool isError) { + if (HasError() || raised_reset_) + return; + + SignalError(isError ? ERROR_XML : ERROR_DOCUMENT_CLOSED); +} + +void +XmppEngineImpl::InternalSendStart(const std::string & to) { + // send stream-beginning + // note, we put a \r\n at tne end fo the first line to cause non-XMPP + // line-oriented servers (e.g., Apache) to reveal themselves more quickly. + *output_ << "\r\n"; +} + +void +XmppEngineImpl::InternalSendStanza(const XmlElement * element) { + // It should really never be necessary to set a FROM attribute on a stanza. + // It is implied by the bind on the stream and if you get it wrong + // (by flipping from/to on a message?) the server will close the stream. + ASSERT(!element->HasAttr(QN_FROM)); + + // TODO: consider caching the XmlPrinter + XmlPrinter::PrintXml(output_.get(), element, + XMPP_CLIENT_NAMESPACES, XMPP_CLIENT_NAMESPACES_LEN); +} + +std::string +XmppEngineImpl::ChooseBestSaslMechanism(const std::vector & mechanisms, bool encrypted) { + return sasl_handler_->ChooseBestSaslMechanism(mechanisms, encrypted); +} + +SaslMechanism * +XmppEngineImpl::GetSaslMechanism(const std::string & name) { + return sasl_handler_->CreateSaslMechanism(name); +} + +void +XmppEngineImpl::SignalBound(const Jid & fullJid) { + if (state_ == STATE_OPENING) { + bound_jid_ = fullJid; + state_ = STATE_OPEN; + } +} + +void +XmppEngineImpl::SignalStreamError(const XmlElement * pelStreamError) { + if (state_ != STATE_CLOSED) { + stream_error_.reset(new XmlElement(*pelStreamError)); + SignalError(ERROR_STREAM); + } +} + +void +XmppEngineImpl::SignalError(Error errorCode) { + if (state_ != STATE_CLOSED) { + error_code_ = errorCode; + state_ = STATE_CLOSED; + } +} + +bool +XmppEngineImpl::HasError() { + return error_code_ != ERROR_NONE; +} + +void +XmppEngineImpl::StartTls(const std::string & domain) { + if (output_handler_) { + output_handler_->StartTls( + tls_server_domain_.empty() ? domain : tls_server_domain_); + encrypted_ = true; + } +} + +XmppEngineImpl::EnterExit::EnterExit(XmppEngineImpl* engine) + : engine_(engine), + state_(engine->state_), + error_(engine->error_code_) { + engine->engine_entered_ += 1; +} + +XmppEngineImpl::EnterExit::~EnterExit() { + XmppEngineImpl* engine = engine_; + + engine->engine_entered_ -= 1; + + bool closing = (engine->state_ != state_ && + engine->state_ == STATE_CLOSED); + bool flushing = closing || (engine->engine_entered_ == 0); + + if (engine->output_handler_ && flushing) { + std::string output = engine->output_->str(); + if (output.length() > 0) + engine->output_handler_->WriteOutput(output.c_str(), output.length()); + engine->output_->str(""); + + if (closing) { + engine->output_handler_->CloseConnection(); + engine->output_handler_ = 0; + } + } + + if (engine->engine_entered_) + return; + + if (engine->raised_reset_) { + engine->stanzaParser_.Reset(); + engine->raised_reset_ = false; + } + + if (engine->session_handler_) { + if (engine->state_ != state_) + engine->session_handler_->OnStateChange(engine->state_); + // Note: Handling of OnStateChange(CLOSED) should allow for the + // deletion of the engine, so no members should be accessed + // after this line. + } +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengineimpl.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengineimpl.h new file mode 100644 index 00000000..c36f168c --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengineimpl.h @@ -0,0 +1,262 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _xmppengineimpl_h_ +#define _xmppengineimpl_h_ + +#include +#include "talk/xmpp/xmppengine.h" +#include "talk/xmpp/xmppstanzaparser.h" + +namespace buzz { + +class XmppLoginTask; +class XmppEngine; +class XmppIqEntry; +class SaslHandler; +class SaslMechanism; + + +//! The XMPP connection engine. +//! This engine implements the client side of the 'core' XMPP protocol. +//! To use it, register an XmppOutputHandler to handle socket output +//! and pass socket input to HandleInput. Then application code can +//! set up the connection with a user, password, and other settings, +//! and then call Connect() to initiate the connection. +//! An application can listen for events and receive stanzas by +//! registering an XmppStanzaHandler via AddStanzaHandler(). +class XmppEngineImpl : public XmppEngine { +public: + XmppEngineImpl(); + virtual ~XmppEngineImpl(); + + // SOCKET INPUT AND OUTPUT ------------------------------------------------ + + //! Registers the handler for socket output + virtual XmppReturnStatus SetOutputHandler(XmppOutputHandler *pxoh); + + //! Provides socket input to the engine + virtual XmppReturnStatus HandleInput(const char * bytes, size_t len); + + //! Advises the engine that the socket has closed + virtual XmppReturnStatus ConnectionClosed(); + + // SESSION SETUP --------------------------------------------------------- + + //! Indicates the (bare) JID for the user to use. + virtual XmppReturnStatus SetUser(const Jid & jid); + + //! Get the login (bare) JID. + virtual const Jid & GetUser(); + + //! Indicates the autentication to use. Takes ownership of the object. + virtual XmppReturnStatus SetSaslHandler(SaslHandler * sasl_handler); + + //! Sets whether TLS will be used within the connection (default true). + virtual XmppReturnStatus SetUseTls(bool useTls); + + //! Sets an alternate domain from which we allows TLS certificates. + //! This is for use in the case where a we want to allow a proxy to + //! serve up its own certificate rather than one owned by the underlying + //! domain. + virtual XmppReturnStatus SetTlsServerDomain(const std::string & proxy_domain); + + //! Gets whether TLS will be used within the connection. + virtual bool GetUseTls(); + + //! Sets the request resource name, if any (optional). + //! Note that the resource name may be overridden by the server; after + //! binding, the actual resource name is available as part of FullJid(). + virtual XmppReturnStatus SetRequestedResource(const std::string& resource); + + //! Gets the request resource name. + virtual const std::string & GetRequestedResource(); + + // SESSION MANAGEMENT --------------------------------------------------- + + //! Set callback for state changes. + virtual XmppReturnStatus SetSessionHandler(XmppSessionHandler* handler); + + //! Initiates the XMPP connection. + //! After supplying connection settings, call this once to initiate, + //! (optionally) encrypt, authenticate, and bind the connection. + virtual XmppReturnStatus Connect(); + + //! The current engine state. + virtual State GetState() { return state_; } + + //! Returns true if the connection is encrypted (under TLS) + virtual bool IsEncrypted() { return encrypted_; } + + //! The error code. + //! Consult this after XmppOutputHandler.OnClose(). + virtual Error GetError() { return error_code_; } + + //! The stream:error stanza, when the error is XMPP_ERROR_STREAM. + //! Notice the stanza returned is owned by the XmppEngine and + //! is deleted when the engine is destroyed. + virtual const XmlElement * GetStreamError() { return stream_error_.get(); } + + //! Closes down the connection. + //! Sends CloseConnection to output, and disconnects and registered + //! session handlers. After Disconnect completes, it is guaranteed + //! that no further callbacks will be made. + virtual XmppReturnStatus Disconnect(); + + // APPLICATION USE ------------------------------------------------------- + + //! Adds a listener for session events. + //! Stanza delivery is chained to session handlers; the first to + //! return 'true' is the last to get each stanza. + virtual XmppReturnStatus AddStanzaHandler(XmppStanzaHandler* handler, + XmppEngine::HandlerLevel level); + + //! Removes a listener for session events. + virtual XmppReturnStatus RemoveStanzaHandler(XmppStanzaHandler* handler); + + //! Sends a stanza to the server. + virtual XmppReturnStatus SendStanza(const XmlElement * pelStanza); + + //! Sends raw text to the server + virtual XmppReturnStatus SendRaw(const std::string & text); + + //! Sends an iq to the server, and registers a callback for the result. + //! Returns the cookie passed to the result handler. + virtual XmppReturnStatus SendIq(const XmlElement* pelStanza, + XmppIqHandler* iq_handler, + XmppIqCookie* cookie); + + //! Unregisters an iq callback handler given its cookie. + //! No callback will come to this handler after it's unregistered. + virtual XmppReturnStatus RemoveIqHandler(XmppIqCookie cookie, + XmppIqHandler** iq_handler); + + //! Forms and sends an error in response to the given stanza. + //! Swaps to and from, sets type to "error", and adds error information + //! based on the passed code. Text is optional and may be STR_EMPTY. + virtual XmppReturnStatus SendStanzaError(const XmlElement * pelOriginal, + XmppStanzaError code, + const std::string & text); + + //! The fullly bound JID. + //! This JID is only valid after binding has succeeded. If the value + //! is JID_NULL, the binding has not succeeded. + virtual const Jid & FullJid() { return bound_jid_; } + + //! The next unused iq id for this connection. + //! Call this when building iq stanzas, to ensure that each iq + //! gets its own unique id. + virtual std::string NextId(); + +private: + friend class XmppLoginTask; + friend class XmppIqEntry; + + void IncomingStanza(const XmlElement *pelStanza); + void IncomingStart(const XmlElement *pelStanza); + void IncomingEnd(bool isError); + + void InternalSendStart(const std::string & domainName); + void InternalSendStanza(const XmlElement * pelStanza); + std::string ChooseBestSaslMechanism(const std::vector & mechanisms, bool encrypted); + SaslMechanism * GetSaslMechanism(const std::string & name); + void SignalBound(const Jid & fullJid); + void SignalStreamError(const XmlElement * pelStreamError); + void SignalError(Error errorCode); + bool HasError(); + void DeleteIqCookies(); + bool HandleIqResponse(const XmlElement * element); + void StartTls(const std::string & domain); + void RaiseReset() { raised_reset_ = true; } + + class StanzaParseHandler : public XmppStanzaParseHandler { + public: + StanzaParseHandler(XmppEngineImpl * outer) : outer_(outer) {} + virtual void StartStream(const XmlElement * pelStream) + { outer_->IncomingStart(pelStream); } + virtual void Stanza(const XmlElement * pelStanza) + { outer_->IncomingStanza(pelStanza); } + virtual void EndStream() + { outer_->IncomingEnd(false); } + virtual void XmlError() + { outer_->IncomingEnd(true); } + private: + XmppEngineImpl * const outer_; + }; + + class EnterExit { + public: + EnterExit(XmppEngineImpl* engine); + ~EnterExit(); + private: + XmppEngineImpl* engine_; + State state_; + Error error_; + + }; + + friend class StanzaParseHandler; + friend class EnterExit; + + StanzaParseHandler stanzaParseHandler_; + XmppStanzaParser stanzaParser_; + + + // state + int engine_entered_; + Jid user_jid_; + std::string password_; + std::string requested_resource_; + bool tls_needed_; + std::string tls_server_domain_; + scoped_ptr login_task_; + + int next_id_; + Jid bound_jid_; + State state_; + bool encrypted_; + Error error_code_; + scoped_ptr stream_error_; + bool raised_reset_; + XmppOutputHandler* output_handler_; + XmppSessionHandler* session_handler_; + + typedef STD_VECTOR(XmppStanzaHandler*) StanzaHandlerVector; + scoped_ptr stanza_handlers_[HL_COUNT]; + + typedef STD_VECTOR(XmppIqEntry*) IqEntryVector; + scoped_ptr iq_entries_; + + scoped_ptr sasl_handler_; + + scoped_ptr output_; +}; + +} + + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengineimpl_iq.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengineimpl_iq.cc new file mode 100644 index 00000000..eb623ed9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppengineimpl_iq.cc @@ -0,0 +1,279 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include +#include +#include "talk/base/common.h" +#include "talk/xmpp/xmppengineimpl.h" +#include "talk/xmpp/constants.h" + +#define new TRACK_NEW + +namespace buzz { + +class XmppIqEntry { + XmppIqEntry(const std::string & id, const std::string & to, + XmppEngine * pxce, XmppIqHandler * iq_handler) : + id_(id), + to_(to), + engine_(pxce), + iq_handler_(iq_handler) { + } + +private: + friend class XmppEngineImpl; + + const std::string id_; + const std::string to_; + XmppEngine * const engine_; + XmppIqHandler * const iq_handler_; +}; + + +XmppReturnStatus +XmppEngineImpl::SendIq(const XmlElement * element, XmppIqHandler * iq_handler, + XmppIqCookie* cookie) { + if (state_ == STATE_CLOSED) + return XMPP_RETURN_BADSTATE; + if (NULL == iq_handler) + return XMPP_RETURN_BADARGUMENT; + if (!element || element->Name() != QN_IQ) + return XMPP_RETURN_BADARGUMENT; + + const std::string& type = element->Attr(QN_TYPE); + if (type != "get" && type != "set") + return XMPP_RETURN_BADARGUMENT; + + if (!element->HasAttr(QN_ID)) + return XMPP_RETURN_BADARGUMENT; + const std::string& id = element->Attr(QN_ID); + + XmppIqEntry * iq_entry = new XmppIqEntry(id, + element->Attr(QN_TO), + this, iq_handler); + iq_entries_->push_back(iq_entry); + SendStanza(element); + + if (cookie) + *cookie = iq_entry; + + return XMPP_RETURN_OK; +} + + +XmppReturnStatus +XmppEngineImpl::RemoveIqHandler(XmppIqCookie cookie, + XmppIqHandler ** iq_handler) { + + std::vector >::iterator pos; + + pos = std::find(iq_entries_->begin(), + iq_entries_->end(), + reinterpret_cast(cookie)); + + if (pos == iq_entries_->end()) + return XMPP_RETURN_BADARGUMENT; + + XmppIqEntry* entry = *pos; + iq_entries_->erase(pos); + if (iq_handler) + *iq_handler = entry->iq_handler_; + delete entry; + + return XMPP_RETURN_OK; +} + +void +XmppEngineImpl::DeleteIqCookies() { + for (size_t i = 0; i < iq_entries_->size(); i += 1) { + XmppIqEntry * iq_entry_ = (*iq_entries_)[i]; + (*iq_entries_)[i] = NULL; + delete iq_entry_; + } + iq_entries_->clear(); +} + +static void +AecImpl(XmlElement * error_element, const QName & name, + const char * type, const char * code) { + error_element->AddElement(new XmlElement(QN_ERROR)); + error_element->AddAttr(QN_CODE, code, 1); + error_element->AddAttr(QN_TYPE, type, 1); + error_element->AddElement(new XmlElement(name, true), 1); +} + + +static void +AddErrorCode(XmlElement * error_element, XmppStanzaError code) { + switch (code) { + case XSE_BAD_REQUEST: + AecImpl(error_element, QN_STANZA_BAD_REQUEST, "modify", "400"); + break; + case XSE_CONFLICT: + AecImpl(error_element, QN_STANZA_CONFLICT, "cancel", "409"); + break; + case XSE_FEATURE_NOT_IMPLEMENTED: + AecImpl(error_element, QN_STANZA_FEATURE_NOT_IMPLEMENTED, + "cancel", "501"); + break; + case XSE_FORBIDDEN: + AecImpl(error_element, QN_STANZA_FORBIDDEN, "auth", "403"); + break; + case XSE_GONE: + AecImpl(error_element, QN_STANZA_GONE, "modify", "302"); + break; + case XSE_INTERNAL_SERVER_ERROR: + AecImpl(error_element, QN_STANZA_INTERNAL_SERVER_ERROR, "wait", "500"); + break; + case XSE_ITEM_NOT_FOUND: + AecImpl(error_element, QN_STANZA_ITEM_NOT_FOUND, "cancel", "404"); + break; + case XSE_JID_MALFORMED: + AecImpl(error_element, QN_STANZA_JID_MALFORMED, "modify", "400"); + break; + case XSE_NOT_ACCEPTABLE: + AecImpl(error_element, QN_STANZA_NOT_ACCEPTABLE, "cancel", "406"); + break; + case XSE_NOT_ALLOWED: + AecImpl(error_element, QN_STANZA_NOT_ALLOWED, "cancel", "405"); + break; + case XSE_PAYMENT_REQUIRED: + AecImpl(error_element, QN_STANZA_PAYMENT_REQUIRED, "auth", "402"); + break; + case XSE_RECIPIENT_UNAVAILABLE: + AecImpl(error_element, QN_STANZA_RECIPIENT_UNAVAILABLE, "wait", "404"); + break; + case XSE_REDIRECT: + AecImpl(error_element, QN_STANZA_REDIRECT, "modify", "302"); + break; + case XSE_REGISTRATION_REQUIRED: + AecImpl(error_element, QN_STANZA_REGISTRATION_REQUIRED, "auth", "407"); + break; + case XSE_SERVER_NOT_FOUND: + AecImpl(error_element, QN_STANZA_REMOTE_SERVER_NOT_FOUND, + "cancel", "404"); + break; + case XSE_SERVER_TIMEOUT: + AecImpl(error_element, QN_STANZA_REMOTE_SERVER_TIMEOUT, "wait", "502"); + break; + case XSE_RESOURCE_CONSTRAINT: + AecImpl(error_element, QN_STANZA_RESOURCE_CONSTRAINT, "wait", "500"); + break; + case XSE_SERVICE_UNAVAILABLE: + AecImpl(error_element, QN_STANZA_SERVICE_UNAVAILABLE, "cancel", "503"); + break; + case XSE_SUBSCRIPTION_REQUIRED: + AecImpl(error_element, QN_STANZA_SUBSCRIPTION_REQUIRED, "auth", "407"); + break; + case XSE_UNDEFINED_CONDITION: + AecImpl(error_element, QN_STANZA_UNDEFINED_CONDITION, "wait", "500"); + break; + case XSE_UNEXPECTED_REQUEST: + AecImpl(error_element, QN_STANZA_UNEXPECTED_REQUEST, "wait", "400"); + break; + } +} + + +XmppReturnStatus +XmppEngineImpl::SendStanzaError(const XmlElement * element_original, + XmppStanzaError code, + const std::string & text) { + + if (state_ == STATE_CLOSED) + return XMPP_RETURN_BADSTATE; + + XmlElement error_element(element_original->Name()); + error_element.AddAttr(QN_TYPE, "error"); + + // copy attrs, copy 'from' to 'to' and strip 'from' + for (const XmlAttr * attribute = element_original->FirstAttr(); + attribute; attribute = attribute->NextAttr()) { + QName name = attribute->Name(); + if (name == QN_TO) + continue; // no need to put a from attr. Server will stamp stanza + else if (name == QN_FROM) + name = QN_TO; + else if (name == QN_TYPE) + continue; + error_element.AddAttr(name, attribute->Value()); + } + + // copy children + for (const XmlChild * child = element_original->FirstChild(); + child; + child = child->NextChild()) { + if (child->IsText()) { + error_element.AddText(child->AsText()->Text()); + } else { + error_element.AddElement(new XmlElement(*(child->AsElement()))); + } + } + + // add error information + AddErrorCode(&error_element, code); + if (text != STR_EMPTY) { + XmlElement * text_element = new XmlElement(QN_STANZA_TEXT, true); + text_element->AddText(text); + error_element.AddElement(text_element); + } + + SendStanza(&error_element); + + return XMPP_RETURN_OK; +} + + +bool +XmppEngineImpl::HandleIqResponse(const XmlElement * element) { + if (iq_entries_->empty()) + return false; + if (element->Name() != QN_IQ) + return false; + std::string type = element->Attr(QN_TYPE); + if (type != "result" && type != "error") + return false; + if (!element->HasAttr(QN_ID)) + return false; + std::string id = element->Attr(QN_ID); + std::string from = element->Attr(QN_FROM); + + for (std::vector::iterator it = iq_entries_->begin(); + it != iq_entries_->end(); it += 1) { + XmppIqEntry * iq_entry = *it; + if (iq_entry->id_ == id && iq_entry->to_ == from) { + iq_entries_->erase(it); + iq_entry->iq_handler_->IqResponse(iq_entry, element); + delete iq_entry; + return true; + } + } + + return false; +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpplogintask.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpplogintask.cc new file mode 100644 index 00000000..470c2dc2 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpplogintask.cc @@ -0,0 +1,357 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include +#include +#include +#include "talk/xmpp/jid.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/base/common.h" +#include "talk/xmpp/xmppengineimpl.h" +#include "talk/xmpp/constants.h" +#include "talk/base/base64.h" +#include "talk/xmpp/xmpplogintask.h" +#include "talk/xmpp/saslmechanism.h" + +#define new TRACK_NEW + +namespace buzz { + +XmppLoginTask::XmppLoginTask(XmppEngineImpl * pctx) : + pctx_(pctx), + authNeeded_(true), + state_(LOGINSTATE_INIT), + pelStanza_(NULL), + isStart_(false), + iqId_(STR_EMPTY), + pelFeatures_(NULL), + fullJid_(STR_EMPTY), + streamId_(STR_EMPTY), + pvecQueuedStanzas_(new std::vector()), + sasl_mech_(NULL) { +} + +XmppLoginTask::~XmppLoginTask() { + for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1) + delete (*pvecQueuedStanzas_)[i]; +} + +void +XmppLoginTask::IncomingStanza(const XmlElement *element, bool isStart) { + pelStanza_ = element; + isStart_ = isStart; + Advance(); + pelStanza_ = NULL; + isStart_ = false; +} + +const XmlElement * +XmppLoginTask::NextStanza() { + const XmlElement * result = pelStanza_; + pelStanza_ = NULL; + return result; +} + +bool +XmppLoginTask::Advance() { + + for (;;) { + + const XmlElement * element = NULL; + + switch (state_) { + + case LOGINSTATE_INIT: { + pctx_->RaiseReset(); + pelFeatures_.reset(NULL); + + pctx_->InternalSendStart(pctx_->user_jid_.domain()); + state_ = LOGINSTATE_STREAMSTART_SENT; + break; + } + + case LOGINSTATE_STREAMSTART_SENT: { + if (NULL == (element = NextStanza())) + return true; + + if (!isStart_ || !HandleStartStream(element)) + return Failure(XmppEngine::ERROR_VERSION); + + state_ = LOGINSTATE_STARTED_XMPP; + return true; + } + + case LOGINSTATE_STARTED_XMPP: { + if (NULL == (element = NextStanza())) + return true; + + if (!HandleFeatures(element)) + return Failure(XmppEngine::ERROR_VERSION); + + if (pctx_->tls_needed_) { + state_ = LOGINSTATE_TLS_INIT; + continue; + } + + if (authNeeded_) { + state_ = LOGINSTATE_AUTH_INIT; + continue; + } + + state_ = LOGINSTATE_BIND_INIT; + continue; + } + + case LOGINSTATE_TLS_INIT: { + const XmlElement * pelTls = GetFeature(QN_TLS_STARTTLS); + if (!pelTls) + return Failure(XmppEngine::ERROR_TLS); + + XmlElement el(QN_TLS_STARTTLS, true); + pctx_->InternalSendStanza(&el); + state_ = LOGINSTATE_TLS_REQUESTED; + continue; + } + + case LOGINSTATE_TLS_REQUESTED: { + if (NULL == (element = NextStanza())) + return true; + if (element->Name() != QN_TLS_PROCEED) + return Failure(XmppEngine::ERROR_TLS); + + // The proper domain to verify against is the real underlying + // domain - i.e., the domain that owns the JID. Our XmppEngineImpl + // also allows matching against a proxy domain instead, if it is told + // to do so - see the implementation of XmppEngineImpl::StartTls and + // XmppEngine::SetTlsServerDomain to see how you can use that feature + pctx_->StartTls(pctx_->user_jid_.domain()); + pctx_->tls_needed_ = false; + state_ = LOGINSTATE_INIT; + continue; + } + + case LOGINSTATE_AUTH_INIT: { + const XmlElement * pelSaslAuth = GetFeature(QN_SASL_MECHANISMS); + if (!pelSaslAuth) { + return Failure(XmppEngine::ERROR_AUTH); + } + + // Collect together the SASL auth mechanisms presented by the server + std::vector mechanisms; + for (const XmlElement * pelMech = + pelSaslAuth->FirstNamed(QN_SASL_MECHANISM); + pelMech; + pelMech = pelMech->NextNamed(QN_SASL_MECHANISM)) { + + mechanisms.push_back(pelMech->BodyText()); + } + + // Given all the mechanisms, choose the best + std::string choice(pctx_->ChooseBestSaslMechanism(mechanisms, pctx_->IsEncrypted())); + if (choice.empty()) { + return Failure(XmppEngine::ERROR_AUTH); + } + + // No recognized auth mechanism - that's an error + sasl_mech_.reset(pctx_->GetSaslMechanism(choice)); + if (sasl_mech_.get() == NULL) { + return Failure(XmppEngine::ERROR_AUTH); + } + + // OK, let's start it. + XmlElement * auth = sasl_mech_->StartSaslAuth(); + if (auth == NULL) { + return Failure(XmppEngine::ERROR_AUTH); + } + + pctx_->InternalSendStanza(auth); + delete auth; + state_ = LOGINSTATE_SASL_RUNNING; + continue; + } + + case LOGINSTATE_SASL_RUNNING: { + if (NULL == (element = NextStanza())) + return true; + if (element->Name().Namespace() != NS_SASL) + return Failure(XmppEngine::ERROR_AUTH); + if (element->Name() == QN_SASL_CHALLENGE) { + XmlElement * response = sasl_mech_->HandleSaslChallenge(element); + if (response == NULL) { + return Failure(XmppEngine::ERROR_AUTH); + } + pctx_->InternalSendStanza(response); + delete response; + state_ = LOGINSTATE_SASL_RUNNING; + continue; + } + if (element->Name() != QN_SASL_SUCCESS) { + return Failure(XmppEngine::ERROR_UNAUTHORIZED); + } + + // Authenticated! + authNeeded_ = false; + state_ = LOGINSTATE_INIT; + continue; + } + + case LOGINSTATE_BIND_INIT: { + const XmlElement * pelBindFeature = GetFeature(QN_BIND_BIND); + const XmlElement * pelSessionFeature = GetFeature(QN_SESSION_SESSION); + if (!pelBindFeature || !pelSessionFeature) + return Failure(XmppEngine::ERROR_BIND); + + XmlElement iq(QN_IQ); + iq.AddAttr(QN_TYPE, "set"); + + iqId_ = pctx_->NextId(); + iq.AddAttr(QN_ID, iqId_); + iq.AddElement(new XmlElement(QN_BIND_BIND, true)); + + if (pctx_->requested_resource_ != STR_EMPTY) { + iq.AddElement(new XmlElement(QN_BIND_RESOURCE), 1); + iq.AddText(pctx_->requested_resource_, 2); + } + pctx_->InternalSendStanza(&iq); + state_ = LOGINSTATE_BIND_REQUESTED; + continue; + } + + case LOGINSTATE_BIND_REQUESTED: { + if (NULL == (element = NextStanza())) + return true; + + if (element->Name() != QN_IQ || element->Attr(QN_ID) != iqId_ || + element->Attr(QN_TYPE) == "get" || element->Attr(QN_TYPE) == "set") + return true; + + if (element->Attr(QN_TYPE) != "result" || element->FirstElement() == NULL || + element->FirstElement()->Name() != QN_BIND_BIND) + return Failure(XmppEngine::ERROR_BIND); + + fullJid_ = Jid(element->FirstElement()->TextNamed(QN_BIND_JID)); + if (!fullJid_.IsFull()) { + return Failure(XmppEngine::ERROR_BIND); + } + + if (pctx_->user_jid_.domain() != STR_DEFAULT_DOMAIN && + fullJid_.BareJid() != pctx_->user_jid_) { + return Failure(XmppEngine::ERROR_BIND); + } + + // now request session + XmlElement iq(QN_IQ); + iq.AddAttr(QN_TYPE, "set"); + + iqId_ = pctx_->NextId(); + iq.AddAttr(QN_ID, iqId_); + iq.AddElement(new XmlElement(QN_SESSION_SESSION, true)); + pctx_->InternalSendStanza(&iq); + + state_ = LOGINSTATE_SESSION_REQUESTED; + continue; + } + + case LOGINSTATE_SESSION_REQUESTED: { + if (NULL == (element = NextStanza())) + return true; + if (element->Name() != QN_IQ || element->Attr(QN_ID) != iqId_ || + element->Attr(QN_TYPE) == "get" || element->Attr(QN_TYPE) == "set") + return false; + + if (element->Attr(QN_TYPE) != "result") + return Failure(XmppEngine::ERROR_BIND); + + pctx_->SignalBound(fullJid_); + FlushQueuedStanzas(); + state_ = LOGINSTATE_DONE; + return true; + } + + case LOGINSTATE_DONE: + return false; + } + } +} + +bool +XmppLoginTask::HandleStartStream(const XmlElement *element) { + + if (element->Name() != QN_STREAM_STREAM) + return false; + + if (element->Attr(QN_XMLNS) != "jabber:client") + return false; + + if (element->Attr(QN_VERSION) != "1.0") + return false; + + if (!element->HasAttr(QN_ID)) + return false; + + streamId_ = element->Attr(QN_ID); + + return true; +} + +bool +XmppLoginTask::HandleFeatures(const XmlElement *element) { + if (element->Name() != QN_STREAM_FEATURES) + return false; + + pelFeatures_.reset(new XmlElement(*element)); + return true; +} + +const XmlElement * +XmppLoginTask::GetFeature(const QName & name) { + return pelFeatures_->FirstNamed(name); +} + +bool +XmppLoginTask::Failure(XmppEngine::Error reason) { + state_ = LOGINSTATE_DONE; + pctx_->SignalError(reason); + return false; +} + +void +XmppLoginTask::OutgoingStanza(const XmlElement * element) { + XmlElement * pelCopy = new XmlElement(*element); + pvecQueuedStanzas_->push_back(pelCopy); +} + +void +XmppLoginTask::FlushQueuedStanzas() { + for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1) { + pctx_->InternalSendStanza((*pvecQueuedStanzas_)[i]); + delete (*pvecQueuedStanzas_)[i]; + } + pvecQueuedStanzas_->clear(); +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpplogintask.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpplogintask.h new file mode 100644 index 00000000..7f321a30 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpplogintask.h @@ -0,0 +1,95 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _logintask_h_ +#define _logintask_h_ + +#include +#include "talk/xmpp/jid.h" +#include "talk/base/scoped_ptr.h" +#include "talk/xmpp/xmppengine.h" +#include "talk/base/stl_decl.h" + +namespace buzz { + +class XmlElement; +class XmppEngineImpl; +class SaslMechanism; + + +class XmppLoginTask { + +public: + XmppLoginTask(XmppEngineImpl *pctx); + ~XmppLoginTask(); + + bool IsDone() + { return state_ == LOGINSTATE_DONE; } + void IncomingStanza(const XmlElement * element, bool isStart); + void OutgoingStanza(const XmlElement *element); + +private: + enum LoginTaskState { + LOGINSTATE_INIT = 0, + LOGINSTATE_STREAMSTART_SENT, + LOGINSTATE_STARTED_XMPP, + LOGINSTATE_TLS_INIT, + LOGINSTATE_AUTH_INIT, + LOGINSTATE_BIND_INIT, + LOGINSTATE_TLS_REQUESTED, + LOGINSTATE_SASL_RUNNING, + LOGINSTATE_BIND_REQUESTED, + LOGINSTATE_SESSION_REQUESTED, + LOGINSTATE_DONE, + }; + + const XmlElement * NextStanza(); + bool Advance(); + bool HandleStartStream(const XmlElement * element); + bool HandleFeatures(const XmlElement * element); + const XmlElement * GetFeature(const QName & name); + bool Failure(XmppEngine::Error reason); + void FlushQueuedStanzas(); + + XmppEngineImpl * pctx_; + bool authNeeded_; + LoginTaskState state_; + const XmlElement * pelStanza_; + bool isStart_; + std::string iqId_; + scoped_ptr pelFeatures_; + Jid fullJid_; + std::string streamId_; + scoped_ptr > > pvecQueuedStanzas_; + + scoped_ptr sasl_mech_; +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpppassword.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpppassword.h new file mode 100644 index 00000000..f431b4e5 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpppassword.h @@ -0,0 +1,163 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _XMPPPASSWORD_H_ +#define _XMPPPASSWORD_H_ + +#include "talk/base/linked_ptr.h" +#include "talk/base/scoped_ptr.h" + +namespace buzz { + +class XmppPasswordImpl { +public: + virtual ~XmppPasswordImpl() {} + virtual size_t GetLength() const = 0; + virtual void CopyTo(char * dest, bool nullterminate) const = 0; + virtual std::string UrlEncode() const = 0; + virtual XmppPasswordImpl * Copy() const = 0; +}; + +class EmptyXmppPasswordImpl : public XmppPasswordImpl { +public: + virtual ~EmptyXmppPasswordImpl() {} + virtual size_t GetLength() const { return 0; } + virtual void CopyTo(char * dest, bool nullterminate) const { + if (nullterminate) { + *dest = '\0'; + } + } + virtual std::string UrlEncode() const { return ""; } + virtual XmppPasswordImpl * Copy() const { return new EmptyXmppPasswordImpl(); } +}; + +class XmppPassword { +public: + XmppPassword() : impl_(new EmptyXmppPasswordImpl()) {} + size_t GetLength() const { return impl_->GetLength(); } + void CopyTo(char * dest, bool nullterminate) const { impl_->CopyTo(dest, nullterminate); } + XmppPassword(const XmppPassword & other) : impl_(other.impl_->Copy()) {} + explicit XmppPassword(const XmppPasswordImpl & impl) : impl_(impl.Copy()) {} + XmppPassword & operator=(const XmppPassword & other) { + if (this != &other) { + impl_.reset(other.impl_->Copy()); + } + return *this; + } + void Clear() { impl_.reset(new EmptyXmppPasswordImpl()); } + std::string UrlEncode() const { return impl_->UrlEncode(); } + +private: + scoped_ptr impl_; +}; + + +// Used for constructing strings where a password is involved and we +// need to ensure that we zero memory afterwards +class FormatXmppPassword { +public: + FormatXmppPassword() { + storage_ = new char[32]; + capacity_ = 32; + length_ = 0; + storage_[0] = 0; + } + + void Append(const std::string & text) { + Append(text.data(), text.length()); + } + + void Append(const char * data, size_t length) { + EnsureStorage(length_ + length + 1); + memcpy(storage_ + length_, data, length); + length_ += length; + storage_[length_] = '\0'; + } + + void Append(const XmppPassword * password) { + size_t len = password->GetLength(); + EnsureStorage(length_ + len + 1); + password->CopyTo(storage_ + length_, true); + length_ += len; + } + + size_t GetLength() { + return length_; + } + + const char * GetData() { + return storage_; + } + + + // Ensures storage of at least n bytes + void EnsureStorage(size_t n) { + if (capacity_ >= n) { + return; + } + + size_t old_capacity = capacity_; + char * old_storage = storage_; + + for (;;) { + capacity_ *= 2; + if (capacity_ >= n) + break; + } + + storage_ = new char[capacity_]; + + if (old_capacity) { + memcpy(storage_, old_storage, length_); + + // zero memory in a way that an optimizer won't optimize it out + old_storage[0] = 0; + for (size_t i = 1; i < old_capacity; i++) { + old_storage[i] = old_storage[i - 1]; + } + delete[] old_storage; + } + } + + ~FormatXmppPassword() { + if (capacity_) { + storage_[0] = 0; + for (size_t i = 1; i < capacity_; i++) { + storage_[i] = storage_[i - 1]; + } + } + delete[] storage_; + } +private: + char * storage_; + size_t capacity_; + size_t length_; +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppstanzaparser.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppstanzaparser.cc new file mode 100644 index 00000000..66ed44fb --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppstanzaparser.cc @@ -0,0 +1,104 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include +#include "talk/xmllite/xmlelement.h" +#include "talk/base/common.h" +#include "talk/xmpp/xmppstanzaparser.h" +#include "talk/xmpp/constants.h" + +#define new TRACK_NEW + +namespace buzz { + +XmppStanzaParser::XmppStanzaParser(XmppStanzaParseHandler *psph) : + psph_(psph), + innerHandler_(this), + parser_(&innerHandler_), + depth_(0), + builder_() { +} + +void +XmppStanzaParser::Reset() { + parser_.Reset(); + depth_ = 0; + builder_.Reset(); +} + +void +XmppStanzaParser::IncomingStartElement( + XmlParseContext * pctx, const char * name, const char ** atts) { + if (depth_++ == 0) { + XmlElement * pelStream = XmlBuilder::BuildElement(pctx, name, atts); + if (pelStream == NULL) { + pctx->RaiseError(XML_ERROR_SYNTAX); + return; + } + psph_->StartStream(pelStream); + delete pelStream; + return; + } + + builder_.StartElement(pctx, name, atts); +} + +void +XmppStanzaParser::IncomingCharacterData( + XmlParseContext * pctx, const char * text, int len) { + if (depth_ > 1) { + builder_.CharacterData(pctx, text, len); + } +} + +void +XmppStanzaParser::IncomingEndElement( + XmlParseContext * pctx, const char * name) { + if (--depth_ == 0) { + psph_->EndStream(); + return; + } + + builder_.EndElement(pctx, name); + + if (depth_ == 1) { + XmlElement *element = builder_.CreateElement(); + psph_->Stanza(element); + delete element; + } +} + +void +XmppStanzaParser::IncomingError( + XmlParseContext * pctx, XML_Error errCode) { + UNUSED(pctx); + UNUSED(errCode); + psph_->XmlError(); +} + +} + diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppstanzaparser.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppstanzaparser.h new file mode 100644 index 00000000..1e109a3d --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmppstanzaparser.h @@ -0,0 +1,96 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _xmppstanzaparser_h_ +#define _xmppstanzaparser_h_ + +#include "talk/xmllite/xmlparser.h" +#include "talk/xmllite/xmlbuilder.h" + + +namespace buzz { + +class XmlElement; + +class XmppStanzaParseHandler { +public: + virtual void StartStream(const XmlElement * pelStream) = 0; + virtual void Stanza(const XmlElement * pelStanza) = 0; + virtual void EndStream() = 0; + virtual void XmlError() = 0; +}; + +class XmppStanzaParser { +public: + XmppStanzaParser(XmppStanzaParseHandler *psph); + bool Parse(const char * data, size_t len, bool isFinal) + { return parser_.Parse(data, len, isFinal); } + void Reset(); + +private: + class ParseHandler : public XmlParseHandler { + public: + ParseHandler(XmppStanzaParser * outer) : outer_(outer) {} + virtual void StartElement(XmlParseContext * pctx, + const char * name, const char ** atts) + { outer_->IncomingStartElement(pctx, name, atts); } + virtual void EndElement(XmlParseContext * pctx, + const char * name) + { outer_->IncomingEndElement(pctx, name); } + virtual void CharacterData(XmlParseContext * pctx, + const char * text, int len) + { outer_->IncomingCharacterData(pctx, text, len); } + virtual void Error(XmlParseContext * pctx, + XML_Error errCode) + { outer_->IncomingError(pctx, errCode); } + private: + XmppStanzaParser * const outer_; + }; + + friend class ParseHandler; + + void IncomingStartElement(XmlParseContext * pctx, + const char * name, const char ** atts); + void IncomingEndElement(XmlParseContext * pctx, + const char * name); + void IncomingCharacterData(XmlParseContext * pctx, + const char * text, int len); + void IncomingError(XmlParseContext * pctx, + XML_Error errCode); + + XmppStanzaParseHandler * psph_; + ParseHandler innerHandler_; + XmlParser parser_; + int depth_; + XmlBuilder builder_; + + }; + + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpptask.cc b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpptask.cc new file mode 100644 index 00000000..82207f3b --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpptask.cc @@ -0,0 +1,168 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#include "talk/xmpp/xmpptask.h" +#include "talk/xmpp/xmppclient.h" +#include "talk/xmpp/xmppengine.h" +#include "talk/xmpp/constants.h" + +namespace buzz { + +XmppTask::XmppTask(Task * parent, XmppEngine::HandlerLevel level) + : Task(parent), client_(NULL) { + XmppClient * client = (XmppClient*)parent->GetParent(XMPP_CLIENT_TASK_CODE); + client_ = client; + id_ = client->NextId(); + client->AddXmppTask(this, level); + client->SignalDisconnected.connect(this, &XmppTask::OnDisconnect); +} + +XmppTask::~XmppTask() { + StopImpl(); +} + +void +XmppTask::StopImpl() { + while (NextStanza() != NULL) {} + if (client_) { + client_->RemoveXmppTask(this); + client_->SignalDisconnected.disconnect(this); + client_ = NULL; + } +} + +XmppReturnStatus +XmppTask::SendStanza(const XmlElement * stanza) { + if (client_ == NULL) + return XMPP_RETURN_BADSTATE; + return client_->SendStanza(stanza); +} + +XmppReturnStatus +XmppTask::SendStanzaError(const XmlElement * element_original, + XmppStanzaError code, + const std::string & text) { + if (client_ == NULL) + return XMPP_RETURN_BADSTATE; + return client_->SendStanzaError(element_original, code, text); +} + +void +XmppTask::Stop() { + StopImpl(); + Task::Stop(); +} + +void +XmppTask::OnDisconnect() { + Error(); +} + +void +XmppTask::QueueStanza(const XmlElement * stanza) { + stanza_queue_.push_back(new XmlElement(*stanza)); + Wake(); +} + +const XmlElement * +XmppTask::NextStanza() { + XmlElement * result = NULL; + if (!stanza_queue_.empty()) { + result = stanza_queue_.front(); + stanza_queue_.pop_front(); + } + next_stanza_.reset(result); + return result; +} + +XmlElement * +XmppTask::MakeIq(const std::string & type, + const buzz::Jid & to, const std::string id) { + XmlElement * result = new XmlElement(QN_IQ); + if (!type.empty()) + result->AddAttr(QN_TYPE, type); + if (to != JID_EMPTY) + result->AddAttr(QN_TO, to.Str()); + if (!id.empty()) + result->AddAttr(QN_ID, id); + return result; +} + +XmlElement * +XmppTask::MakeIqResult(const XmlElement * query) { + XmlElement * result = new XmlElement(QN_IQ); + result->AddAttr(QN_TYPE, STR_RESULT); + if (query->HasAttr(QN_FROM)) { + result->AddAttr(QN_TO, query->Attr(QN_FROM)); + } + result->AddAttr(QN_ID, query->Attr(QN_ID)); + return result; +} + +bool +XmppTask::MatchResponseIq(const XmlElement * stanza, + const Jid & to, const std::string & id) { + if (stanza->Name() != QN_IQ) + return false; + + if (stanza->Attr(QN_ID) != id) + return false; + + Jid from(stanza->Attr(QN_FROM)); + if (from != to) { + Jid me = client_->jid(); + // we address the server as "", but it is legal for the server + // to identify itself with "domain" or "myself@domain" + if (to != JID_EMPTY) { + return false; + } + + if (from != Jid(me.domain()) && from != me.BareJid()) { + return false; + } + } + + + return true; +} + +bool +XmppTask::MatchRequestIq(const XmlElement * stanza, + const std::string & type, const QName & qn) { + if (stanza->Name() != QN_IQ) + return false; + + if (stanza->Attr(QN_TYPE) != type) + return false; + + if (stanza->FirstNamed(qn) == NULL) + return false; + + return true; +} + +} diff --git a/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpptask.h b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpptask.h new file mode 100644 index 00000000..3b56a1c9 --- /dev/null +++ b/kopete/protocols/jabber/jingle/libjingle/talk/xmpp/xmpptask.h @@ -0,0 +1,113 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * 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 name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 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. + */ + +#ifndef _XMPPTASK_H_ +#define _XMPPTASK_H_ + +#include +#include +#include "talk/base/sigslot.h" +#include "talk/xmpp/xmppengine.h" +#include "talk/base/task.h" + +namespace buzz { + +///////////////////////////////////////////////////////////////////// +// +// XMPPTASK +// +///////////////////////////////////////////////////////////////////// +// +// See Task and XmppClient first. +// +// XmppTask is a task that is designed to go underneath XmppClient and be +// useful there. It has a way of finding its XmppClient parent so you +// can have it nested arbitrarily deep under an XmppClient and it can +// still find the XMPP services. +// +// Tasks register themselves to listen to particular kinds of stanzas +// that are sent out by the client. Rather than processing stanzas +// right away, they should decide if they own the sent stanza, +// and if so, queue it and Wake() the task, or if a stanza does not belong +// to you, return false right away so the next XmppTask can take a crack. +// This technique (synchronous recognize, but asynchronous processing) +// allows you to have arbitrary logic for recognizing stanzas yet still, +// for example, disconnect a client while processing a stanza - +// without reentrancy problems. +// +///////////////////////////////////////////////////////////////////// + +class XmppClient; + +class XmppTask : + public Task, + public XmppStanzaHandler, + public sigslot::has_slots<> +{ +public: + XmppTask(Task * parent, XmppEngine::HandlerLevel level = XmppEngine::HL_NONE); + virtual ~XmppTask(); + + virtual XmppClient * GetClient() const { return client_; } + std::string task_id() const { return id_; } + +protected: + friend class XmppClient; + + XmppReturnStatus SendStanza(const XmlElement * stanza); + XmppReturnStatus SetResult(const std::string & code); + XmppReturnStatus SendStanzaError(const XmlElement * element_original, + XmppStanzaError code, + const std::string & text); + + virtual void Stop(); + virtual bool HandleStanza(const XmlElement * stanza) { return false; } + virtual void OnDisconnect(); + virtual int ProcessReponse() { return STATE_DONE; } + + void QueueStanza(const XmlElement * stanza); + const XmlElement * NextStanza(); + + bool MatchResponseIq(const XmlElement * stanza, const Jid & to, const std::string & task_id); + bool MatchRequestIq(const XmlElement * stanza, const std::string & type, const QName & qn); + XmlElement *MakeIqResult(const XmlElement * query); + XmlElement *MakeIq(const std::string & type, + const Jid & to, const std::string task_id); + +private: + void StopImpl(); + + XmppClient * client_; + std::deque stanza_queue_; + scoped_ptr next_stanza_; + std::string id_; + +}; + +} + +#endif diff --git a/kopete/protocols/jabber/jingle/voicecaller.h b/kopete/protocols/jabber/jingle/voicecaller.h new file mode 100644 index 00000000..0f0d18bb --- /dev/null +++ b/kopete/protocols/jabber/jingle/voicecaller.h @@ -0,0 +1,96 @@ +#define PsiAccount JabberAccount +class PsiAccount; + +#ifndef VOICECALLER_H +#define VOICECALLER_H + +#include "im.h" + + + + +using namespace XMPP; + +/** + * \brief An abstract class for a voice call implementation. + */ +class VoiceCaller : public QObject +{ + Q_OBJECT + +public: + /** + * \brief Base constructor. + * + * \param account the account to which this voice caller belongs + */ + VoiceCaller(PsiAccount* account) : account_(account) { }; + + /** + * \brief Retrieves the account to which this voice caller belongs. + */ + PsiAccount* account() { return account_; } + + /** + * \brief Initializes the voice caller. + * This should be called when the connection is open. + */ + virtual void initialize() = 0; + + /** + * \brief De-initializes the voice caller. + * This should be called when the connection is about to be closed. + */ + virtual void deinitialize() = 0; + + /** + * \brief Call the given JID. + */ + virtual void call(const Jid&) = 0; + + /** + * \brief Accept a call from the given JID. + */ + virtual void accept(const Jid&) = 0; + + /** + * \brief Reject the call from the given JID. + */ + virtual void reject(const Jid&) = 0; + + /** + * \brief Terminate the call from the given JID. + */ + virtual void terminate(const Jid&) = 0; + +signals: + /** + * \brief Incoming call from the given JID. + */ + void incoming(const Jid&); + + /** + * \brief Contact accepted an incoming call. + */ + void accepted(const Jid&); + + /** + * \brief Contact rejected an incoming call. + */ + void rejected(const Jid&); + + /** + * \brief Call with given JID is in progress. + */ + void in_progress(const Jid&); + + /** + * \brief Call with given JID is terminated. + */ + void terminated(const Jid&); + +private: + PsiAccount* account_; +}; + +#endif diff --git a/kopete/protocols/jabber/kioslave/Makefile.am b/kopete/protocols/jabber/kioslave/Makefile.am new file mode 100644 index 00000000..7fe4d3d6 --- /dev/null +++ b/kopete/protocols/jabber/kioslave/Makefile.am @@ -0,0 +1,25 @@ +METASOURCES = AUTO + +INCLUDES = \ + -I$(srcdir)/.. \ + -I$(srcdir)/../libiris/iris/include \ + -I$(srcdir)/../libiris/iris/xmpp-im \ + -I$(srcdir)/../libiris/iris/jabber \ + -I$(srcdir)/../libiris/qca/src \ + -I$(srcdir)/../libiris/cutestuff/util \ + -I$(srcdir)/../libiris/cutestuff/network \ + $(all_includes) + +kde_module_LTLIBRARIES = kio_jabberdisco.la + +kio_jabberdisco_la_SOURCES = jabberdisco.cpp +kio_jabberdisco_la_LIBADD = ../libjabberclient.la ../libiris/qca/src/libqca.la ../libiris/iris/include/libiris.la ../libiris/iris/xmpp-im/libiris_xmpp_im.la ../libiris/iris/xmpp-core/libiris_xmpp_core.la ../libiris/iris/jabber/libiris_jabber.la ../libiris/cutestuff/util/libcutestuff_util.la ../libiris/cutestuff/network/libcutestuff_network.la $(LIB_KIO) +kio_jabberdisco_la_LDFLAGS = -no-undefined -module $(KDE_PLUGIN) $(all_libraries) + +noinst_HEADERS = jabberdisco.h + +protocol_DATA = jabberdisco.protocol +protocoldir = $(kde_servicesdir) + +messages: rc.cpp + $(XGETTEXT) *.cpp -o $(podir)/kio_jabberdisco.pot diff --git a/kopete/protocols/jabber/kioslave/jabberdisco.cpp b/kopete/protocols/jabber/kioslave/jabberdisco.cpp new file mode 100644 index 00000000..a6775320 --- /dev/null +++ b/kopete/protocols/jabber/kioslave/jabberdisco.cpp @@ -0,0 +1,399 @@ + +/*************************************************************************** + Jabber Service Discovery KIO Slave + ------------------- + begin : Wed June 1 2005 + copyright : (C) 2005 by Till Gerken + + Kopete (C) 2001-2005 Kopete developers + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include + +#include "jabberdisco.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "jabberclient.h" + +JabberDiscoProtocol::JabberDiscoProtocol ( const QCString &pool_socket, const QCString &app_socket ) + : KIO::SlaveBase ( "kio_jabberdisco", pool_socket, app_socket ) +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Slave launched." << endl; + + m_jabberClient = 0l; + m_connected = false; + +} + + +JabberDiscoProtocol::~JabberDiscoProtocol () +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Slave is shutting down." << endl; + + delete m_jabberClient; + +} + +void JabberDiscoProtocol::setHost ( const QString &host, int port, const QString &user, const QString &pass ) +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << " Host " << host << ", port " << port << ", user " << user << endl; + + m_host = host; + m_port = !port ? 5222 : port; + m_user = QString(user).replace ( "%", "@" ); + m_password = pass; + +} + +void JabberDiscoProtocol::openConnection () +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << endl; + + if ( m_connected ) + { + return; + } + + // instantiate new client backend or clean up old one + if ( !m_jabberClient ) + { + m_jabberClient = new JabberClient; + + QObject::connect ( m_jabberClient, SIGNAL ( csDisconnected () ), this, SLOT ( slotCSDisconnected () ) ); + QObject::connect ( m_jabberClient, SIGNAL ( csError ( int ) ), this, SLOT ( slotCSError ( int ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( tlsWarning ( int ) ), this, SLOT ( slotHandleTLSWarning ( int ) ) ); + QObject::connect ( m_jabberClient, SIGNAL ( connected () ), this, SLOT ( slotConnected () ) ); + QObject::connect ( m_jabberClient, SIGNAL ( error ( JabberClient::ErrorCode ) ), this, SLOT ( slotClientError ( JabberClient::ErrorCode ) ) ); + + QObject::connect ( m_jabberClient, SIGNAL ( debugMessage ( const QString & ) ), + this, SLOT ( slotClientDebugMessage ( const QString & ) ) ); + } + else + { + m_jabberClient->disconnect (); + } + + // we need to use the old protocol for now + m_jabberClient->setUseXMPP09 ( true ); + + // set SSL flag (this should be converted to forceTLS when using the new protocol) + m_jabberClient->setUseSSL ( false ); + + // override server and port (this should be dropped when using the new protocol and no direct SSL) + m_jabberClient->setOverrideHost ( true, m_host, m_port ); + + // allow plaintext password authentication or not? + m_jabberClient->setAllowPlainTextPassword ( false ); + + switch ( m_jabberClient->connect ( XMPP::Jid ( m_user + QString("/") + "JabberBrowser" ), m_password ) ) + { + case JabberClient::NoTLS: + // no SSL support, at the connecting stage this means the problem is client-side + error ( KIO::ERR_UPGRADE_REQUIRED, i18n ( "TLS" ) ); + break; + + case JabberClient::Ok: + default: + // everything alright! + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Waiting for socket to open..." << endl; + break; + } + + connected (); + +} + +void JabberDiscoProtocol::closeConnection () +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << endl; + + if ( m_jabberClient ) + { + m_jabberClient->disconnect (); + } + +} + +void JabberDiscoProtocol::slave_status () +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << endl; + + slaveStatus ( m_host, m_connected ); + +} + +void JabberDiscoProtocol::get ( const KURL &url ) +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << endl; + + m_command = Get; + m_url = url; + + mimeType ( "inode/directory" ); + + finished (); + +} + +void JabberDiscoProtocol::listDir ( const KURL &url ) +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << endl; + + m_command = ListDir; + m_url = url; + + openConnection (); + +} + +void JabberDiscoProtocol::mimetype ( const KURL &/*url*/ ) +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << endl; + + mimeType("inode/directory"); + + finished (); + +} + +void JabberDiscoProtocol::slotClientDebugMessage ( const QString &msg ) +{ + + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << msg << endl; + +} + +void JabberDiscoProtocol::slotHandleTLSWarning ( int validityResult ) +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Handling TLS warning..." << endl; + + if ( messageBox ( KIO::SlaveBase::WarningContinueCancel, + i18n ( "The server certificate is invalid. Do you want to continue? " ), + i18n ( "Certificate Warning" ) ) == KMessageBox::Continue ) + { + // resume stream + m_jabberClient->continueAfterTLSWarning (); + } + else + { + // disconnect stream + closeConnection (); + } + +} + +void JabberDiscoProtocol::slotClientError ( JabberClient::ErrorCode errorCode ) +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Handling client error..." << endl; + + switch ( errorCode ) + { + case JabberClient::NoTLS: + default: + error ( KIO::ERR_UPGRADE_REQUIRED, i18n ( "TLS" ) ); + closeConnection (); + break; + } + +} + +void JabberDiscoProtocol::slotConnected () +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Connected to Jabber server." << endl; + + XMPP::JT_DiscoItems *discoTask; + + m_connected = true; + + // now execute command + switch ( m_command ) + { + case ListDir: // list a directory + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Listing directory..." << endl; + discoTask = new XMPP::JT_DiscoItems ( m_jabberClient->rootTask () ); + connect ( discoTask, SIGNAL ( finished () ), this, SLOT ( slotQueryFinished () ) ); + discoTask->get ( m_host ); + discoTask->go ( true ); + break; + + case Get: // retrieve an item + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Retrieving item..." << endl; + break; + + default: // do nothing by default + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Unknown command " << m_command << endl; + break; + } + +} + +void JabberDiscoProtocol::slotQueryFinished () +{ + kdDebug ( JABBER_DISCO_DEBUG ) << "Query task finished" << endl; + + XMPP::JT_DiscoItems * task = (XMPP::JT_DiscoItems *) sender (); + + if (!task->success ()) + { + error ( KIO::ERR_COULD_NOT_READ, "" ); + return; + } + + XMPP::DiscoList::const_iterator itemsEnd = task->items().end (); + for (XMPP::DiscoList::const_iterator it = task->items().begin (); it != itemsEnd; ++it) + { + KIO::UDSAtom atom; + KIO::UDSEntry entry; + + atom.m_uds = KIO::UDS_NAME; + atom.m_str = (*it).jid().userHost (); + entry.prepend ( atom ); + + atom.m_uds = KIO::UDS_SIZE; + atom.m_long = 0; + entry.prepend ( atom ); + + atom.m_uds = KIO::UDS_LINK_DEST; + atom.m_str = (*it).name (); + entry.prepend ( atom ); + + atom.m_uds = KIO::UDS_MIME_TYPE; + atom.m_str = "inode/directory"; + entry.prepend ( atom ); + + atom.m_uds = KIO::UDS_SIZE; + atom.m_long = 0; + entry.prepend ( atom ); + + listEntry ( entry, false ); + + } + + listEntry ( KIO::UDSEntry(), true ); + + finished (); + +} + +void JabberDiscoProtocol::slotCSDisconnected () +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Disconnected from Jabber server." << endl; + + /* + * We should delete the JabberClient instance here, + * but timers etc prevent us from doing so. Iris does + * not like to be deleted from a slot. + */ + m_connected = false; + +} + +void JabberDiscoProtocol::slotCSError ( int errorCode ) +{ + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Error in stream signalled." << endl; + + if ( ( errorCode == XMPP::ClientStream::ErrAuth ) + && ( m_jabberClient->clientStream()->errorCondition () == XMPP::ClientStream::NotAuthorized ) ) + { + kdDebug ( JABBER_DISCO_DEBUG ) << k_funcinfo << "Incorrect password, retrying." << endl; + + KIO::AuthInfo authInfo; + authInfo.username = m_user; + authInfo.password = m_password; + if ( openPassDlg ( authInfo, i18n ( "The login details are incorrect. Do you want to try again?" ) ) ) + { + m_user = authInfo.username; + m_password = authInfo.password; + closeConnection (); + openConnection (); + } + else + { + closeConnection (); + error ( KIO::ERR_COULD_NOT_AUTHENTICATE, "" ); + } + } + else + { + closeConnection (); + error ( KIO::ERR_CONNECTION_BROKEN, "" ); + } + +} + +bool breakEventLoop = false; + +class EventLoopThread : public QThread +{ +public: + void run (); +}; + +void EventLoopThread::run () +{ + + while ( true ) + { + qApp->processEvents (); + msleep ( 100 ); + + if ( breakEventLoop ) + break; + } + +} + +void JabberDiscoProtocol::dispatchLoop () +{ + + EventLoopThread eventLoopThread; + + eventLoopThread.start (); + SlaveBase::dispatchLoop (); + breakEventLoop = true; + eventLoopThread.wait (); + +} + +extern "C" +{ + KDE_EXPORT int kdemain(int argc, char **argv); +} + + +int kdemain ( int argc, char **argv ) +{ + KApplication app(argc, argv, "kio_jabberdisco", false, true); + + kdDebug(JABBER_DISCO_DEBUG) << k_funcinfo << endl; + + if ( argc != 4 ) + { + kdDebug(JABBER_DISCO_DEBUG) << "Usage: kio_jabberdisco protocol domain-socket1 domain-socket2" << endl; + exit(-1); + } + + JabberDiscoProtocol slave ( argv[2], argv[3] ); + slave.dispatchLoop (); + + return 0; +} + +#include "jabberdisco.moc" diff --git a/kopete/protocols/jabber/kioslave/jabberdisco.h b/kopete/protocols/jabber/kioslave/jabberdisco.h new file mode 100644 index 00000000..f2f6d78d --- /dev/null +++ b/kopete/protocols/jabber/kioslave/jabberdisco.h @@ -0,0 +1,82 @@ + +/*************************************************************************** + Jabber Service Discovery KIO Slave + ------------------- + begin : Wed June 1 2005 + copyright : (C) 2005 by Till Gerken + + Kopete (C) 2001-2005 Kopete developers + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef _JABBERDISCO_H_ +#define _JABBERDISCO_H_ + +#include +#include +#include + +#include +#include +#include +#include + +#define JABBER_DISCO_DEBUG 0 + +class JabberClient; + +class JabberDiscoProtocol : public QObject, public KIO::SlaveBase +{ + +Q_OBJECT + +public: + JabberDiscoProtocol ( const QCString &pool_socket, const QCString &app_socket ); + virtual ~JabberDiscoProtocol (); + + void setHost ( const QString &host, int port, const QString &user, const QString &pass ); + + void openConnection (); + void closeConnection (); + + void slave_status (); + + void get ( const KURL &url ); + void listDir ( const KURL &url ); + void mimetype ( const KURL &url ); + + void dispatchLoop (); + +private slots: + void slotClientDebugMessage ( const QString &msg ); + void slotHandleTLSWarning ( int validityResult ); + void slotClientError ( JabberClient::ErrorCode errorCode ); + void slotConnected (); + void slotCSDisconnected (); + void slotCSError ( int error ); + + void slotQueryFinished (); + +private: + enum CommandType { Get, ListDir }; + + QString m_host, m_user, m_password; + int m_port; + KURL m_url; + bool m_connected; + + CommandType m_command; + + JabberClient *m_jabberClient; + +}; + +#endif diff --git a/kopete/protocols/jabber/kioslave/jabberdisco.protocol b/kopete/protocols/jabber/kioslave/jabberdisco.protocol new file mode 100644 index 00000000..01237e73 --- /dev/null +++ b/kopete/protocols/jabber/kioslave/jabberdisco.protocol @@ -0,0 +1,53 @@ +[Protocol] +exec=kio_jabberdisco +protocol=jabber +input=none +output=filesystem +reading=true +writing=false +makedir=false +linking=false +moving=false +Icon=remote +Description=A KIO slave for Jabber Service Discovery +Description[be]=Модуль kioslave Ð´Ð»Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ ÑервіÑаў Jabber +Description[bn]=Jabber সারà§à¦­à¦¿à¦¸ ডিসকভারির জনà§à¦¯ à¦à¦•à¦Ÿà¦¿ কে-আই-ও সà§à¦²à§‡à¦­ +Description[bs]=KIO slave za otkrivanje Jabber servisa +Description[ca]=Un esclau KIO pel servei de de descoberta del Jabber +Description[cs]=Pomocný protokol pro zjiÅ¡Å¥ování služeb Jabber +Description[da]=En kioslave til at opdage jabber service +Description[de]=Ein Ein-/Ausgabemodul zum Auffinden von Jabber-Diensten +Description[el]=Ένα kioslave για την ανίχνευση υπηÏεσίας Jabber +Description[es]=Un «kioslave» para el servicio de descubrimiento jabber +Description[et]=Jabberi teenuste tuvastamise KIO-moodul +Description[eu]=Jabber aurkikuntza zerbitzureako KIO morroi bat +Description[fa]=یک پیرو KIO برای خدمت اکتشاÙÛŒ Jabber +Description[fr]=Un module d'entrée / sortie pour la recherche de service Jabber +Description[gl]=Un KIO slave para Jabber Service Discovery +Description[hu]=KDE-protokoll a Jabber szolgáltatáskeresÅ‘ használatához +Description[is]=kioslave fyrir Jabber þjónustu uppgötvun +Description[it]=Un KIO slave per il servizio di discovery per Jabber +Description[ja]=Jabber Service Discovery ã® KIO スレーブ +Description[ka]=KIO slave Jabber სერვისის დირექტáƒáƒ áƒ˜áƒ˜áƒ¡áƒ—ვის +Description[kk]=Jabber қызметін байқау KIO slave қызметі +Description[km]=KIO slave មួយ​សម្រាប់​របក​គំហើញ​សáŸážœáž¶ Jabber +Description[lt]=Priedas (kioslave) FISH protokolui +Description[nb]=En kioslave for Jabber tjenestesøk +Description[nds]=En In-/Utgaavmoduul för't Finnen vun Jabber-Deensten +Description[ne]=जà¥à¤¯à¤¾à¤¬à¤° सेवा खोजीका लागि कियो सà¥à¤²à¤¾à¤­ +Description[nl]=Een kioslave voor Jabber Service Discovery +Description[nn]=Ein KIO-slave for Jabber-tenesteoppdaging +Description[pl]=Wtyczka protokoÅ‚u KIO dla usÅ‚ugi odkrywania usÅ‚ug Jabbera (Jabber Service Discovery) +Description[pt]=Um 'kioslave' para a Descoberta de Serviços do Jabber +Description[pt_BR]=Um KIO-Slave para a descoberta de serviço do Jabber +Description[ru]=Обработчик KIO Ð´Ð»Ñ Ð¾Ð±Ð½Ð°Ñ€ÑƒÐ¶ÐµÐ½Ð¸Ñ Ñлужб Jabber +Description[sk]=KIO otrok pre Jabber Service Discovery +Description[sl]=KIO slave za odkrivanje storitev za Jabber +Description[sr]=KIO Ñлуга за Jabber Service Discovery +Description[sr@Latn]=KIO sluga za Jabber Service Discovery +Description[sv]=En I/O-slav för Jabber tjänstupptäckt +Description[tr]=Jabber Servis Bulucu için KIOSlave +Description[uk]=Підлеглий Ð’/Ð’ Ð´Ð»Ñ Ð²Ð¸ÑÐ²Ð»ÐµÐ½Ð½Ñ Ñлужби Jabber +Description[zh_CN]=Jabber æœåŠ¡å‘现的 KIO slave +Description[zh_HK]=ç”¨æ–¼ç™¼ç¾ Jabber æœå‹™çš„ KIO slave +Description[zh_TW]=Jabber æœå‹™çš„ kioslave diff --git a/kopete/protocols/jabber/kopete_jabber.desktop b/kopete/protocols/jabber/kopete_jabber.desktop new file mode 100644 index 00000000..28c1f89d --- /dev/null +++ b/kopete/protocols/jabber/kopete_jabber.desktop @@ -0,0 +1,79 @@ +[Desktop Entry] +Type=Service +X-Kopete-Version=1000900 +Icon=jabber_protocol +ServiceTypes=Kopete/Protocol +X-KDE-Library=kopete_jabber +X-Kopete-Messaging-Protocol=messaging/xmpp +X-KDE-PluginInfo-Author=Kopete Developers +X-KDE-PluginInfo-Email=kopete-devel@kde.org +X-KDE-PluginInfo-Name=kopete_jabber +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Protocols +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Name=Jabber +Name[hi]=जैबर +Name[ne]=जà¥à¤¯à¤¾à¤¬à¤° +Name[pa]=ਜੱਬਰ +Name[ta]=ஜாபர௠+Comment=Protocol to connect to Jabber +Comment[ar]=البروتوكول سيتصل بـ Jabber +Comment[be]=Пратакол Jabber +Comment[bg]=Протокол за връзка Ñ Jabber +Comment[bn]=Jabber-ঠসংযোগ করতে পà§à¦°à§‹à¦Ÿà§‹à¦•à¦² +Comment[br]=Komenad kevreañ ouzh Jabber +Comment[bs]=Jabber protokol +Comment[ca]=Protocol per a connectar-se a Jabber +Comment[cs]=Protokol k pÅ™ipojení k Jabberu +Comment[cy]=Protocol i gysylltu â Jabber +Comment[da]=Protokol til at forbinde til Jabber +Comment[de]=Protokoll zur Verbindung mit Jabber +Comment[el]=ΠÏωτόκολλο για σÏνδεση στο Jabber +Comment[es]=Protocolo de conexión con Jabber +Comment[et]=Protokoll ühendumiseks Jabberiga +Comment[eu]=Jabber-era konektatzeko protokoloa +Comment[fa]=قرارداد برای اتصال به Jabber +Comment[fi]=Yhteyskäytäntö Jabber-verkkoon kytkeytymiseen +Comment[fr]=Protocole pour se connecter sur Jabber +Comment[ga]=Prótacal chun ceangal le Jabber +Comment[gl]=Protocolo para se conectar a Jabber +Comment[he]=פרוטוקול התחברות ל- Jabber +Comment[hi]=जैबर से जà¥à¤¡à¤¼à¤¨à¥‡ का पà¥à¤°à¥‹à¤Ÿà¥‹à¤•à¥‰à¤² +Comment[hr]=Protokol za povezivanje na Jabber +Comment[hu]=Protokoll a Jabber használatához +Comment[is]=Samskiptamáti til að tengjast Jabber +Comment[it]=Protocollo per connessione a Jabber +Comment[ja]=Jabber ã«æŽ¥ç¶šã™ã‚‹ãƒ—ロトコル +Comment[ka]=Jabberთáƒáƒœ დáƒáƒ™áƒáƒ•áƒ¨áƒ˜áƒ áƒ”ბის áƒáƒ¥áƒ›áƒ˜ +Comment[kk]=Jabber-ге қоÑылу протоколы +Comment[km]=ពិធីការ​ភ្ជាប់​ទៅ Jabber +Comment[lt]=Protokolas prisijungimui prie Jabber +Comment[mk]=Протокол за поврзување на Jabber +Comment[nb]=Protokoll for Ã¥ koble til Jabber +Comment[nds]=Protokoll för't Tokoppeln na Jabber +Comment[ne]=जà¥à¤¯à¤¾à¤¬à¤°à¤®à¤¾ जडान गरà¥à¤¨à¥‡ पà¥à¤°à¥‹à¤Ÿà¥‹à¤•à¤² +Comment[nl]=Protocol voor Jabber +Comment[nn]=Protokoll for Ã¥ kopla til Jabber +Comment[pl]=Protokół poÅ‚Ä…czenia z serwerem Jabbera +Comment[pt]=Um protocolo para se ligar ao Jabber +Comment[pt_BR]=Protocolo para conexão ao Jabber +Comment[ro]=Protocol de conectare la Jabber +Comment[ru]=Протокол Ð´Ð»Ñ Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ðº Jabber +Comment[sk]=Protokol pre pripojenie k Jabber +Comment[sl]=Protokol za povezavo na Jabber +Comment[sr]=Протокол за повезивање на Jabber +Comment[sr@Latn]=Protokol za povezivanje na Jabber +Comment[sv]=Protokoll för att ansluta till Jabber +Comment[ta]=ஜாபரà¯à®Ÿà®©à¯ இணைகà¯à®• விதிமà¯à®±à¯ˆ +Comment[tg]=Қарордоди пайваÑтшавӣ ба Jabber +Comment[tr]=Jabber'e baÄŸlantı iletiÅŸim kuralı +Comment[uk]=Протокол Ð´Ð»Ñ Ð·'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð· Jabber +Comment[uz]=Jabber uchun protokol +Comment[uz@cyrillic]=Jabber учун протокол +Comment[zh_CN]=连接到 Jabber åè®® +Comment[zh_HK]=用來連接至 Jabber 的通訊å”定 +Comment[zh_TW]=連到 Jabber çš„å”定 + diff --git a/kopete/protocols/jabber/libiris/001_last_activity.patch b/kopete/protocols/jabber/libiris/001_last_activity.patch new file mode 100644 index 00000000..24673e80 --- /dev/null +++ b/kopete/protocols/jabber/libiris/001_last_activity.patch @@ -0,0 +1,113 @@ +Index: iris/xmpp-im/xmpp_tasks.h +=================================================================== +--- iris/xmpp-im/xmpp_tasks.h (revision 419672) ++++ iris/xmpp-im/xmpp_tasks.h (working copy) +@@ -195,6 +195,29 @@ + Private *d; + }; + ++ class JT_GetLastActivity : public Task ++ { ++ Q_OBJECT ++ public: ++ JT_GetLastActivity(Task *); ++ ~JT_GetLastActivity(); ++ ++ void get(const Jid &); ++ ++ int seconds() const; ++ const QString &message() const; ++ ++ void onGo(); ++ bool take(const QDomElement &x); ++ ++ private: ++ class Private; ++ Private *d; ++ ++ QDomElement iq; ++ Jid jid; ++ }; ++ + class JT_GetServices : public Task + { + Q_OBJECT +Index: iris/xmpp-im/xmpp_tasks.cpp +=================================================================== +--- iris/xmpp-im/xmpp_tasks.cpp (revision 419672) ++++ iris/xmpp-im/xmpp_tasks.cpp (working copy) +@@ -773,6 +773,74 @@ + + + //---------------------------------------------------------------------------- ++// JT_GetLastActivity ++//---------------------------------------------------------------------------- ++class JT_GetLastActivity::Private ++{ ++public: ++ Private() {} ++ ++ int seconds; ++ QString message; ++}; ++ ++JT_GetLastActivity::JT_GetLastActivity(Task *parent) ++:Task(parent) ++{ ++ d = new Private; ++} ++ ++JT_GetLastActivity::~JT_GetLastActivity() ++{ ++ delete d; ++} ++ ++void JT_GetLastActivity::get(const Jid &j) ++{ ++ jid = j; ++ iq = createIQ(doc(), "get", jid.full(), id()); ++ QDomElement query = doc()->createElement("query"); ++ query.setAttribute("xmlns", "jabber:iq:last"); ++ iq.appendChild(query); ++} ++ ++int JT_GetLastActivity::seconds() const ++{ ++ return d->seconds; ++} ++ ++const QString &JT_GetLastActivity::message() const ++{ ++ return d->message; ++} ++ ++void JT_GetLastActivity::onGo() ++{ ++ send(iq); ++} ++ ++bool JT_GetLastActivity::take(const QDomElement &x) ++{ ++ if(!iqVerify(x, jid, id())) ++ return false; ++ ++ if(x.attribute("type") == "result") { ++ QDomElement q = queryTag(x); ++ ++ d->message = q.text(); ++ bool ok; ++ d->seconds = q.attribute("seconds").toInt(&ok); ++ ++ setSuccess(ok); ++ } ++ else { ++ setError(x); ++ } ++ ++ return true; ++} ++ ++//---------------------------------------------------------------------------- + // JT_GetServices + //---------------------------------------------------------------------------- + JT_GetServices::JT_GetServices(Task *parent) diff --git a/kopete/protocols/jabber/libiris/002_offline_event.patch b/kopete/protocols/jabber/libiris/002_offline_event.patch new file mode 100644 index 00000000..dfaa1f8e --- /dev/null +++ b/kopete/protocols/jabber/libiris/002_offline_event.patch @@ -0,0 +1,17 @@ +? 002_offline_event.patch +Index: iris/xmpp-im/types.cpp +=================================================================== +RCS file: /home/kde/kdenetwork/kopete/protocols/jabber/libiris/iris/xmpp-im/types.cpp,v +retrieving revision 1.3 +diff -u -p -r1.3 types.cpp +--- iris/xmpp-im/types.cpp 21 May 2004 14:35:44 -0000 1.3 ++++ iris/xmpp-im/types.cpp 5 Feb 2005 21:04:44 -0000 +@@ -639,6 +639,8 @@ bool Message::fromStanza(const Stanza &s + d->eventList += ComposingEvent; + else if (evtag == "delivered") + d->eventList += DeliveredEvent; ++ else if (evtag == "offline") ++ d->eventList += OfflineEvent; + } + if (d->eventList.isEmpty()) + d->eventList += CancelEvent; diff --git a/kopete/protocols/jabber/libiris/003_case_insensitive_jid.patch b/kopete/protocols/jabber/libiris/003_case_insensitive_jid.patch new file mode 100644 index 00000000..d4b0e285 --- /dev/null +++ b/kopete/protocols/jabber/libiris/003_case_insensitive_jid.patch @@ -0,0 +1,14 @@ +Index: iris/xmpp-core/jid.cpp +=================================================================== +--- iris/xmpp-core/jid.cpp (revision 469141) ++++ iris/xmpp-core/jid.cpp (working copy) +@@ -233,6 +233,9 @@ + b = d; + else + b = n + '@' + d; ++ ++ b=b.lower(); // JID are not case sensitive ++ + if(r.isEmpty()) + f = b; + else diff --git a/kopete/protocols/jabber/libiris/004_xhtml_im.patch b/kopete/protocols/jabber/libiris/004_xhtml_im.patch new file mode 100644 index 00000000..990ab4f7 --- /dev/null +++ b/kopete/protocols/jabber/libiris/004_xhtml_im.patch @@ -0,0 +1,266 @@ +Index: iris/include/xmpp.h +=================================================================== +--- iris/include/xmpp.h (revision 470311) ++++ iris/include/xmpp.h (working copy) +@@ -318,8 +318,11 @@ + + QDomDocument & doc() const; + QString baseNS() const; ++ QString xhtmlImNS() const; ++ QString xhtmlNS() const; + QDomElement createElement(const QString &ns, const QString &tagName); + QDomElement createTextElement(const QString &ns, const QString &tagName, const QString &text); ++ QDomElement createXHTMLElement(const QString &xHTML); + void appendChild(const QDomElement &e); + + Kind kind() const; +@@ -372,6 +375,8 @@ + + virtual QDomDocument & doc() const=0; + virtual QString baseNS() const=0; ++ virtual QString xhtmlImNS() const=0; ++ virtual QString xhtmlNS() const=0; + virtual bool old() const=0; + + virtual void close()=0; +@@ -479,6 +484,8 @@ + // reimplemented + QDomDocument & doc() const; + QString baseNS() const; ++ QString xhtmlImNS() const; ++ QString xhtmlNS() const; + bool old() const; + + void close(); +Index: iris/include/im.h +=================================================================== +--- iris/include/im.h (revision 470311) ++++ iris/include/im.h (working copy) +@@ -65,6 +65,7 @@ + QString lang() const; + QString subject(const QString &lang="") const; + QString body(const QString &lang="") const; ++ QString xHTMLBody(const QString &lang="") const; + QString thread() const; + Stanza::Error error() const; + +@@ -75,6 +76,7 @@ + void setLang(const QString &s); + void setSubject(const QString &s, const QString &lang=""); + void setBody(const QString &s, const QString &lang=""); ++ void setXHTMLBody(const QString &s, const QString &lang="", const QString &attr = ""); + void setThread(const QString &s); + void setError(const Stanza::Error &err); + +@@ -286,6 +288,7 @@ + bool canSearch() const; + bool canGroupchat() const; + bool canDisco() const; ++ bool canXHTML() const; + bool isGateway() const; + bool haveVCard() const; + +@@ -298,6 +301,7 @@ + FID_Disco, + FID_Gateway, + FID_VCard, ++ FID_Xhtml, + + // private Psi actions + FID_Add +Index: iris/xmpp-im/types.cpp +=================================================================== +--- iris/xmpp-im/types.cpp (revision 470311) ++++ iris/xmpp-im/types.cpp (working copy) +@@ -19,7 +19,7 @@ + */ + + #include"im.h" +- ++#include "protocol.h" + #include + #include + +@@ -180,7 +180,8 @@ + Jid to, from; + QString id, type, lang; + +- StringMap subject, body; ++ StringMap subject, body, xHTMLBody; ++ + QString thread; + Stanza::Error error; + +@@ -279,6 +280,11 @@ + return d->body[lang]; + } + ++QString Message::xHTMLBody(const QString &lang) const ++{ ++ return d->xHTMLBody[lang]; ++} ++ + QString Message::thread() const + { + return d->thread; +@@ -340,9 +346,16 @@ + void Message::setBody(const QString &s, const QString &lang) + { + d->body[lang] = s; +- //d->flag = false; + } + ++void Message::setXHTMLBody(const QString &s, const QString &lang, const QString &attr) ++{ ++ //ugly but needed if s is not a node but a list of leaf ++ ++ QString content = "\n" + s +"\n"; ++ d->xHTMLBody[lang] = content; ++} ++ + void Message::setThread(const QString &s) + { + d->thread = s; +@@ -489,7 +502,19 @@ + s.appendChild(e); + } + } +- ++ if ( !d->xHTMLBody.isEmpty()) { ++ QDomElement parent = s.createElement(s.xhtmlImNS(), "html"); ++ for(it = d->xHTMLBody.begin(); it != d->xHTMLBody.end(); ++it) { ++ const QString &str = it.data(); ++ if(!str.isEmpty()) { ++ QDomElement child = s.createXHTMLElement(str); ++ if(!it.key().isEmpty()) ++ child.setAttributeNS(NS_XML, "xml:lang", it.key()); ++ parent.appendChild(child); ++ } ++ } ++ s.appendChild(parent); ++ } + if(d->type == "error") + s.setError(d->error); + +@@ -591,6 +616,21 @@ + else if(e.tagName() == "thread") + d->thread = e.text(); + } ++ else if (e.namespaceURI() == s.xhtmlImNS()) { ++ if (e.tagName() == "html") { ++ QDomNodeList htmlNL= e.childNodes(); ++ for (unsigned int x = 0; x < htmlNL.count(); x++) { ++ QDomElement i = htmlNL.item(x).toElement(); ++ ++ if (i.tagName() == "body") { ++ QDomDocument RichText; ++ QString lang = i.attributeNS(NS_XML, "lang", ""); ++ RichText.appendChild(i); ++ d-> xHTMLBody[lang] = RichText.toString(); ++ } ++ } ++ } ++ } + else { + //printf("extension element: [%s]\n", e.tagName().latin1()); + } +@@ -1418,6 +1458,16 @@ + return test(ns); + } + ++#define FID_XHTML "http://jabber.org/protocol/xhtml-im" ++bool Features::canXHTML() const ++{ ++ QStringList ns; ++ ++ ns << FID_XHTML; ++ ++ return test(ns); ++} ++ + #define FID_GROUPCHAT "jabber:iq:conference" + bool Features::canGroupchat() const + { +Index: iris/xmpp-im/xmpp_tasks.cpp +=================================================================== +--- iris/xmpp-im/xmpp_tasks.cpp (revision 470311) ++++ iris/xmpp-im/xmpp_tasks.cpp (working copy) +@@ -1348,6 +1348,10 @@ + query.appendChild(feature); + + feature = doc()->createElement("feature"); ++ feature.setAttribute("var", "http://jabber.org/protocol/xhtml-im"); ++ query.appendChild(feature); ++ ++ feature = doc()->createElement("feature"); + feature.setAttribute("var", "http://jabber.org/protocol/si/profile/file-transfer"); + query.appendChild(feature); + +Index: iris/xmpp-core/protocol.h +=================================================================== +--- iris/xmpp-core/protocol.h (revision 470311) ++++ iris/xmpp-core/protocol.h (working copy) +@@ -35,6 +35,8 @@ + #define NS_SESSION "urn:ietf:params:xml:ns:xmpp-session" + #define NS_STANZAS "urn:ietf:params:xml:ns:xmpp-stanzas" + #define NS_BIND "urn:ietf:params:xml:ns:xmpp-bind" ++#define NS_XHTML_IM "http://jabber.org/protocol/xhtml-im" ++#define NS_XHTML "http://www.w3.org/1999/xhtml" + + namespace XMPP + { +Index: iris/xmpp-core/stream.cpp +=================================================================== +--- iris/xmpp-core/stream.cpp (revision 470311) ++++ iris/xmpp-core/stream.cpp (working copy) +@@ -293,6 +293,16 @@ + return d->s->baseNS(); + } + ++QString Stanza::xhtmlImNS() const ++{ ++ return d->s->xhtmlImNS(); ++} ++ ++QString Stanza::xhtmlNS() const ++{ ++ return d->s->xhtmlNS(); ++} ++ + QDomElement Stanza::createElement(const QString &ns, const QString &tagName) + { + return d->s->doc().createElementNS(ns, tagName); +@@ -305,6 +315,16 @@ + return e; + } + ++QDomElement Stanza::createXHTMLElement(const QString &xHTML) ++{ ++ QDomDocument doc; ++ ++ doc.setContent(xHTML, true); ++ QDomElement root = doc.documentElement(); ++ //QDomElement e; ++ return (root); ++} ++ + void Stanza::appendChild(const QDomElement &e) + { + d->e.appendChild(e); +@@ -861,6 +881,16 @@ + return NS_CLIENT; + } + ++QString ClientStream::xhtmlImNS() const ++{ ++ return NS_XHTML_IM; ++} ++ ++QString ClientStream::xhtmlNS() const ++{ ++ return NS_XHTML; ++} ++ + void ClientStream::setAllowPlain(bool b) + { + d->allowPlain = b; diff --git a/kopete/protocols/jabber/libiris/005_join_muc_with_password.patch b/kopete/protocols/jabber/libiris/005_join_muc_with_password.patch new file mode 100644 index 00000000..058825db --- /dev/null +++ b/kopete/protocols/jabber/libiris/005_join_muc_with_password.patch @@ -0,0 +1,163 @@ +Index: iris/include/im.h +=================================================================== +--- iris/include/im.h (révision 498969) ++++ iris/include/im.h (copie de travail) +@@ -607,6 +607,7 @@ + FileTransferManager *fileTransferManager() const; + + bool groupChatJoin(const QString &host, const QString &room, const QString &nick); ++ bool groupChatJoin(const QString &host, const QString &room, const QString &nick, const QString &password); + void groupChatSetStatus(const QString &host, const QString &room, const Status &); + void groupChatChangeNick(const QString &host, const QString &room, const QString &nick, const Status &); + void groupChatLeave(const QString &host, const QString &room); +Index: iris/xmpp-im/client.cpp +=================================================================== +--- iris/xmpp-im/client.cpp (révision 498969) ++++ iris/xmpp-im/client.cpp (copie de travail) +@@ -315,6 +315,35 @@ + return true; + } + ++bool Client::groupChatJoin(const QString &host, const QString &room, const QString &nick, const QString &password) ++{ ++ Jid jid(room + "@" + host + "/" + nick); ++ for(QValueList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end();) { ++ GroupChat &i = *it; ++ if(i.j.compare(jid, false)) { ++ // if this room is shutting down, then free it up ++ if(i.status == GroupChat::Closing) ++ it = d->groupChatList.remove(it); ++ else ++ return false; ++ } ++ else ++ ++it; ++ } ++ ++ debug(QString("Client: Joined: [%1]\n").arg(jid.full())); ++ GroupChat i; ++ i.j = jid; ++ i.status = GroupChat::Connecting; ++ d->groupChatList += i; ++ ++ JT_MucPresence *j = new JT_MucPresence(rootTask()); ++ j->pres(jid, Status(), password); ++ j->go(true); ++ ++ return true; ++} ++ + void Client::groupChatSetStatus(const QString &host, const QString &room, const Status &_s) + { + Jid jid(room + "@" + host); +Index: iris/xmpp-im/xmpp_tasks.h +=================================================================== +--- iris/xmpp-im/xmpp_tasks.h (révision 498969) ++++ iris/xmpp-im/xmpp_tasks.h (copie de travail) +@@ -439,6 +439,26 @@ + class Private; + Private *d; + }; ++ ++ class JT_MucPresence : public Task ++ { ++ Q_OBJECT ++ public: ++ JT_MucPresence(Task *parent); ++ ~JT_MucPresence(); ++ ++ void pres(const Status &); ++ void pres(const Jid &, const Status &, const QString &password); ++ ++ void onGo(); ++ ++ private: ++ QDomElement tag; ++ int type; ++ ++ class Private; ++ Private *d; ++ }; + } + + #endif +Index: iris/xmpp-im/xmpp_tasks.cpp +=================================================================== +--- iris/xmpp-im/xmpp_tasks.cpp (révision 498969) ++++ iris/xmpp-im/xmpp_tasks.cpp (copie de travail) +@@ -1956,3 +1956,75 @@ + return true; + } + ++//---------------------------------------------------------------------------- ++// JT_MucPresence ++//---------------------------------------------------------------------------- ++JT_MucPresence::JT_MucPresence(Task *parent) ++:Task(parent) ++{ ++ type = -1; ++} ++ ++JT_MucPresence::~JT_MucPresence() ++{ ++} ++ ++void JT_MucPresence::pres(const Status &s) ++{ ++ type = 0; ++ ++ tag = doc()->createElement("presence"); ++ if(!s.isAvailable()) { ++ tag.setAttribute("type", "unavailable"); ++ if(!s.status().isEmpty()) ++ tag.appendChild(textTag(doc(), "status", s.status())); ++ } ++ else { ++ if(s.isInvisible()) ++ tag.setAttribute("type", "invisible"); ++ ++ if(!s.show().isEmpty()) ++ tag.appendChild(textTag(doc(), "show", s.show())); ++ if(!s.status().isEmpty()) ++ tag.appendChild(textTag(doc(), "status", s.status())); ++ ++ tag.appendChild( textTag(doc(), "priority", QString("%1").arg(s.priority()) ) ); ++ ++ if(!s.keyID().isEmpty()) { ++ QDomElement x = textTag(doc(), "x", s.keyID()); ++ x.setAttribute("xmlns", "http://jabber.org/protocol/e2e"); ++ tag.appendChild(x); ++ } ++ if(!s.xsigned().isEmpty()) { ++ QDomElement x = textTag(doc(), "x", s.xsigned()); ++ x.setAttribute("xmlns", "jabber:x:signed"); ++ tag.appendChild(x); ++ } ++ ++ if(!s.capsNode().isEmpty() && !s.capsVersion().isEmpty()) { ++ QDomElement c = doc()->createElement("c"); ++ c.setAttribute("xmlns","http://jabber.org/protocol/caps"); ++ c.setAttribute("node",s.capsNode()); ++ c.setAttribute("ver",s.capsVersion()); ++ if (!s.capsExt().isEmpty()) ++ c.setAttribute("ext",s.capsExt()); ++ tag.appendChild(c); ++ } ++ } ++} ++ ++void JT_MucPresence::pres(const Jid &to, const Status &s, const QString &password) ++{ ++ pres(s); ++ tag.setAttribute("to", to.full()); ++ QDomElement x = textTag(doc(), "x", s.xsigned()); ++ x.setAttribute("xmlns", "http://jabber.org/protocol/muc"); ++ x.appendChild( textTag(doc(), "password", password.latin1()) ); ++ tag.appendChild(x); ++} ++ ++void JT_MucPresence::onGo() ++{ ++ send(tag); ++ setSuccess(); ++} diff --git a/kopete/protocols/jabber/libiris/006_private_storage.patch b/kopete/protocols/jabber/libiris/006_private_storage.patch new file mode 100644 index 00000000..288d24c5 --- /dev/null +++ b/kopete/protocols/jabber/libiris/006_private_storage.patch @@ -0,0 +1,130 @@ +Index: iris/xmpp-im/xmpp_tasks.h +=================================================================== +--- iris/xmpp-im/xmpp_tasks.h (revision 499691) ++++ iris/xmpp-im/xmpp_tasks.h (working copy) +@@ -459,6 +459,27 @@ + class Private; + Private *d; + }; ++ ++ class JT_PrivateStorage : public Task ++ { ++ Q_OBJECT ++ public: ++ JT_PrivateStorage(Task *parent); ++ ~JT_PrivateStorage(); ++ ++ void set(const QDomElement &); ++ void get(const QString &tag, const QString& xmlns); ++ ++ QDomElement element(); ++ ++ void onGo(); ++ bool take(const QDomElement &); ++ ++ private: ++ class Private; ++ Private *d; ++ }; ++ + } + + #endif +Index: iris/xmpp-im/xmpp_tasks.cpp +=================================================================== +--- iris/xmpp-im/xmpp_tasks.cpp (revision 499691) ++++ iris/xmpp-im/xmpp_tasks.cpp (working copy) +@@ -2028,3 +2028,93 @@ + send(tag); + setSuccess(); + } ++ ++ ++//---------------------------------------------------------------------------- ++// JT_PrivateStorage ++//---------------------------------------------------------------------------- ++class JT_PrivateStorage::Private ++{ ++ public: ++ Private() : type(-1) {} ++ ++ QDomElement iq; ++ QDomElement elem; ++ int type; ++}; ++ ++JT_PrivateStorage::JT_PrivateStorage(Task *parent) ++ :Task(parent) ++{ ++ d = new Private; ++} ++ ++JT_PrivateStorage::~JT_PrivateStorage() ++{ ++ delete d; ++} ++ ++void JT_PrivateStorage::get(const QString& tag, const QString& xmlns) ++{ ++ d->type = 0; ++ d->iq = createIQ(doc(), "get" , QString() , id() ); ++ QDomElement query = doc()->createElement("query"); ++ query.setAttribute("xmlns", "jabber:iq:private"); ++ d->iq.appendChild(query); ++ QDomElement s = doc()->createElement(tag); ++ if(!xmlns.isEmpty()) ++ s.setAttribute("xmlns", xmlns); ++ query.appendChild(s); ++} ++ ++void JT_PrivateStorage::set(const QDomElement& element) ++{ ++ d->type = 1; ++ d->elem=element; ++ QDomNode n=doc()->importNode(element,true); ++ ++ d->iq = createIQ(doc(), "set" , QString() , id() ); ++ QDomElement query = doc()->createElement("query"); ++ query.setAttribute("xmlns", "jabber:iq:private"); ++ d->iq.appendChild(query); ++ query.appendChild(n); ++} ++ ++void JT_PrivateStorage::onGo() ++{ ++ send(d->iq); ++} ++ ++bool JT_PrivateStorage::take(const QDomElement &x) ++{ ++ QString to = client()->host(); ++ if(!iqVerify(x, to, id())) ++ return false; ++ ++ if(x.attribute("type") == "result") { ++ if(d->type == 0) { ++ QDomElement q = queryTag(x); ++ for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { ++ QDomElement i = n.toElement(); ++ if(i.isNull()) ++ continue; ++ d->elem=i; ++ break; ++ } ++ } ++ setSuccess(); ++ return true; ++ } ++ else { ++ setError(x); ++ } ++ ++ return true; ++} ++ ++ ++QDomElement JT_PrivateStorage::element( ) ++{ ++ return d->elem; ++} ++ diff --git a/kopete/protocols/jabber/libiris/007_chatstates.patch b/kopete/protocols/jabber/libiris/007_chatstates.patch new file mode 100644 index 00000000..af32728c --- /dev/null +++ b/kopete/protocols/jabber/libiris/007_chatstates.patch @@ -0,0 +1,132 @@ +Index: iris/include/im.h +=================================================================== +--- iris/include/im.h (revision 525193) ++++ iris/include/im.h (working copy) +@@ -49,7 +49,7 @@ + typedef QValueList UrlList; + typedef QMap StringMap; + typedef enum { OfflineEvent, DeliveredEvent, DisplayedEvent, +- ComposingEvent, CancelEvent } MsgEvent; ++ ComposingEvent, CancelEvent, InactiveEvent, GoneEvent } MsgEvent; + + class Message + { +Index: iris/xmpp-im/types.cpp +=================================================================== +--- iris/xmpp-im/types.cpp (revision 525193) ++++ iris/xmpp-im/types.cpp (working copy) +@@ -544,28 +544,49 @@ + else + x.appendChild(s.createTextElement("jabber:x:event","id",d->eventId)); + } ++ else ++ s.appendChild( s.createElement(NS_CHATSTATES , "active" ) ); + ++ bool need_x_event=false; + for(QValueList::ConstIterator ev = d->eventList.begin(); ev != d->eventList.end(); ++ev) { + switch (*ev) { + case OfflineEvent: + x.appendChild(s.createElement("jabber:x:event", "offline")); ++ need_x_event=true; + break; + case DeliveredEvent: + x.appendChild(s.createElement("jabber:x:event", "delivered")); ++ need_x_event=true; + break; + case DisplayedEvent: + x.appendChild(s.createElement("jabber:x:event", "displayed")); ++ need_x_event=true; + break; + case ComposingEvent: + x.appendChild(s.createElement("jabber:x:event", "composing")); ++ need_x_event=true; ++ if (d->body.isEmpty()) ++ s.appendChild( s.createElement(NS_CHATSTATES , "composing" ) ); + break; + case CancelEvent: +- // Add nothing ++ need_x_event=true; ++ if (d->body.isEmpty()) ++ s.appendChild( s.createElement(NS_CHATSTATES , "paused" ) ); + break; ++ case InactiveEvent: ++ if (d->body.isEmpty()) ++ s.appendChild( s.createElement(NS_CHATSTATES , "inactive" ) ); ++ break; ++ case GoneEvent: ++ if (d->body.isEmpty()) ++ s.appendChild( s.createElement(NS_CHATSTATES , "gone" ) ); ++ break; + } + } +- s.appendChild(x); +- } ++ if(need_x_event) //we don't need to have the (empty) x:event element if this is only or ++ s.appendChild(x); ++ } ++ + + // xencrypted + if(!d->xencrypted.isEmpty()) +@@ -595,6 +616,7 @@ + d->subject.clear(); + d->body.clear(); + d->thread = QString(); ++ d->eventList.clear(); + + QDomElement root = s.element(); + +@@ -631,6 +653,33 @@ + } + } + } ++ else if (e.namespaceURI() == NS_CHATSTATES) ++ { ++ if(e.tagName() == "active") ++ { ++ //like in JEP-0022 we let the client know that we can receive ComposingEvent ++ // (we can do that according to §4.6 of the JEP-0085) ++ d->eventList += ComposingEvent; ++ d->eventList += InactiveEvent; ++ d->eventList += GoneEvent; ++ } ++ else if (e.tagName() == "composing") ++ { ++ d->eventList += ComposingEvent; ++ } ++ else if (e.tagName() == "paused") ++ { ++ d->eventList += CancelEvent; ++ } ++ else if (e.tagName() == "inactive") ++ { ++ d->eventList += InactiveEvent; ++ } ++ else if (e.tagName() == "gone") ++ { ++ d->eventList += GoneEvent; ++ } ++ } + else { + //printf("extension element: [%s]\n", e.tagName().latin1()); + } +@@ -664,7 +713,6 @@ + } + + // events +- d->eventList.clear(); + nl = root.elementsByTagNameNS("jabber:x:event", "x"); + if (nl.count()) { + nl = nl.item(0).childNodes(); +Index: iris/xmpp-core/protocol.h +=================================================================== +--- iris/xmpp-core/protocol.h (revision 525193) ++++ iris/xmpp-core/protocol.h (working copy) +@@ -37,6 +37,7 @@ + #define NS_BIND "urn:ietf:params:xml:ns:xmpp-bind" + #define NS_XHTML_IM "http://jabber.org/protocol/xhtml-im" + #define NS_XHTML "http://www.w3.org/1999/xhtml" ++#define NS_CHATSTATES "http://jabber.org/protocol/chatstates" + + namespace XMPP + { diff --git a/kopete/protocols/jabber/libiris/008_chatstatesfix.patch b/kopete/protocols/jabber/libiris/008_chatstatesfix.patch new file mode 100644 index 00000000..63a4f680 --- /dev/null +++ b/kopete/protocols/jabber/libiris/008_chatstatesfix.patch @@ -0,0 +1,38 @@ +Index: iris/xmpp-im/types.cpp +=================================================================== +--- iris/xmpp-im/types.cpp (revision 526236) ++++ iris/xmpp-im/types.cpp (working copy) +@@ -544,7 +544,7 @@ + else + x.appendChild(s.createTextElement("jabber:x:event","id",d->eventId)); + } +- else ++ else if (d->type=="chat" || d->type=="groupchat") + s.appendChild( s.createElement(NS_CHATSTATES , "active" ) ); + + bool need_x_event=false; +@@ -565,20 +565,20 @@ + case ComposingEvent: + x.appendChild(s.createElement("jabber:x:event", "composing")); + need_x_event=true; +- if (d->body.isEmpty()) ++ if (d->body.isEmpty() && (d->type=="chat" || d->type=="groupchat") ) + s.appendChild( s.createElement(NS_CHATSTATES , "composing" ) ); + break; + case CancelEvent: + need_x_event=true; +- if (d->body.isEmpty()) ++ if (d->body.isEmpty() && (d->type=="chat" || d->type=="groupchat") ) + s.appendChild( s.createElement(NS_CHATSTATES , "paused" ) ); + break; + case InactiveEvent: +- if (d->body.isEmpty()) ++ if (d->body.isEmpty() && (d->type=="chat" || d->type=="groupchat") ) + s.appendChild( s.createElement(NS_CHATSTATES , "inactive" ) ); + break; + case GoneEvent: +- if (d->body.isEmpty()) ++ if (d->body.isEmpty() && (d->type=="chat" || d->type=="groupchat") ) + s.appendChild( s.createElement(NS_CHATSTATES , "gone" ) ); + break; + } diff --git a/kopete/protocols/jabber/libiris/Makefile.am b/kopete/protocols/jabber/libiris/Makefile.am new file mode 100644 index 00000000..a80d204c --- /dev/null +++ b/kopete/protocols/jabber/libiris/Makefile.am @@ -0,0 +1,2 @@ +SUBDIRS = iris qca cutestuff + diff --git a/kopete/protocols/jabber/libiris/README_BEFORE_COMMITTING b/kopete/protocols/jabber/libiris/README_BEFORE_COMMITTING new file mode 100644 index 00000000..1fd42d3a --- /dev/null +++ b/kopete/protocols/jabber/libiris/README_BEFORE_COMMITTING @@ -0,0 +1,21 @@ +This library is the xmpp backend also used in Psi. (http://psi.affinix.com) +The main author is Justin Karneges (infiniti@affinix.com) and other +Psi developers, see the Psi homepage for details. + +Please DO NOT change the source unless really necessary. This is a +third-party library and any change will make synching very hard in the +future. It is best to send patches upstream so they'll end up in the +main tree. We will benefit from them at the next synch point. + +If you really really need to make a change to one of the source files, +please make sure to commit a diff to the original file in this directory in +the form of 001_your_fix_name.patch. Always pick the next free number +for your patch, the version found in this directory is meant to have +all patches applied in order. When committing, CCMAIL kopete-devel@kde.org. + +Changes to the Makefile.am files are fine and require no diffs, since Psi +uses qmake. + +This library depends on: libidn (compile time), qca-tls (runtime) + +27.02.2004, Till Gerken (till@tantalo.net) diff --git a/kopete/protocols/jabber/libiris/cutestuff/Makefile.am b/kopete/protocols/jabber/libiris/cutestuff/Makefile.am new file mode 100644 index 00000000..8f579310 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = network util diff --git a/kopete/protocols/jabber/libiris/cutestuff/README b/kopete/protocols/jabber/libiris/cutestuff/README new file mode 100644 index 00000000..c4509acc --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/README @@ -0,0 +1,13 @@ +iconset - generic classes for handling iconsets / animations +idle - detecting desktop idle +input - making life easier with text input (including richtext) +openpgp - pgp/gpg classes +richtext - richtext parsing function, xhtml conversion +ssl - SSL +tray - desktop tray icon +util - various things, see util/TODO +globalaccel - global hotkeys +network - sockets, servers, dns, and proxies +sasl - SASL library +xmlsec - XML Encryption +crash - generates some (hopefully useful) feedback when program crashes diff --git a/kopete/protocols/jabber/libiris/cutestuff/TODO b/kopete/protocols/jabber/libiris/cutestuff/TODO new file mode 100644 index 00000000..e897c854 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/TODO @@ -0,0 +1,25 @@ +test: + httppoll + sasl + xmlenc + +code: + bsocket: 'maintain' internal sockets even after destruct (till flush) + qssl: server support + securestream: wrap QSSLFilter as ByteStream + qrandom: better randomness (use /dev/urandom on unix, srand on windows) + floating TODOs in gnupg, gpgproc ? + import misha's code + finish globalaccel + finish dirwatch + trayicon? + +port: + win32: bconsole + +document: + sha1 + servsock + srvresolver + bsocket + diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/Makefile.am b/kopete/protocols/jabber/libiris/cutestuff/network/Makefile.am new file mode 100644 index 00000000..5e370089 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/Makefile.am @@ -0,0 +1,16 @@ +METASOURCES = AUTO + +noinst_LTLIBRARIES = libcutestuff_network.la +INCLUDES = -I$(srcdir)/../util -I$(srcdir)/../../qca/src $(all_includes) + +libcutestuff_network_la_SOURCES = \ + bsocket.cpp \ + httpconnect.cpp \ + httppoll.cpp \ + ndns.cpp \ + servsock.cpp \ + socks.cpp \ + srvresolver.cpp + +KDE_OPTIONS = nofinal + diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/bsocket.cpp b/kopete/protocols/jabber/libiris/cutestuff/network/bsocket.cpp new file mode 100644 index 00000000..57e5fe66 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/bsocket.cpp @@ -0,0 +1,394 @@ +/* + * bsocket.cpp - QSocket wrapper based on Bytestream with SRV DNS support + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"bsocket.h" + +#include +#include +#include +#include +#include"safedelete.h" +#ifndef NO_NDNS +#include"ndns.h" +#endif +#include"srvresolver.h" + +#ifdef BS_DEBUG +#include +#endif + +#define READBUFSIZE 65536 + +// CS_NAMESPACE_BEGIN + +class BSocket::Private +{ +public: + Private() + { + qsock = 0; + } + + QSocket *qsock; + int state; + +#ifndef NO_NDNS + NDns ndns; +#endif + SrvResolver srv; + QString host; + int port; + SafeDelete sd; +}; + +BSocket::BSocket(QObject *parent) +:ByteStream(parent) +{ + d = new Private; +#ifndef NO_NDNS + connect(&d->ndns, SIGNAL(resultsReady()), SLOT(ndns_done())); +#endif + connect(&d->srv, SIGNAL(resultsReady()), SLOT(srv_done())); + + reset(); +} + +BSocket::~BSocket() +{ + reset(true); + delete d; +} + +void BSocket::reset(bool clear) +{ + if(d->qsock) { + d->qsock->disconnect(this); + + if(!clear && d->qsock->isOpen()) { + // move remaining into the local queue + QByteArray block(d->qsock->bytesAvailable()); + d->qsock->readBlock(block.data(), block.size()); + appendRead(block); + } + + d->sd.deleteLater(d->qsock); + d->qsock = 0; + } + else { + if(clear) + clearReadBuffer(); + } + + if(d->srv.isBusy()) + d->srv.stop(); +#ifndef NO_NDNS + if(d->ndns.isBusy()) + d->ndns.stop(); +#endif + d->state = Idle; +} + +void BSocket::ensureSocket() +{ + if(!d->qsock) { + d->qsock = new QSocket; +#if QT_VERSION >= 0x030200 + d->qsock->setReadBufferSize(READBUFSIZE); +#endif + connect(d->qsock, SIGNAL(hostFound()), SLOT(qs_hostFound())); + connect(d->qsock, SIGNAL(connected()), SLOT(qs_connected())); + connect(d->qsock, SIGNAL(connectionClosed()), SLOT(qs_connectionClosed())); + connect(d->qsock, SIGNAL(delayedCloseFinished()), SLOT(qs_delayedCloseFinished())); + connect(d->qsock, SIGNAL(readyRead()), SLOT(qs_readyRead())); + connect(d->qsock, SIGNAL(bytesWritten(int)), SLOT(qs_bytesWritten(int))); + connect(d->qsock, SIGNAL(error(int)), SLOT(qs_error(int))); + } +} + +void BSocket::connectToHost(const QString &host, Q_UINT16 port) +{ + reset(true); + d->host = host; + d->port = port; +#ifdef NO_NDNS + d->state = Connecting; + do_connect(); +#else + d->state = HostLookup; + d->ndns.resolve(d->host); +#endif +} + +void BSocket::connectToServer(const QString &srv, const QString &type) +{ + reset(true); + d->state = HostLookup; + d->srv.resolve(srv, type, "tcp"); +} + +int BSocket::socket() const +{ + if(d->qsock) + return d->qsock->socket(); + else + return -1; +} + +void BSocket::setSocket(int s) +{ + reset(true); + ensureSocket(); + d->state = Connected; + d->qsock->setSocket(s); +} + +int BSocket::state() const +{ + return d->state; +} + +bool BSocket::isOpen() const +{ + if(d->state == Connected) + return true; + else + return false; +} + +void BSocket::close() +{ + if(d->state == Idle) + return; + + if(d->qsock) { + d->qsock->close(); + d->state = Closing; + if(d->qsock->bytesToWrite() == 0) + reset(); + } + else { + reset(); + } +} + +void BSocket::write(const QByteArray &a) +{ + if(d->state != Connected) + return; +#ifdef BS_DEBUG + QCString cs; + cs.resize(a.size()+1); + memcpy(cs.data(), a.data(), a.size()); + QString s = QString::fromUtf8(cs); + fprintf(stderr, "BSocket: writing [%d]: {%s}\n", a.size(), cs.data()); +#endif + d->qsock->writeBlock(a.data(), a.size()); +} + +QByteArray BSocket::read(int bytes) +{ + QByteArray block; + if(d->qsock) { + int max = bytesAvailable(); + if(bytes <= 0 || bytes > max) + bytes = max; + block.resize(bytes); + d->qsock->readBlock(block.data(), block.size()); + } + else + block = ByteStream::read(bytes); + +#ifdef BS_DEBUG + QCString cs; + cs.resize(block.size()+1); + memcpy(cs.data(), block.data(), block.size()); + QString s = QString::fromUtf8(cs); + fprintf(stderr, "BSocket: read [%d]: {%s}\n", block.size(), s.latin1()); +#endif + return block; +} + +int BSocket::bytesAvailable() const +{ + if(d->qsock) + return d->qsock->bytesAvailable(); + else + return ByteStream::bytesAvailable(); +} + +int BSocket::bytesToWrite() const +{ + if(!d->qsock) + return 0; + return d->qsock->bytesToWrite(); +} + +QHostAddress BSocket::address() const +{ + if(d->qsock) + return d->qsock->address(); + else + return QHostAddress(); +} + +Q_UINT16 BSocket::port() const +{ + if(d->qsock) + return d->qsock->port(); + else + return 0; +} + +QHostAddress BSocket::peerAddress() const +{ + if(d->qsock) + return d->qsock->peerAddress(); + else + return QHostAddress(); +} + +Q_UINT16 BSocket::peerPort() const +{ + if(d->qsock) + return d->qsock->port(); + else + return 0; +} + +void BSocket::srv_done() +{ + if(d->srv.failed()) { +#ifdef BS_DEBUG + fprintf(stderr, "BSocket: Error resolving hostname.\n"); +#endif + error(ErrHostNotFound); + return; + } + + d->host = d->srv.resultAddress().toString(); + d->port = d->srv.resultPort(); + do_connect(); + //QTimer::singleShot(0, this, SLOT(do_connect())); + //hostFound(); +} + +void BSocket::ndns_done() +{ +#ifndef NO_NDNS + if(d->ndns.result()) { + d->host = d->ndns.resultString(); + d->state = Connecting; + do_connect(); + //QTimer::singleShot(0, this, SLOT(do_connect())); + //hostFound(); + } + else { +#ifdef BS_DEBUG + fprintf(stderr, "BSocket: Error resolving hostname.\n"); +#endif + error(ErrHostNotFound); + } +#endif +} + +void BSocket::do_connect() +{ +#ifdef BS_DEBUG + fprintf(stderr, "BSocket: Connecting to %s:%d\n", d->host.latin1(), d->port); +#endif + ensureSocket(); + d->qsock->connectToHost(d->host, d->port); +} + +void BSocket::qs_hostFound() +{ + //SafeDeleteLock s(&d->sd); +} + +void BSocket::qs_connected() +{ + d->state = Connected; +#ifdef BS_DEBUG + fprintf(stderr, "BSocket: Connected.\n"); +#endif + SafeDeleteLock s(&d->sd); + connected(); +} + +void BSocket::qs_connectionClosed() +{ +#ifdef BS_DEBUG + fprintf(stderr, "BSocket: Connection Closed.\n"); +#endif + SafeDeleteLock s(&d->sd); + reset(); + connectionClosed(); +} + +void BSocket::qs_delayedCloseFinished() +{ +#ifdef BS_DEBUG + fprintf(stderr, "BSocket: Delayed Close Finished.\n"); +#endif + SafeDeleteLock s(&d->sd); + reset(); + delayedCloseFinished(); +} + +void BSocket::qs_readyRead() +{ + SafeDeleteLock s(&d->sd); + readyRead(); +} + +void BSocket::qs_bytesWritten(int x) +{ +#ifdef BS_DEBUG + fprintf(stderr, "BSocket: BytesWritten [%d].\n", x); +#endif + SafeDeleteLock s(&d->sd); + bytesWritten(x); +} + +void BSocket::qs_error(int x) +{ +#ifdef BS_DEBUG + fprintf(stderr, "BSocket: Error.\n"); +#endif + SafeDeleteLock s(&d->sd); + + // connection error during SRV host connect? try next + if(d->state == HostLookup && (x == QSocket::ErrConnectionRefused || x == QSocket::ErrHostNotFound)) { + d->srv.next(); + return; + } + + reset(); + if(x == QSocket::ErrConnectionRefused) + error(ErrConnectionRefused); + else if(x == QSocket::ErrHostNotFound) + error(ErrHostNotFound); + else if(x == QSocket::ErrSocketRead) + error(ErrRead); +} + +// CS_NAMESPACE_END + +#include "bsocket.moc" diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/bsocket.h b/kopete/protocols/jabber/libiris/cutestuff/network/bsocket.h new file mode 100644 index 00000000..bedaa54e --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/bsocket.h @@ -0,0 +1,87 @@ +/* + * bsocket.h - QSocket wrapper based on Bytestream with SRV DNS support + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_BSOCKET_H +#define CS_BSOCKET_H + +#include +#include +#include"bytestream.h" + +// CS_NAMESPACE_BEGIN + +class BSocket : public ByteStream +{ + Q_OBJECT +public: + enum Error { ErrConnectionRefused = ErrCustom, ErrHostNotFound }; + enum State { Idle, HostLookup, Connecting, Connected, Closing }; + BSocket(QObject *parent=0); + ~BSocket(); + + void connectToHost(const QString &host, Q_UINT16 port); + void connectToServer(const QString &srv, const QString &type); + int socket() const; + void setSocket(int); + int state() const; + + // from ByteStream + bool isOpen() const; + void close(); + void write(const QByteArray &); + QByteArray read(int bytes=0); + int bytesAvailable() const; + int bytesToWrite() const; + + // local + QHostAddress address() const; + Q_UINT16 port() const; + + // remote + QHostAddress peerAddress() const; + Q_UINT16 peerPort() const; + +signals: + void hostFound(); + void connected(); + +private slots: + void qs_hostFound(); + void qs_connected(); + void qs_connectionClosed(); + void qs_delayedCloseFinished(); + void qs_readyRead(); + void qs_bytesWritten(int); + void qs_error(int); + void srv_done(); + void ndns_done(); + void do_connect(); + +private: + class Private; + Private *d; + + void reset(bool clear=false); + void ensureSocket(); +}; + +// CS_NAMESPACE_END + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/httpconnect.cpp b/kopete/protocols/jabber/libiris/cutestuff/network/httpconnect.cpp new file mode 100644 index 00000000..c194324a --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/httpconnect.cpp @@ -0,0 +1,369 @@ +/* + * httpconnect.cpp - HTTP "CONNECT" proxy + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"httpconnect.h" + +#include +#include"bsocket.h" +#include"base64.h" + +#ifdef PROX_DEBUG +#include +#endif + +// CS_NAMESPACE_BEGIN + +static QString extractLine(QByteArray *buf, bool *found) +{ + // scan for newline + int n; + for(n = 0; n < (int)buf->size()-1; ++n) { + if(buf->at(n) == '\r' && buf->at(n+1) == '\n') { + QCString cstr; + cstr.resize(n+1); + memcpy(cstr.data(), buf->data(), n); + n += 2; // hack off CR/LF + + memmove(buf->data(), buf->data() + n, buf->size() - n); + buf->resize(buf->size() - n); + QString s = QString::fromUtf8(cstr); + + if(found) + *found = true; + return s; + } + } + + if(found) + *found = false; + return ""; +} + +static bool extractMainHeader(const QString &line, QString *proto, int *code, QString *msg) +{ + int n = line.find(' '); + if(n == -1) + return false; + if(proto) + *proto = line.mid(0, n); + ++n; + int n2 = line.find(' ', n); + if(n2 == -1) + return false; + if(code) + *code = line.mid(n, n2-n).toInt(); + n = n2+1; + if(msg) + *msg = line.mid(n); + return true; +} + +class HttpConnect::Private +{ +public: + Private() {} + + BSocket sock; + QString host; + int port; + QString user, pass; + QString real_host; + int real_port; + + QByteArray recvBuf; + + bool inHeader; + QStringList headerLines; + + int toWrite; + bool active; +}; + +HttpConnect::HttpConnect(QObject *parent) +:ByteStream(parent) +{ + d = new Private; + connect(&d->sock, SIGNAL(connected()), SLOT(sock_connected())); + connect(&d->sock, SIGNAL(connectionClosed()), SLOT(sock_connectionClosed())); + connect(&d->sock, SIGNAL(delayedCloseFinished()), SLOT(sock_delayedCloseFinished())); + connect(&d->sock, SIGNAL(readyRead()), SLOT(sock_readyRead())); + connect(&d->sock, SIGNAL(bytesWritten(int)), SLOT(sock_bytesWritten(int))); + connect(&d->sock, SIGNAL(error(int)), SLOT(sock_error(int))); + + reset(true); +} + +HttpConnect::~HttpConnect() +{ + reset(true); + delete d; +} + +void HttpConnect::reset(bool clear) +{ + if(d->sock.state() != BSocket::Idle) + d->sock.close(); + if(clear) { + clearReadBuffer(); + d->recvBuf.resize(0); + } + d->active = false; +} + +void HttpConnect::setAuth(const QString &user, const QString &pass) +{ + d->user = user; + d->pass = pass; +} + +void HttpConnect::connectToHost(const QString &proxyHost, int proxyPort, const QString &host, int port) +{ + reset(true); + + d->host = proxyHost; + d->port = proxyPort; + d->real_host = host; + d->real_port = port; + +#ifdef PROX_DEBUG + fprintf(stderr, "HttpConnect: Connecting to %s:%d", proxyHost.latin1(), proxyPort); + if(d->user.isEmpty()) + fprintf(stderr, "\n"); + else + fprintf(stderr, ", auth {%s,%s}\n", d->user.latin1(), d->pass.latin1()); +#endif + d->sock.connectToHost(d->host, d->port); +} + +bool HttpConnect::isOpen() const +{ + return d->active; +} + +void HttpConnect::close() +{ + d->sock.close(); + if(d->sock.bytesToWrite() == 0) + reset(); +} + +void HttpConnect::write(const QByteArray &buf) +{ + if(d->active) + d->sock.write(buf); +} + +QByteArray HttpConnect::read(int bytes) +{ + return ByteStream::read(bytes); +} + +int HttpConnect::bytesAvailable() const +{ + return ByteStream::bytesAvailable(); +} + +int HttpConnect::bytesToWrite() const +{ + if(d->active) + return d->sock.bytesToWrite(); + else + return 0; +} + +void HttpConnect::sock_connected() +{ +#ifdef PROX_DEBUG + fprintf(stderr, "HttpConnect: Connected\n"); +#endif + d->inHeader = true; + d->headerLines.clear(); + + // connected, now send the request + QString s; + s += QString("CONNECT ") + d->real_host + ':' + QString::number(d->real_port) + " HTTP/1.0\r\n"; + if(!d->user.isEmpty()) { + QString str = d->user + ':' + d->pass; + s += QString("Proxy-Authorization: Basic ") + Base64::encodeString(str) + "\r\n"; + } + s += "Proxy-Connection: Keep-Alive\r\n"; + s += "Pragma: no-cache\r\n"; + s += "\r\n"; + + QCString cs = s.utf8(); + QByteArray block(cs.length()); + memcpy(block.data(), cs.data(), block.size()); + d->toWrite = block.size(); + d->sock.write(block); +} + +void HttpConnect::sock_connectionClosed() +{ + if(d->active) { + reset(); + connectionClosed(); + } + else { + error(ErrProxyNeg); + } +} + +void HttpConnect::sock_delayedCloseFinished() +{ + if(d->active) { + reset(); + delayedCloseFinished(); + } +} + +void HttpConnect::sock_readyRead() +{ + QByteArray block = d->sock.read(); + + if(!d->active) { + ByteStream::appendArray(&d->recvBuf, block); + + if(d->inHeader) { + // grab available lines + while(1) { + bool found; + QString line = extractLine(&d->recvBuf, &found); + if(!found) + break; + if(line.isEmpty()) { + d->inHeader = false; + break; + } + d->headerLines += line; + } + + // done with grabbing the header? + if(!d->inHeader) { + QString str = d->headerLines.first(); + d->headerLines.remove(d->headerLines.begin()); + + QString proto; + int code; + QString msg; + if(!extractMainHeader(str, &proto, &code, &msg)) { +#ifdef PROX_DEBUG + fprintf(stderr, "HttpConnect: invalid header!\n"); +#endif + reset(true); + error(ErrProxyNeg); + return; + } + else { +#ifdef PROX_DEBUG + fprintf(stderr, "HttpConnect: header proto=[%s] code=[%d] msg=[%s]\n", proto.latin1(), code, msg.latin1()); + for(QStringList::ConstIterator it = d->headerLines.begin(); it != d->headerLines.end(); ++it) + fprintf(stderr, "HttpConnect: * [%s]\n", (*it).latin1()); +#endif + } + + if(code == 200) { // OK +#ifdef PROX_DEBUG + fprintf(stderr, "HttpConnect: << Success >>\n"); +#endif + d->active = true; + connected(); + + if(!d->recvBuf.isEmpty()) { + appendRead(d->recvBuf); + d->recvBuf.resize(0); + readyRead(); + return; + } + } + else { + int err; + QString errStr; + if(code == 407) { // Authentication failed + err = ErrProxyAuth; + errStr = tr("Authentication failed"); + } + else if(code == 404) { // Host not found + err = ErrHostNotFound; + errStr = tr("Host not found"); + } + else if(code == 403) { // Access denied + err = ErrProxyNeg; + errStr = tr("Access denied"); + } + else if(code == 503) { // Connection refused + err = ErrConnectionRefused; + errStr = tr("Connection refused"); + } + else { // invalid reply + err = ErrProxyNeg; + errStr = tr("Invalid reply"); + } + +#ifdef PROX_DEBUG + fprintf(stderr, "HttpConnect: << Error >> [%s]\n", errStr.latin1()); +#endif + reset(true); + error(err); + return; + } + } + } + } + else { + appendRead(block); + readyRead(); + return; + } +} + +void HttpConnect::sock_bytesWritten(int x) +{ + if(d->toWrite > 0) { + int size = x; + if(d->toWrite < x) + size = d->toWrite; + d->toWrite -= size; + x -= size; + } + + if(d->active && x > 0) + bytesWritten(x); +} + +void HttpConnect::sock_error(int x) +{ + if(d->active) { + reset(); + error(ErrRead); + } + else { + reset(true); + if(x == BSocket::ErrHostNotFound) + error(ErrProxyConnect); + else if(x == BSocket::ErrConnectionRefused) + error(ErrProxyConnect); + else if(x == BSocket::ErrRead) + error(ErrProxyNeg); + } +} + +// CS_NAMESPACE_END + +#include "httpconnect.moc" diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/httpconnect.h b/kopete/protocols/jabber/libiris/cutestuff/network/httpconnect.h new file mode 100644 index 00000000..38129c60 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/httpconnect.h @@ -0,0 +1,67 @@ +/* + * httpconnect.h - HTTP "CONNECT" proxy + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_HTTPCONNECT_H +#define CS_HTTPCONNECT_H + +#include"bytestream.h" + +// CS_NAMESPACE_BEGIN + +class HttpConnect : public ByteStream +{ + Q_OBJECT +public: + enum Error { ErrConnectionRefused = ErrCustom, ErrHostNotFound, ErrProxyConnect, ErrProxyNeg, ErrProxyAuth }; + HttpConnect(QObject *parent=0); + ~HttpConnect(); + + void setAuth(const QString &user, const QString &pass=""); + void connectToHost(const QString &proxyHost, int proxyPort, const QString &host, int port); + + // from ByteStream + bool isOpen() const; + void close(); + void write(const QByteArray &); + QByteArray read(int bytes=0); + int bytesAvailable() const; + int bytesToWrite() const; + +signals: + void connected(); + +private slots: + void sock_connected(); + void sock_connectionClosed(); + void sock_delayedCloseFinished(); + void sock_readyRead(); + void sock_bytesWritten(int); + void sock_error(int); + +private: + class Private; + Private *d; + + void reset(bool clear=false); +}; + +// CS_NAMESPACE_END + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/httppoll.cpp b/kopete/protocols/jabber/libiris/cutestuff/network/httppoll.cpp new file mode 100644 index 00000000..4975d0e5 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/httppoll.cpp @@ -0,0 +1,666 @@ +/* + * httppoll.cpp - HTTP polling proxy + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"httppoll.h" + +#include +#include +#include +#include +#include +#include +#include"bsocket.h" +#include"base64.h" + +#ifdef PROX_DEBUG +#include +#endif + +#define POLL_KEYS 64 + +// CS_NAMESPACE_BEGIN + +static QByteArray randomArray(int size) +{ + QByteArray a(size); + for(int n = 0; n < size; ++n) + a[n] = (char)(256.0*rand()/(RAND_MAX+1.0)); + return a; +} + +//---------------------------------------------------------------------------- +// HttpPoll +//---------------------------------------------------------------------------- +static QString hpk(int n, const QString &s) +{ + if(n == 0) + return s; + else + return Base64::arrayToString( QCA::SHA1::hash( QCString(hpk(n - 1, s).latin1()) ) ); +} + +class HttpPoll::Private +{ +public: + Private() {} + + HttpProxyPost http; + QString host; + int port; + QString user, pass; + QString url; + bool use_proxy; + + QByteArray out; + + int state; + bool closing; + QString ident; + + QTimer *t; + + QString key[POLL_KEYS]; + int key_n; + + int polltime; +}; + +HttpPoll::HttpPoll(QObject *parent) +:ByteStream(parent) +{ + d = new Private; + + d->polltime = 30; + d->t = new QTimer; + connect(d->t, SIGNAL(timeout()), SLOT(do_sync())); + + connect(&d->http, SIGNAL(result()), SLOT(http_result())); + connect(&d->http, SIGNAL(error(int)), SLOT(http_error(int))); + + reset(true); +} + +HttpPoll::~HttpPoll() +{ + reset(true); + delete d->t; + delete d; +} + +void HttpPoll::reset(bool clear) +{ + if(d->http.isActive()) + d->http.stop(); + if(clear) + clearReadBuffer(); + clearWriteBuffer(); + d->out.resize(0); + d->state = 0; + d->closing = false; + d->t->stop(); +} + +void HttpPoll::setAuth(const QString &user, const QString &pass) +{ + d->user = user; + d->pass = pass; +} + +void HttpPoll::connectToUrl(const QString &url) +{ + connectToHost("", 0, url); +} + +void HttpPoll::connectToHost(const QString &proxyHost, int proxyPort, const QString &url) +{ + reset(true); + + // using proxy? + if(!proxyHost.isEmpty()) { + d->host = proxyHost; + d->port = proxyPort; + d->url = url; + d->use_proxy = true; + } + else { + QUrl u = url; + d->host = u.host(); + if(u.hasPort()) + d->port = u.port(); + else + d->port = 80; + d->url = u.encodedPathAndQuery(); + d->use_proxy = false; + } + + resetKey(); + bool last; + QString key = getKey(&last); + +#ifdef PROX_DEBUG + fprintf(stderr, "HttpPoll: Connecting to %s:%d [%s]", d->host.latin1(), d->port, d->url.latin1()); + if(d->user.isEmpty()) + fprintf(stderr, "\n"); + else + fprintf(stderr, ", auth {%s,%s}\n", d->user.latin1(), d->pass.latin1()); +#endif + QGuardedPtr self = this; + syncStarted(); + if(!self) + return; + + d->state = 1; + d->http.setAuth(d->user, d->pass); + d->http.post(d->host, d->port, d->url, makePacket("0", key, "", QByteArray()), d->use_proxy); +} + +QByteArray HttpPoll::makePacket(const QString &ident, const QString &key, const QString &newkey, const QByteArray &block) +{ + QString str = ident; + if(!key.isEmpty()) { + str += ';'; + str += key; + } + if(!newkey.isEmpty()) { + str += ';'; + str += newkey; + } + str += ','; + QCString cs = str.latin1(); + int len = cs.length(); + + QByteArray a(len + block.size()); + memcpy(a.data(), cs.data(), len); + memcpy(a.data() + len, block.data(), block.size()); + return a; +} + +int HttpPoll::pollInterval() const +{ + return d->polltime; +} + +void HttpPoll::setPollInterval(int seconds) +{ + d->polltime = seconds; +} + +bool HttpPoll::isOpen() const +{ + return (d->state == 2 ? true: false); +} + +void HttpPoll::close() +{ + if(d->state == 0 || d->closing) + return; + + if(bytesToWrite() == 0) + reset(); + else + d->closing = true; +} + +void HttpPoll::http_result() +{ + // check for death :) + QGuardedPtr self = this; + syncFinished(); + if(!self) + return; + + // get id and packet + QString id; + QString cookie = d->http.getHeader("Set-Cookie"); + int n = cookie.find("ID="); + if(n == -1) { + reset(); + error(ErrRead); + return; + } + n += 3; + int n2 = cookie.find(';', n); + if(n2 != -1) + id = cookie.mid(n, n2-n); + else + id = cookie.mid(n); + QByteArray block = d->http.body(); + + // session error? + if(id.right(2) == ":0") { + if(id == "0:0" && d->state == 2) { + reset(); + connectionClosed(); + return; + } + else { + reset(); + error(ErrRead); + return; + } + } + + d->ident = id; + bool justNowConnected = false; + if(d->state == 1) { + d->state = 2; + justNowConnected = true; + } + + // sync up again soon + if(bytesToWrite() > 0 || !d->closing) + d->t->start(d->polltime * 1000, true); + + // connecting + if(justNowConnected) { + connected(); + } + else { + if(!d->out.isEmpty()) { + int x = d->out.size(); + d->out.resize(0); + takeWrite(x); + bytesWritten(x); + } + } + + if(!self) + return; + + if(!block.isEmpty()) { + appendRead(block); + readyRead(); + } + + if(!self) + return; + + if(bytesToWrite() > 0) { + do_sync(); + } + else { + if(d->closing) { + reset(); + delayedCloseFinished(); + return; + } + } +} + +void HttpPoll::http_error(int x) +{ + reset(); + if(x == HttpProxyPost::ErrConnectionRefused) + error(ErrConnectionRefused); + else if(x == HttpProxyPost::ErrHostNotFound) + error(ErrHostNotFound); + else if(x == HttpProxyPost::ErrSocket) + error(ErrRead); + else if(x == HttpProxyPost::ErrProxyConnect) + error(ErrProxyConnect); + else if(x == HttpProxyPost::ErrProxyNeg) + error(ErrProxyNeg); + else if(x == HttpProxyPost::ErrProxyAuth) + error(ErrProxyAuth); +} + +int HttpPoll::tryWrite() +{ + if(!d->http.isActive()) + do_sync(); + return 0; +} + +void HttpPoll::do_sync() +{ + if(d->http.isActive()) + return; + + d->t->stop(); + d->out = takeWrite(0, false); + + bool last; + QString key = getKey(&last); + QString newkey; + if(last) { + resetKey(); + newkey = getKey(&last); + } + + QGuardedPtr self = this; + syncStarted(); + if(!self) + return; + + d->http.post(d->host, d->port, d->url, makePacket(d->ident, key, newkey, d->out), d->use_proxy); +} + +void HttpPoll::resetKey() +{ +#ifdef PROX_DEBUG + fprintf(stderr, "HttpPoll: reset key!\n"); +#endif + QByteArray a = randomArray(64); + QString str = QString::fromLatin1(a.data(), a.size()); + + d->key_n = POLL_KEYS; + for(int n = 0; n < POLL_KEYS; ++n) + d->key[n] = hpk(n+1, str); +} + +const QString & HttpPoll::getKey(bool *last) +{ + *last = false; + --(d->key_n); + if(d->key_n == 0) + *last = true; + return d->key[d->key_n]; +} + + +//---------------------------------------------------------------------------- +// HttpProxyPost +//---------------------------------------------------------------------------- +static QString extractLine(QByteArray *buf, bool *found) +{ + // scan for newline + int n; + for(n = 0; n < (int)buf->size()-1; ++n) { + if(buf->at(n) == '\r' && buf->at(n+1) == '\n') { + QCString cstr; + cstr.resize(n+1); + memcpy(cstr.data(), buf->data(), n); + n += 2; // hack off CR/LF + + memmove(buf->data(), buf->data() + n, buf->size() - n); + buf->resize(buf->size() - n); + QString s = QString::fromUtf8(cstr); + + if(found) + *found = true; + return s; + } + } + + if(found) + *found = false; + return ""; +} + +static bool extractMainHeader(const QString &line, QString *proto, int *code, QString *msg) +{ + int n = line.find(' '); + if(n == -1) + return false; + if(proto) + *proto = line.mid(0, n); + ++n; + int n2 = line.find(' ', n); + if(n2 == -1) + return false; + if(code) + *code = line.mid(n, n2-n).toInt(); + n = n2+1; + if(msg) + *msg = line.mid(n); + return true; +} + +class HttpProxyPost::Private +{ +public: + Private() {} + + BSocket sock; + QByteArray postdata, recvBuf, body; + QString url; + QString user, pass; + bool inHeader; + QStringList headerLines; + bool asProxy; + QString host; +}; + +HttpProxyPost::HttpProxyPost(QObject *parent) +:QObject(parent) +{ + d = new Private; + connect(&d->sock, SIGNAL(connected()), SLOT(sock_connected())); + connect(&d->sock, SIGNAL(connectionClosed()), SLOT(sock_connectionClosed())); + connect(&d->sock, SIGNAL(readyRead()), SLOT(sock_readyRead())); + connect(&d->sock, SIGNAL(error(int)), SLOT(sock_error(int))); + reset(true); +} + +HttpProxyPost::~HttpProxyPost() +{ + reset(true); + delete d; +} + +void HttpProxyPost::reset(bool clear) +{ + if(d->sock.state() != BSocket::Idle) + d->sock.close(); + d->recvBuf.resize(0); + if(clear) + d->body.resize(0); +} + +void HttpProxyPost::setAuth(const QString &user, const QString &pass) +{ + d->user = user; + d->pass = pass; +} + +bool HttpProxyPost::isActive() const +{ + return (d->sock.state() == BSocket::Idle ? false: true); +} + +void HttpProxyPost::post(const QString &proxyHost, int proxyPort, const QString &url, const QByteArray &data, bool asProxy) +{ + reset(true); + + d->host = proxyHost; + d->url = url; + d->postdata = data; + d->asProxy = asProxy; + +#ifdef PROX_DEBUG + fprintf(stderr, "HttpProxyPost: Connecting to %s:%d", proxyHost.latin1(), proxyPort); + if(d->user.isEmpty()) + fprintf(stderr, "\n"); + else + fprintf(stderr, ", auth {%s,%s}\n", d->user.latin1(), d->pass.latin1()); +#endif + d->sock.connectToHost(proxyHost, proxyPort); +} + +void HttpProxyPost::stop() +{ + reset(); +} + +QByteArray HttpProxyPost::body() const +{ + return d->body; +} + +QString HttpProxyPost::getHeader(const QString &var) const +{ + for(QStringList::ConstIterator it = d->headerLines.begin(); it != d->headerLines.end(); ++it) { + const QString &s = *it; + int n = s.find(": "); + if(n == -1) + continue; + QString v = s.mid(0, n); + if(v == var) + return s.mid(n+2); + } + return ""; +} + +void HttpProxyPost::sock_connected() +{ +#ifdef PROX_DEBUG + fprintf(stderr, "HttpProxyPost: Connected\n"); +#endif + d->inHeader = true; + d->headerLines.clear(); + + QUrl u = d->url; + + // connected, now send the request + QString s; + s += QString("POST ") + d->url + " HTTP/1.0\r\n"; + if(d->asProxy) { + if(!d->user.isEmpty()) { + QString str = d->user + ':' + d->pass; + s += QString("Proxy-Authorization: Basic ") + Base64::encodeString(str) + "\r\n"; + } + s += "Proxy-Connection: Keep-Alive\r\n"; + s += "Pragma: no-cache\r\n"; + s += QString("Host: ") + u.host() + "\r\n"; + } + else { + s += QString("Host: ") + d->host + "\r\n"; + } + s += "Content-Type: application/x-www-form-urlencoded\r\n"; + s += QString("Content-Length: ") + QString::number(d->postdata.size()) + "\r\n"; + s += "\r\n"; + + // write request + QCString cs = s.utf8(); + QByteArray block(cs.length()); + memcpy(block.data(), cs.data(), block.size()); + d->sock.write(block); + + // write postdata + d->sock.write(d->postdata); +} + +void HttpProxyPost::sock_connectionClosed() +{ + d->body = d->recvBuf.copy(); + reset(); + result(); +} + +void HttpProxyPost::sock_readyRead() +{ + QByteArray block = d->sock.read(); + ByteStream::appendArray(&d->recvBuf, block); + + if(d->inHeader) { + // grab available lines + while(1) { + bool found; + QString line = extractLine(&d->recvBuf, &found); + if(!found) + break; + if(line.isEmpty()) { + d->inHeader = false; + break; + } + d->headerLines += line; + } + + // done with grabbing the header? + if(!d->inHeader) { + QString str = d->headerLines.first(); + d->headerLines.remove(d->headerLines.begin()); + + QString proto; + int code; + QString msg; + if(!extractMainHeader(str, &proto, &code, &msg)) { +#ifdef PROX_DEBUG + fprintf(stderr, "HttpProxyPost: invalid header!\n"); +#endif + reset(true); + error(ErrProxyNeg); + return; + } + else { +#ifdef PROX_DEBUG + fprintf(stderr, "HttpProxyPost: header proto=[%s] code=[%d] msg=[%s]\n", proto.latin1(), code, msg.latin1()); + for(QStringList::ConstIterator it = d->headerLines.begin(); it != d->headerLines.end(); ++it) + fprintf(stderr, "HttpProxyPost: * [%s]\n", (*it).latin1()); +#endif + } + + if(code == 200) { // OK +#ifdef PROX_DEBUG + fprintf(stderr, "HttpProxyPost: << Success >>\n"); +#endif + } + else { + int err; + QString errStr; + if(code == 407) { // Authentication failed + err = ErrProxyAuth; + errStr = tr("Authentication failed"); + } + else if(code == 404) { // Host not found + err = ErrHostNotFound; + errStr = tr("Host not found"); + } + else if(code == 403) { // Access denied + err = ErrProxyNeg; + errStr = tr("Access denied"); + } + else if(code == 503) { // Connection refused + err = ErrConnectionRefused; + errStr = tr("Connection refused"); + } + else { // invalid reply + err = ErrProxyNeg; + errStr = tr("Invalid reply"); + } + +#ifdef PROX_DEBUG + fprintf(stderr, "HttpProxyPost: << Error >> [%s]\n", errStr.latin1()); +#endif + reset(true); + error(err); + return; + } + } + } +} + +void HttpProxyPost::sock_error(int x) +{ +#ifdef PROX_DEBUG + fprintf(stderr, "HttpProxyPost: socket error: %d\n", x); +#endif + reset(true); + if(x == BSocket::ErrHostNotFound) + error(ErrProxyConnect); + else if(x == BSocket::ErrConnectionRefused) + error(ErrProxyConnect); + else if(x == BSocket::ErrRead) + error(ErrProxyNeg); +} + +// CS_NAMESPACE_END + +#include "httppoll.moc" diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/httppoll.h b/kopete/protocols/jabber/libiris/cutestuff/network/httppoll.h new file mode 100644 index 00000000..8bbebee3 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/httppoll.h @@ -0,0 +1,104 @@ +/* + * httppoll.h - HTTP polling proxy + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_HTTPPOLL_H +#define CS_HTTPPOLL_H + +#include"bytestream.h" + +// CS_NAMESPACE_BEGIN + +class HttpPoll : public ByteStream +{ + Q_OBJECT +public: + enum Error { ErrConnectionRefused = ErrCustom, ErrHostNotFound, ErrProxyConnect, ErrProxyNeg, ErrProxyAuth }; + HttpPoll(QObject *parent=0); + ~HttpPoll(); + + void setAuth(const QString &user, const QString &pass=""); + void connectToUrl(const QString &url); + void connectToHost(const QString &proxyHost, int proxyPort, const QString &url); + + int pollInterval() const; + void setPollInterval(int seconds); + + // from ByteStream + bool isOpen() const; + void close(); + +signals: + void connected(); + void syncStarted(); + void syncFinished(); + +protected: + int tryWrite(); + +private slots: + void http_result(); + void http_error(int); + void do_sync(); + +private: + class Private; + Private *d; + + void reset(bool clear=false); + QByteArray makePacket(const QString &ident, const QString &key, const QString &newkey, const QByteArray &block); + void resetKey(); + const QString & getKey(bool *); +}; + +class HttpProxyPost : public QObject +{ + Q_OBJECT +public: + enum Error { ErrConnectionRefused, ErrHostNotFound, ErrSocket, ErrProxyConnect, ErrProxyNeg, ErrProxyAuth }; + HttpProxyPost(QObject *parent=0); + ~HttpProxyPost(); + + void setAuth(const QString &user, const QString &pass=""); + bool isActive() const; + void post(const QString &proxyHost, int proxyPort, const QString &url, const QByteArray &data, bool asProxy=true); + void stop(); + QByteArray body() const; + QString getHeader(const QString &) const; + +signals: + void result(); + void error(int); + +private slots: + void sock_connected(); + void sock_connectionClosed(); + void sock_readyRead(); + void sock_error(int); + +private: + class Private; + Private *d; + + void reset(bool clear=false); +}; + +// CS_NAMESPACE_END + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/ndns.cpp b/kopete/protocols/jabber/libiris/cutestuff/network/ndns.cpp new file mode 100644 index 00000000..7fe60973 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/ndns.cpp @@ -0,0 +1,378 @@ +/* + * ndns.cpp - native DNS resolution + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +//! \class NDns ndns.h +//! \brief Simple DNS resolution using native system calls +//! +//! This class is to be used when Qt's QDns is not good enough. Because QDns +//! does not use threads, it cannot make a system call asyncronously. Thus, +//! QDns tries to imitate the behavior of each platform's native behavior, and +//! generally falls short. +//! +//! NDns uses a thread to make the system call happen in the background. This +//! gives your program native DNS behavior, at the cost of requiring threads +//! to build. +//! +//! \code +//! #include "ndns.h" +//! +//! ... +//! +//! NDns dns; +//! dns.resolve("psi.affinix.com"); +//! +//! // The class will emit the resultsReady() signal when the resolution +//! // is finished. You may then retrieve the results: +//! +//! uint ip_address = dns.result(); +//! +//! // or if you want to get the IP address as a string: +//! +//! QString ip_address = dns.resultString(); +//! \endcode + +#include"ndns.h" + +#include +#include +#include +#include + +#ifdef Q_OS_UNIX +#include +#include +#include +#include +#endif + +#ifdef Q_OS_WIN32 +#include +#endif + +// CS_NAMESPACE_BEGIN + +//! \if _hide_doc_ +class NDnsWorkerEvent : public QCustomEvent +{ +public: + enum Type { WorkerEvent = QEvent::User + 100 }; + NDnsWorkerEvent(NDnsWorker *); + + NDnsWorker *worker; +}; + +class NDnsWorker : public QThread +{ +public: + NDnsWorker(QObject *, const QCString &); + + bool success; + bool cancelled; + QHostAddress addr; + +protected: + void run(); + +private: + QCString host; + QObject *par; +}; +//! \endif + +//---------------------------------------------------------------------------- +// NDnsManager +//---------------------------------------------------------------------------- +#ifndef HAVE_GETHOSTBYNAME_R +static QMutex *workerMutex = 0; +static QMutex *workerCancelled = 0; +#endif +static NDnsManager *man = 0; +bool winsock_init = false; + +class NDnsManager::Item +{ +public: + NDns *ndns; + NDnsWorker *worker; +}; + +class NDnsManager::Private +{ +public: + Item *find(const NDns *n) + { + QPtrListIterator it(list); + for(Item *i; (i = it.current()); ++it) { + if(i->ndns == n) + return i; + } + return 0; + } + + Item *find(const NDnsWorker *w) + { + QPtrListIterator it(list); + for(Item *i; (i = it.current()); ++it) { + if(i->worker == w) + return i; + } + return 0; + } + + QPtrList list; +}; + +NDnsManager::NDnsManager() +{ +#ifndef HAVE_GETHOSTBYNAME_R + workerMutex = new QMutex; + workerCancelled = new QMutex; +#endif + +#ifdef Q_OS_WIN32 + if(!winsock_init) { + winsock_init = true; + QSocketDevice *sd = new QSocketDevice; + delete sd; + } +#endif + + d = new Private; + d->list.setAutoDelete(true); + + connect(qApp, SIGNAL(aboutToQuit()), SLOT(app_aboutToQuit())); +} + +NDnsManager::~NDnsManager() +{ + delete d; + +#ifndef HAVE_GETHOSTBYNAME_R + delete workerMutex; + workerMutex = 0; + delete workerCancelled; + workerCancelled = 0; +#endif +} + +void NDnsManager::resolve(NDns *self, const QString &name) +{ + Item *i = new Item; + i->ndns = self; + i->worker = new NDnsWorker(this, name.utf8()); + d->list.append(i); + + i->worker->start(); +} + +void NDnsManager::stop(NDns *self) +{ + Item *i = d->find(self); + if(!i) + return; + // disassociate + i->ndns = 0; + +#ifndef HAVE_GETHOSTBYNAME_R + // cancel + workerCancelled->lock(); + i->worker->cancelled = true; + workerCancelled->unlock(); +#endif +} + +bool NDnsManager::isBusy(const NDns *self) const +{ + Item *i = d->find(self); + return (i ? true: false); +} + +bool NDnsManager::event(QEvent *e) +{ + if((int)e->type() == (int)NDnsWorkerEvent::WorkerEvent) { + NDnsWorkerEvent *we = static_cast(e); + we->worker->wait(); // ensure that the thread is terminated + + Item *i = d->find(we->worker); + if(!i) { + // should NOT happen + return true; + } + QHostAddress addr = i->worker->addr; + NDns *ndns = i->ndns; + delete i->worker; + d->list.removeRef(i); + + // nuke manager if no longer needed (code that follows MUST BE SAFE!) + tryDestroy(); + + // requestor still around? + if(ndns) + ndns->finished(addr); + return true; + } + return false; +} + +void NDnsManager::tryDestroy() +{ + if(d->list.isEmpty()) { + man = 0; + delete this; + } +} + +void NDnsManager::app_aboutToQuit() +{ + while(man) { + QEventLoop *e = qApp->eventLoop(); + e->processEvents(QEventLoop::WaitForMore); + } +} + + +//---------------------------------------------------------------------------- +// NDns +//---------------------------------------------------------------------------- + +//! \fn void NDns::resultsReady() +//! This signal is emitted when the DNS resolution succeeds or fails. + +//! +//! Constructs an NDns object with parent \a parent. +NDns::NDns(QObject *parent) +:QObject(parent) +{ +} + +//! +//! Destroys the object and frees allocated resources. +NDns::~NDns() +{ + stop(); +} + +//! +//! Resolves hostname \a host (eg. psi.affinix.com) +void NDns::resolve(const QString &host) +{ + stop(); + if(!man) + man = new NDnsManager; + man->resolve(this, host); +} + +//! +//! Cancels the lookup action. +//! \note This will not stop the underlying system call, which must finish before the next lookup will proceed. +void NDns::stop() +{ + if(man) + man->stop(this); +} + +//! +//! Returns the IP address as a 32-bit integer in host-byte-order. This will be 0 if the lookup failed. +//! \sa resultsReady() +uint NDns::result() const +{ + return addr.ip4Addr(); +} + +//! +//! Returns the IP address as a string. This will be an empty string if the lookup failed. +//! \sa resultsReady() +QString NDns::resultString() const +{ + return addr.toString(); +} + +//! +//! Returns TRUE if busy resolving a hostname. +bool NDns::isBusy() const +{ + if(!man) + return false; + return man->isBusy(this); +} + +void NDns::finished(const QHostAddress &a) +{ + addr = a; + resultsReady(); +} + +//---------------------------------------------------------------------------- +// NDnsWorkerEvent +//---------------------------------------------------------------------------- +NDnsWorkerEvent::NDnsWorkerEvent(NDnsWorker *p) +:QCustomEvent(WorkerEvent) +{ + worker = p; +} + +//---------------------------------------------------------------------------- +// NDnsWorker +//---------------------------------------------------------------------------- +NDnsWorker::NDnsWorker(QObject *_par, const QCString &_host) +{ + success = cancelled = false; + par = _par; + host = _host.copy(); // do we need this to avoid sharing across threads? +} + +void NDnsWorker::run() +{ + hostent *h = 0; + +#ifdef HAVE_GETHOSTBYNAME_R + hostent buf; + char char_buf[1024]; + int err; + gethostbyname_r(host.data(), &buf, char_buf, sizeof(char_buf), &h, &err); +#else + // lock for gethostbyname + QMutexLocker locker(workerMutex); + + // check for cancel + workerCancelled->lock(); + bool cancel = cancelled; + workerCancelled->unlock(); + + if(!cancel) + h = gethostbyname(host.data()); +#endif + + if(!h) { + success = false; + QApplication::postEvent(par, new NDnsWorkerEvent(this)); + return; + } + + in_addr a = *((struct in_addr *)h->h_addr_list[0]); + addr.setAddress(ntohl(a.s_addr)); + success = true; + + QApplication::postEvent(par, new NDnsWorkerEvent(this)); +} + +// CS_NAMESPACE_END + +#include "ndns.moc" diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/ndns.h b/kopete/protocols/jabber/libiris/cutestuff/network/ndns.h new file mode 100644 index 00000000..c11d1a28 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/ndns.h @@ -0,0 +1,88 @@ +/* + * ndns.h - native DNS resolution + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_NDNS_H +#define CS_NDNS_H + +#include +#include +#include +#include +#include + +// CS_NAMESPACE_BEGIN + +class NDnsWorker; +class NDnsManager; + +class NDns : public QObject +{ + Q_OBJECT +public: + NDns(QObject *parent=0); + ~NDns(); + + void resolve(const QString &); + void stop(); + bool isBusy() const; + + uint result() const; + QString resultString() const; + +signals: + void resultsReady(); + +private: + QHostAddress addr; + + friend class NDnsManager; + void finished(const QHostAddress &); +}; + +class NDnsManager : public QObject +{ + Q_OBJECT +public: + ~NDnsManager(); + class Item; + +//! \if _hide_doc_ +protected: + bool event(QEvent *); +//! \endif + +private slots: + void app_aboutToQuit(); + +private: + class Private; + Private *d; + + friend class NDns; + NDnsManager(); + void resolve(NDns *self, const QString &name); + void stop(NDns *self); + bool isBusy(const NDns *self) const; + void tryDestroy(); +}; + +// CS_NAMESPACE_END + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/servsock.cpp b/kopete/protocols/jabber/libiris/cutestuff/network/servsock.cpp new file mode 100644 index 00000000..4aee36dc --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/servsock.cpp @@ -0,0 +1,112 @@ +/* + * servsock.cpp - simple wrapper to QServerSocket + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"servsock.h" + +// CS_NAMESPACE_BEGIN + +//---------------------------------------------------------------------------- +// ServSock +//---------------------------------------------------------------------------- +class ServSock::Private +{ +public: + Private() {} + + ServSockSignal *serv; +}; + +ServSock::ServSock(QObject *parent) +:QObject(parent) +{ + d = new Private; + d->serv = 0; +} + +ServSock::~ServSock() +{ + stop(); + delete d; +} + +bool ServSock::isActive() const +{ + return (d->serv ? true: false); +} + +bool ServSock::listen(Q_UINT16 port) +{ + stop(); + + d->serv = new ServSockSignal(port); + if(!d->serv->ok()) { + delete d->serv; + d->serv = 0; + return false; + } + connect(d->serv, SIGNAL(connectionReady(int)), SLOT(sss_connectionReady(int))); + + return true; +} + +void ServSock::stop() +{ + delete d->serv; + d->serv = 0; +} + +int ServSock::port() const +{ + if(d->serv) + return d->serv->port(); + else + return -1; +} + +QHostAddress ServSock::address() const +{ + if(d->serv) + return d->serv->address(); + else + return QHostAddress(); +} + +void ServSock::sss_connectionReady(int s) +{ + connectionReady(s); +} + + +//---------------------------------------------------------------------------- +// ServSockSignal +//---------------------------------------------------------------------------- +ServSockSignal::ServSockSignal(int port) +:QServerSocket(port, 16) +{ +} + +void ServSockSignal::newConnection(int x) +{ + connectionReady(x); +} + +// CS_NAMESPACE_END + +#include "servsock.moc" diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/servsock.h b/kopete/protocols/jabber/libiris/cutestuff/network/servsock.h new file mode 100644 index 00000000..60a0c99d --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/servsock.h @@ -0,0 +1,68 @@ +/* + * servsock.h - simple wrapper to QServerSocket + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_SERVSOCK_H +#define CS_SERVSOCK_H + +#include + +// CS_NAMESPACE_BEGIN + +class ServSock : public QObject +{ + Q_OBJECT +public: + ServSock(QObject *parent=0); + ~ServSock(); + + bool isActive() const; + bool listen(Q_UINT16 port); + void stop(); + int port() const; + QHostAddress address() const; + +signals: + void connectionReady(int); + +private slots: + void sss_connectionReady(int); + +private: + class Private; + Private *d; +}; + +class ServSockSignal : public QServerSocket +{ + Q_OBJECT +public: + ServSockSignal(int port); + +signals: + void connectionReady(int); + +protected: + // reimplemented + void newConnection(int); +}; + +// CS_NAMESPACE_END + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/socks.cpp b/kopete/protocols/jabber/libiris/cutestuff/network/socks.cpp new file mode 100644 index 00000000..bae374f5 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/socks.cpp @@ -0,0 +1,1223 @@ +/* + * socks.cpp - SOCKS5 TCP proxy client/server + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"socks.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_UNIX +#include +#include +#endif + +#ifdef Q_OS_WIN32 +#include +#endif + +#include"servsock.h" +#include"bsocket.h" + +#ifdef PROX_DEBUG +#include +#endif + +// CS_NAMESPACE_BEGIN + +//---------------------------------------------------------------------------- +// SocksUDP +//---------------------------------------------------------------------------- +static QByteArray sp_create_udp(const QString &host, Q_UINT16 port, const QByteArray &buf) +{ + // detect for IP addresses + //QHostAddress addr; + //if(addr.setAddress(host)) + // return sp_set_request(addr, port, cmd1); + + QCString h = host.utf8(); + h.truncate(255); + h = QString::fromUtf8(h).utf8(); // delete any partial characters? + int hlen = h.length(); + + int at = 0; + QByteArray a(4); + a[at++] = 0x00; // reserved + a[at++] = 0x00; // reserved + a[at++] = 0x00; // frag + a[at++] = 0x03; // address type = domain + + // host + a.resize(at+hlen+1); + a[at++] = hlen; + memcpy(a.data() + at, h.data(), hlen); + at += hlen; + + // port + a.resize(at+2); + unsigned short p = htons(port); + memcpy(a.data() + at, &p, 2); + at += 2; + + a.resize(at+buf.size()); + memcpy(a.data() + at, buf.data(), buf.size()); + + return a; +} + +struct SPS_UDP +{ + QString host; + Q_UINT16 port; + QByteArray data; +}; + +static int sp_read_udp(QByteArray *from, SPS_UDP *s) +{ + int full_len = 4; + if((int)from->size() < full_len) + return 0; + + QString host; + QHostAddress addr; + unsigned char atype = from->at(3); + + if(atype == 0x01) { + full_len += 4; + if((int)from->size() < full_len) + return 0; + Q_UINT32 ip4; + memcpy(&ip4, from->data() + 4, 4); + addr.setAddress(ntohl(ip4)); + host = addr.toString(); + } + else if(atype == 0x03) { + ++full_len; + if((int)from->size() < full_len) + return 0; + unsigned char host_len = from->at(4); + full_len += host_len; + if((int)from->size() < full_len) + return 0; + QCString cs(host_len+1); + memcpy(cs.data(), from->data() + 5, host_len); + host = QString::fromLatin1(cs); + } + else if(atype == 0x04) { + full_len += 16; + if((int)from->size() < full_len) + return 0; + Q_UINT8 a6[16]; + memcpy(a6, from->data() + 4, 16); + addr.setAddress(a6); + host = addr.toString(); + } + + full_len += 2; + if((int)from->size() < full_len) + return 0; + + Q_UINT16 p; + memcpy(&p, from->data() + full_len - 2, 2); + + s->host = host; + s->port = ntohs(p); + s->data.resize(from->size() - full_len); + memcpy(s->data.data(), from->data() + full_len, s->data.size()); + + return 1; +} + +class SocksUDP::Private +{ +public: + QSocketDevice *sd; + QSocketNotifier *sn; + SocksClient *sc; + QHostAddress routeAddr; + int routePort; + QString host; + int port; +}; + +SocksUDP::SocksUDP(SocksClient *sc, const QString &host, int port, const QHostAddress &routeAddr, int routePort) +:QObject(sc) +{ + d = new Private; + d->sc = sc; + d->sd = new QSocketDevice(QSocketDevice::Datagram); + d->sd->setBlocking(false); + d->sn = new QSocketNotifier(d->sd->socket(), QSocketNotifier::Read); + connect(d->sn, SIGNAL(activated(int)), SLOT(sn_activated(int))); + d->host = host; + d->port = port; + d->routeAddr = routeAddr; + d->routePort = routePort; +} + +SocksUDP::~SocksUDP() +{ + delete d->sn; + delete d->sd; + delete d; +} + +void SocksUDP::change(const QString &host, int port) +{ + d->host = host; + d->port = port; +} + +void SocksUDP::write(const QByteArray &data) +{ + QByteArray buf = sp_create_udp(d->host, d->port, data); + d->sd->setBlocking(true); + d->sd->writeBlock(buf.data(), buf.size(), d->routeAddr, d->routePort); + d->sd->setBlocking(false); +} + +void SocksUDP::sn_activated(int) +{ + QByteArray buf(8192); + int actual = d->sd->readBlock(buf.data(), buf.size()); + buf.resize(actual); + packetReady(buf); +} + +//---------------------------------------------------------------------------- +// SocksClient +//---------------------------------------------------------------------------- +#define REQ_CONNECT 0x01 +#define REQ_BIND 0x02 +#define REQ_UDPASSOCIATE 0x03 + +#define RET_SUCCESS 0x00 +#define RET_UNREACHABLE 0x04 +#define RET_CONNREFUSED 0x05 + +// spc = socks packet client +// sps = socks packet server +// SPCS = socks packet client struct +// SPSS = socks packet server struct + +// Version +static QByteArray spc_set_version() +{ + QByteArray ver(4); + ver[0] = 0x05; // socks version 5 + ver[1] = 0x02; // number of methods + ver[2] = 0x00; // no-auth + ver[3] = 0x02; // username + return ver; +} + +static QByteArray sps_set_version(int method) +{ + QByteArray ver(2); + ver[0] = 0x05; + ver[1] = method; + return ver; +} + +struct SPCS_VERSION +{ + unsigned char version; + QByteArray methodList; +}; + +static int spc_get_version(QByteArray *from, SPCS_VERSION *s) +{ + if(from->size() < 1) + return 0; + if(from->at(0) != 0x05) // only SOCKS5 supported + return -1; + if(from->size() < 2) + return 0; + uint num = from->at(1); + if(num > 16) // who the heck has over 16 auth methods?? + return -1; + if(from->size() < 2 + num) + return 0; + QByteArray a = ByteStream::takeArray(from, 2+num); + s->version = a[0]; + s->methodList.resize(num); + memcpy(s->methodList.data(), a.data() + 2, num); + return 1; +} + +struct SPSS_VERSION +{ + unsigned char version; + unsigned char method; +}; + +static int sps_get_version(QByteArray *from, SPSS_VERSION *s) +{ + if(from->size() < 2) + return 0; + QByteArray a = ByteStream::takeArray(from, 2); + s->version = a[0]; + s->method = a[1]; + return 1; +} + +// authUsername +static QByteArray spc_set_authUsername(const QCString &user, const QCString &pass) +{ + int len1 = user.length(); + int len2 = pass.length(); + if(len1 > 255) + len1 = 255; + if(len2 > 255) + len2 = 255; + QByteArray a(1+1+len1+1+len2); + a[0] = 0x01; // username auth version 1 + a[1] = len1; + memcpy(a.data() + 2, user.data(), len1); + a[2+len1] = len2; + memcpy(a.data() + 3 + len1, pass.data(), len2); + return a; +} + +static QByteArray sps_set_authUsername(bool success) +{ + QByteArray a(2); + a[0] = 0x01; + a[1] = success ? 0x00 : 0xff; + return a; +} + +struct SPCS_AUTHUSERNAME +{ + QString user, pass; +}; + +static int spc_get_authUsername(QByteArray *from, SPCS_AUTHUSERNAME *s) +{ + if(from->size() < 1) + return 0; + unsigned char ver = from->at(0); + if(ver != 0x01) + return -1; + if(from->size() < 2) + return 0; + unsigned char ulen = from->at(1); + if((int)from->size() < ulen + 3) + return 0; + unsigned char plen = from->at(ulen+2); + if((int)from->size() < ulen + plen + 3) + return 0; + QByteArray a = ByteStream::takeArray(from, ulen + plen + 3); + + QCString user, pass; + user.resize(ulen+1); + pass.resize(plen+1); + memcpy(user.data(), a.data()+2, ulen); + memcpy(pass.data(), a.data()+ulen+3, plen); + s->user = QString::fromUtf8(user); + s->pass = QString::fromUtf8(pass); + return 1; +} + +struct SPSS_AUTHUSERNAME +{ + unsigned char version; + bool success; +}; + +static int sps_get_authUsername(QByteArray *from, SPSS_AUTHUSERNAME *s) +{ + if(from->size() < 2) + return 0; + QByteArray a = ByteStream::takeArray(from, 2); + s->version = a[0]; + s->success = a[1] == 0 ? true: false; + return 1; +} + +// connectRequest +static QByteArray sp_set_request(const QHostAddress &addr, unsigned short port, unsigned char cmd1) +{ + int at = 0; + QByteArray a(4); + a[at++] = 0x05; // socks version 5 + a[at++] = cmd1; + a[at++] = 0x00; // reserved + if(addr.isIp4Addr()) { + a[at++] = 0x01; // address type = ipv4 + Q_UINT32 ip4 = htonl(addr.ip4Addr()); + a.resize(at+4); + memcpy(a.data() + at, &ip4, 4); + at += 4; + } + else { + a[at++] = 0x04; + Q_UINT8 a6[16]; + QStringList s6 = QStringList::split(':', addr.toString(), true); + int at = 0; + Q_UINT16 c; + bool ok; + for(QStringList::ConstIterator it = s6.begin(); it != s6.end(); ++it) { + c = (*it).toInt(&ok, 16); + a6[at++] = (c >> 8); + a6[at++] = c & 0xff; + } + a.resize(at+16); + memcpy(a.data() + at, a6, 16); + at += 16; + } + + // port + a.resize(at+2); + unsigned short p = htons(port); + memcpy(a.data() + at, &p, 2); + + return a; +} + +static QByteArray sp_set_request(const QString &host, Q_UINT16 port, unsigned char cmd1) +{ + // detect for IP addresses + QHostAddress addr; + if(addr.setAddress(host)) + return sp_set_request(addr, port, cmd1); + + QCString h = host.utf8(); + h.truncate(255); + h = QString::fromUtf8(h).utf8(); // delete any partial characters? + int hlen = h.length(); + + int at = 0; + QByteArray a(4); + a[at++] = 0x05; // socks version 5 + a[at++] = cmd1; + a[at++] = 0x00; // reserved + a[at++] = 0x03; // address type = domain + + // host + a.resize(at+hlen+1); + a[at++] = hlen; + memcpy(a.data() + at, h.data(), hlen); + at += hlen; + + // port + a.resize(at+2); + unsigned short p = htons(port); + memcpy(a.data() + at, &p, 2); + + return a; +} + +struct SPS_CONNREQ +{ + unsigned char version; + unsigned char cmd; + int address_type; + QString host; + QHostAddress addr; + Q_UINT16 port; +}; + +static int sp_get_request(QByteArray *from, SPS_CONNREQ *s) +{ + int full_len = 4; + if((int)from->size() < full_len) + return 0; + + QString host; + QHostAddress addr; + unsigned char atype = from->at(3); + + if(atype == 0x01) { + full_len += 4; + if((int)from->size() < full_len) + return 0; + Q_UINT32 ip4; + memcpy(&ip4, from->data() + 4, 4); + addr.setAddress(ntohl(ip4)); + } + else if(atype == 0x03) { + ++full_len; + if((int)from->size() < full_len) + return 0; + unsigned char host_len = from->at(4); + full_len += host_len; + if((int)from->size() < full_len) + return 0; + QCString cs(host_len+1); + memcpy(cs.data(), from->data() + 5, host_len); + host = QString::fromLatin1(cs); + } + else if(atype == 0x04) { + full_len += 16; + if((int)from->size() < full_len) + return 0; + Q_UINT8 a6[16]; + memcpy(a6, from->data() + 4, 16); + addr.setAddress(a6); + } + + full_len += 2; + if((int)from->size() < full_len) + return 0; + + QByteArray a = ByteStream::takeArray(from, full_len); + + Q_UINT16 p; + memcpy(&p, a.data() + full_len - 2, 2); + + s->version = a[0]; + s->cmd = a[1]; + s->address_type = atype; + s->host = host; + s->addr = addr; + s->port = ntohs(p); + + return 1; +} + +enum { StepVersion, StepAuth, StepRequest }; + +class SocksClient::Private +{ +public: + Private() {} + + BSocket sock; + QString host; + int port; + QString user, pass; + QString real_host; + int real_port; + + QByteArray recvBuf; + bool active; + int step; + int authMethod; + bool incoming, waiting; + + QString rhost; + int rport; + + int pending; + + bool udp; + QString udpAddr; + int udpPort; +}; + +SocksClient::SocksClient(QObject *parent) +:ByteStream(parent) +{ + init(); + + d->incoming = false; +} + +SocksClient::SocksClient(int s, QObject *parent) +:ByteStream(parent) +{ + init(); + + d->incoming = true; + d->waiting = true; + d->sock.setSocket(s); +} + +void SocksClient::init() +{ + d = new Private; + connect(&d->sock, SIGNAL(connected()), SLOT(sock_connected())); + connect(&d->sock, SIGNAL(connectionClosed()), SLOT(sock_connectionClosed())); + connect(&d->sock, SIGNAL(delayedCloseFinished()), SLOT(sock_delayedCloseFinished())); + connect(&d->sock, SIGNAL(readyRead()), SLOT(sock_readyRead())); + connect(&d->sock, SIGNAL(bytesWritten(int)), SLOT(sock_bytesWritten(int))); + connect(&d->sock, SIGNAL(error(int)), SLOT(sock_error(int))); + + reset(true); +} + +SocksClient::~SocksClient() +{ + reset(true); + delete d; +} + +void SocksClient::reset(bool clear) +{ + if(d->sock.state() != BSocket::Idle) + d->sock.close(); + if(clear) + clearReadBuffer(); + d->recvBuf.resize(0); + d->active = false; + d->waiting = false; + d->udp = false; + d->pending = 0; +} + +bool SocksClient::isIncoming() const +{ + return d->incoming; +} + +void SocksClient::setAuth(const QString &user, const QString &pass) +{ + d->user = user; + d->pass = pass; +} + +void SocksClient::connectToHost(const QString &proxyHost, int proxyPort, const QString &host, int port, bool udpMode) +{ + reset(true); + + d->host = proxyHost; + d->port = proxyPort; + d->real_host = host; + d->real_port = port; + d->udp = udpMode; + +#ifdef PROX_DEBUG + fprintf(stderr, "SocksClient: Connecting to %s:%d", proxyHost.latin1(), proxyPort); + if(d->user.isEmpty()) + fprintf(stderr, "\n"); + else + fprintf(stderr, ", auth {%s,%s}\n", d->user.latin1(), d->pass.latin1()); +#endif + d->sock.connectToHost(d->host, d->port); +} + +bool SocksClient::isOpen() const +{ + return d->active; +} + +void SocksClient::close() +{ + d->sock.close(); + if(d->sock.bytesToWrite() == 0) + reset(); +} + +void SocksClient::writeData(const QByteArray &buf) +{ + d->pending += buf.size(); + d->sock.write(buf); +} + +void SocksClient::write(const QByteArray &buf) +{ + if(d->active && !d->udp) + d->sock.write(buf); +} + +QByteArray SocksClient::read(int bytes) +{ + return ByteStream::read(bytes); +} + +int SocksClient::bytesAvailable() const +{ + return ByteStream::bytesAvailable(); +} + +int SocksClient::bytesToWrite() const +{ + if(d->active) + return d->sock.bytesToWrite(); + else + return 0; +} + +void SocksClient::sock_connected() +{ +#ifdef PROX_DEBUG + fprintf(stderr, "SocksClient: Connected\n"); +#endif + + d->step = StepVersion; + writeData(spc_set_version()); +} + +void SocksClient::sock_connectionClosed() +{ + if(d->active) { + reset(); + connectionClosed(); + } + else { + error(ErrProxyNeg); + } +} + +void SocksClient::sock_delayedCloseFinished() +{ + if(d->active) { + reset(); + delayedCloseFinished(); + } +} + +void SocksClient::sock_readyRead() +{ + QByteArray block = d->sock.read(); + + if(!d->active) { + if(d->incoming) + processIncoming(block); + else + processOutgoing(block); + } + else { + if(!d->udp) { + appendRead(block); + readyRead(); + } + } +} + +void SocksClient::processOutgoing(const QByteArray &block) +{ +#ifdef PROX_DEBUG + // show hex + fprintf(stderr, "SocksClient: client recv { "); + for(int n = 0; n < (int)block.size(); ++n) + fprintf(stderr, "%02X ", (unsigned char)block[n]); + fprintf(stderr, " } \n"); +#endif + ByteStream::appendArray(&d->recvBuf, block); + + if(d->step == StepVersion) { + SPSS_VERSION s; + int r = sps_get_version(&d->recvBuf, &s); + if(r == -1) { + reset(true); + error(ErrProxyNeg); + return; + } + else if(r == 1) { + if(s.version != 0x05 || s.method == 0xff) { +#ifdef PROX_DEBUG + fprintf(stderr, "SocksClient: Method selection failed\n"); +#endif + reset(true); + error(ErrProxyNeg); + return; + } + + QString str; + if(s.method == 0x00) { + str = "None"; + d->authMethod = AuthNone; + } + else if(s.method == 0x02) { + str = "Username/Password"; + d->authMethod = AuthUsername; + } + else { +#ifdef PROX_DEBUG + fprintf(stderr, "SocksClient: Server wants to use unknown method '%02x'\n", s.method); +#endif + reset(true); + error(ErrProxyNeg); + return; + } + + if(d->authMethod == AuthNone) { + // no auth, go straight to the request + do_request(); + } + else if(d->authMethod == AuthUsername) { + d->step = StepAuth; +#ifdef PROX_DEBUG + fprintf(stderr, "SocksClient: Authenticating [Username] ...\n"); +#endif + writeData(spc_set_authUsername(d->user.latin1(), d->pass.latin1())); + } + } + } + if(d->step == StepAuth) { + if(d->authMethod == AuthUsername) { + SPSS_AUTHUSERNAME s; + int r = sps_get_authUsername(&d->recvBuf, &s); + if(r == -1) { + reset(true); + error(ErrProxyNeg); + return; + } + else if(r == 1) { + if(s.version != 0x01) { + reset(true); + error(ErrProxyNeg); + return; + } + if(!s.success) { + reset(true); + error(ErrProxyAuth); + return; + } + + do_request(); + } + } + } + else if(d->step == StepRequest) { + SPS_CONNREQ s; + int r = sp_get_request(&d->recvBuf, &s); + if(r == -1) { + reset(true); + error(ErrProxyNeg); + return; + } + else if(r == 1) { + if(s.cmd != RET_SUCCESS) { +#ifdef PROX_DEBUG + fprintf(stderr, "SocksClient: client << Error >> [%02x]\n", s.cmd); +#endif + reset(true); + if(s.cmd == RET_UNREACHABLE) + error(ErrHostNotFound); + else if(s.cmd == RET_CONNREFUSED) + error(ErrConnectionRefused); + else + error(ErrProxyNeg); + return; + } + +#ifdef PROX_DEBUG + fprintf(stderr, "SocksClient: client << Success >>\n"); +#endif + if(d->udp) { + if(s.address_type == 0x03) + d->udpAddr = s.host; + else + d->udpAddr = s.addr.toString(); + d->udpPort = s.port; + } + + d->active = true; + + QGuardedPtr self = this; + connected(); + if(!self) + return; + + if(!d->recvBuf.isEmpty()) { + appendRead(d->recvBuf); + d->recvBuf.resize(0); + readyRead(); + } + } + } +} + +void SocksClient::do_request() +{ +#ifdef PROX_DEBUG + fprintf(stderr, "SocksClient: Requesting ...\n"); +#endif + d->step = StepRequest; + int cmd = d->udp ? REQ_UDPASSOCIATE : REQ_CONNECT; + QByteArray buf; + if(!d->real_host.isEmpty()) + buf = sp_set_request(d->real_host, d->real_port, cmd); + else + buf = sp_set_request(QHostAddress(), 0, cmd); + writeData(buf); +} + +void SocksClient::sock_bytesWritten(int x) +{ + int bytes = x; + if(d->pending >= bytes) { + d->pending -= bytes; + bytes = 0; + } + else { + bytes -= d->pending; + d->pending = 0; + } + if(bytes > 0) + bytesWritten(bytes); +} + +void SocksClient::sock_error(int x) +{ + if(d->active) { + reset(); + error(ErrRead); + } + else { + reset(true); + if(x == BSocket::ErrHostNotFound) + error(ErrProxyConnect); + else if(x == BSocket::ErrConnectionRefused) + error(ErrProxyConnect); + else if(x == BSocket::ErrRead) + error(ErrProxyNeg); + } +} + +void SocksClient::serve() +{ + d->waiting = false; + d->step = StepVersion; + continueIncoming(); +} + +void SocksClient::processIncoming(const QByteArray &block) +{ +#ifdef PROX_DEBUG + // show hex + fprintf(stderr, "SocksClient: server recv { "); + for(int n = 0; n < (int)block.size(); ++n) + fprintf(stderr, "%02X ", (unsigned char)block[n]); + fprintf(stderr, " } \n"); +#endif + ByteStream::appendArray(&d->recvBuf, block); + + if(!d->waiting) + continueIncoming(); +} + +void SocksClient::continueIncoming() +{ + if(d->recvBuf.isEmpty()) + return; + + if(d->step == StepVersion) { + SPCS_VERSION s; + int r = spc_get_version(&d->recvBuf, &s); + if(r == -1) { + reset(true); + error(ErrProxyNeg); + return; + } + else if(r == 1) { + if(s.version != 0x05) { + reset(true); + error(ErrProxyNeg); + return; + } + + int methods = 0; + for(int n = 0; n < (int)s.methodList.size(); ++n) { + unsigned char c = s.methodList[n]; + if(c == 0x00) + methods |= AuthNone; + else if(c == 0x02) + methods |= AuthUsername; + } + d->waiting = true; + incomingMethods(methods); + } + } + else if(d->step == StepAuth) { + SPCS_AUTHUSERNAME s; + int r = spc_get_authUsername(&d->recvBuf, &s); + if(r == -1) { + reset(true); + error(ErrProxyNeg); + return; + } + else if(r == 1) { + d->waiting = true; + incomingAuth(s.user, s.pass); + } + } + else if(d->step == StepRequest) { + SPS_CONNREQ s; + int r = sp_get_request(&d->recvBuf, &s); + if(r == -1) { + reset(true); + error(ErrProxyNeg); + return; + } + else if(r == 1) { + d->waiting = true; + if(s.cmd == REQ_CONNECT) { + if(!s.host.isEmpty()) + d->rhost = s.host; + else + d->rhost = s.addr.toString(); + d->rport = s.port; + incomingConnectRequest(d->rhost, d->rport); + } + else if(s.cmd == REQ_UDPASSOCIATE) { + incomingUDPAssociateRequest(); + } + else { + requestDeny(); + return; + } + } + } +} + +void SocksClient::chooseMethod(int method) +{ + if(d->step != StepVersion || !d->waiting) + return; + + unsigned char c; + if(method == AuthNone) { + d->step = StepRequest; + c = 0x00; + } + else { + d->step = StepAuth; + c = 0x02; + } + + // version response + d->waiting = false; + writeData(sps_set_version(c)); + continueIncoming(); +} + +void SocksClient::authGrant(bool b) +{ + if(d->step != StepAuth || !d->waiting) + return; + + if(b) + d->step = StepRequest; + + // auth response + d->waiting = false; + writeData(sps_set_authUsername(b)); + if(!b) { + reset(true); + return; + } + continueIncoming(); +} + +void SocksClient::requestDeny() +{ + if(d->step != StepRequest || !d->waiting) + return; + + // response + d->waiting = false; + writeData(sp_set_request(d->rhost, d->rport, RET_UNREACHABLE)); + reset(true); +} + +void SocksClient::grantConnect() +{ + if(d->step != StepRequest || !d->waiting) + return; + + // response + d->waiting = false; + writeData(sp_set_request(d->rhost, d->rport, RET_SUCCESS)); + d->active = true; +#ifdef PROX_DEBUG + fprintf(stderr, "SocksClient: server << Success >>\n"); +#endif + + if(!d->recvBuf.isEmpty()) { + appendRead(d->recvBuf); + d->recvBuf.resize(0); + readyRead(); + } +} + +void SocksClient::grantUDPAssociate(const QString &relayHost, int relayPort) +{ + if(d->step != StepRequest || !d->waiting) + return; + + // response + d->waiting = false; + writeData(sp_set_request(relayHost, relayPort, RET_SUCCESS)); + d->udp = true; + d->active = true; +#ifdef PROX_DEBUG + fprintf(stderr, "SocksClient: server << Success >>\n"); +#endif + + if(!d->recvBuf.isEmpty()) + d->recvBuf.resize(0); +} + +QHostAddress SocksClient::peerAddress() const +{ + return d->sock.peerAddress(); +} + +Q_UINT16 SocksClient::peerPort() const +{ + return d->sock.peerPort(); +} + +QString SocksClient::udpAddress() const +{ + return d->udpAddr; +} + +Q_UINT16 SocksClient::udpPort() const +{ + return d->udpPort; +} + +SocksUDP *SocksClient::createUDP(const QString &host, int port, const QHostAddress &routeAddr, int routePort) +{ + return new SocksUDP(this, host, port, routeAddr, routePort); +} + +//---------------------------------------------------------------------------- +// SocksServer +//---------------------------------------------------------------------------- +class SocksServer::Private +{ +public: + Private() {} + + ServSock serv; + QPtrList incomingConns; + QSocketDevice *sd; + QSocketNotifier *sn; +}; + +SocksServer::SocksServer(QObject *parent) +:QObject(parent) +{ + d = new Private; + d->sd = 0; + d->sn = 0; + connect(&d->serv, SIGNAL(connectionReady(int)), SLOT(connectionReady(int))); +} + +SocksServer::~SocksServer() +{ + stop(); + d->incomingConns.setAutoDelete(true); + d->incomingConns.clear(); + delete d; +} + +bool SocksServer::isActive() const +{ + return d->serv.isActive(); +} + +bool SocksServer::listen(Q_UINT16 port, bool udp) +{ + stop(); + if(!d->serv.listen(port)) + return false; + if(udp) { + d->sd = new QSocketDevice(QSocketDevice::Datagram); + d->sd->setBlocking(false); + if(!d->sd->bind(QHostAddress(), port)) { + delete d->sd; + d->sd = 0; + d->serv.stop(); + return false; + } + d->sn = new QSocketNotifier(d->sd->socket(), QSocketNotifier::Read); + connect(d->sn, SIGNAL(activated(int)), SLOT(sn_activated(int))); + } + return true; +} + +void SocksServer::stop() +{ + delete d->sn; + d->sn = 0; + delete d->sd; + d->sd = 0; + d->serv.stop(); +} + +int SocksServer::port() const +{ + return d->serv.port(); +} + +QHostAddress SocksServer::address() const +{ + return d->serv.address(); +} + +SocksClient *SocksServer::takeIncoming() +{ + if(d->incomingConns.isEmpty()) + return 0; + + SocksClient *c = d->incomingConns.getFirst(); + d->incomingConns.removeRef(c); + + // we don't care about errors anymore + disconnect(c, SIGNAL(error(int)), this, SLOT(connectionError())); + + // don't serve the connection until the event loop, to give the caller a chance to map signals + QTimer::singleShot(0, c, SLOT(serve())); + + return c; +} + +void SocksServer::writeUDP(const QHostAddress &addr, int port, const QByteArray &data) +{ + if(d->sd) { + d->sd->setBlocking(true); + d->sd->writeBlock(data.data(), data.size(), addr, port); + d->sd->setBlocking(false); + } +} + +void SocksServer::connectionReady(int s) +{ + SocksClient *c = new SocksClient(s, this); + connect(c, SIGNAL(error(int)), this, SLOT(connectionError())); + d->incomingConns.append(c); + incomingReady(); +} + +void SocksServer::connectionError() +{ + SocksClient *c = (SocksClient *)sender(); + d->incomingConns.removeRef(c); + c->deleteLater(); +} + +void SocksServer::sn_activated(int) +{ + QByteArray buf(8192); + int actual = d->sd->readBlock(buf.data(), buf.size()); + buf.resize(actual); + QHostAddress pa = d->sd->peerAddress(); + int pp = d->sd->peerPort(); + SPS_UDP s; + int r = sp_read_udp(&buf, &s); + if(r != 1) + return; + incomingUDP(s.host, s.port, pa, pp, s.data); +} + +// CS_NAMESPACE_END + +#include "socks.moc" diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/socks.h b/kopete/protocols/jabber/libiris/cutestuff/network/socks.h new file mode 100644 index 00000000..8f1e4ddc --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/socks.h @@ -0,0 +1,160 @@ +/* + * socks.h - SOCKS5 TCP proxy client/server + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_SOCKS_H +#define CS_SOCKS_H + +#include"bytestream.h" + +// CS_NAMESPACE_BEGIN + +class QHostAddress; +class SocksClient; +class SocksServer; + +class SocksUDP : public QObject +{ + Q_OBJECT +public: + ~SocksUDP(); + + void change(const QString &host, int port); + void write(const QByteArray &data); + +signals: + void packetReady(const QByteArray &data); + +private slots: + void sn_activated(int); + +private: + class Private; + Private *d; + + friend class SocksClient; + SocksUDP(SocksClient *sc, const QString &host, int port, const QHostAddress &routeAddr, int routePort); +}; + +class SocksClient : public ByteStream +{ + Q_OBJECT +public: + enum Error { ErrConnectionRefused = ErrCustom, ErrHostNotFound, ErrProxyConnect, ErrProxyNeg, ErrProxyAuth }; + enum Method { AuthNone=0x0001, AuthUsername=0x0002 }; + enum Request { ReqConnect, ReqUDPAssociate }; + SocksClient(QObject *parent=0); + SocksClient(int, QObject *parent=0); + ~SocksClient(); + + bool isIncoming() const; + + // outgoing + void setAuth(const QString &user, const QString &pass=""); + void connectToHost(const QString &proxyHost, int proxyPort, const QString &host, int port, bool udpMode=false); + + // incoming + void chooseMethod(int); + void authGrant(bool); + void requestDeny(); + void grantConnect(); + void grantUDPAssociate(const QString &relayHost, int relayPort); + + // from ByteStream + bool isOpen() const; + void close(); + void write(const QByteArray &); + QByteArray read(int bytes=0); + int bytesAvailable() const; + int bytesToWrite() const; + + // remote address + QHostAddress peerAddress() const; + Q_UINT16 peerPort() const; + + // udp + QString udpAddress() const; + Q_UINT16 udpPort() const; + SocksUDP *createUDP(const QString &host, int port, const QHostAddress &routeAddr, int routePort); + +signals: + // outgoing + void connected(); + + // incoming + void incomingMethods(int); + void incomingAuth(const QString &user, const QString &pass); + void incomingConnectRequest(const QString &host, int port); + void incomingUDPAssociateRequest(); + +private slots: + void sock_connected(); + void sock_connectionClosed(); + void sock_delayedCloseFinished(); + void sock_readyRead(); + void sock_bytesWritten(int); + void sock_error(int); + void serve(); + +private: + class Private; + Private *d; + + void init(); + void reset(bool clear=false); + void do_request(); + void processOutgoing(const QByteArray &); + void processIncoming(const QByteArray &); + void continueIncoming(); + void writeData(const QByteArray &a); +}; + +class SocksServer : public QObject +{ + Q_OBJECT +public: + SocksServer(QObject *parent=0); + ~SocksServer(); + + bool isActive() const; + bool listen(Q_UINT16 port, bool udp=false); + void stop(); + int port() const; + QHostAddress address() const; + SocksClient *takeIncoming(); + + void writeUDP(const QHostAddress &addr, int port, const QByteArray &data); + +signals: + void incomingReady(); + void incomingUDP(const QString &host, int port, const QHostAddress &addr, int sourcePort, const QByteArray &data); + +private slots: + void connectionReady(int); + void connectionError(); + void sn_activated(int); + +private: + class Private; + Private *d; +}; + +// CS_NAMESPACE_END + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/srvresolver.cpp b/kopete/protocols/jabber/libiris/cutestuff/network/srvresolver.cpp new file mode 100644 index 00000000..0c454c49 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/srvresolver.cpp @@ -0,0 +1,320 @@ +/* + * srvresolver.cpp - class to simplify SRV lookups + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"srvresolver.h" + +#include +#include +#include +#include"safedelete.h" + +#ifndef NO_NDNS +#include"ndns.h" +#endif + +// CS_NAMESPACE_BEGIN + +static void sortSRVList(QValueList &list) +{ + QValueList tmp = list; + list.clear(); + + while(!tmp.isEmpty()) { + QValueList::Iterator p = tmp.end(); + for(QValueList::Iterator it = tmp.begin(); it != tmp.end(); ++it) { + if(p == tmp.end()) + p = it; + else { + int a = (*it).priority; + int b = (*p).priority; + int j = (*it).weight; + int k = (*p).weight; + if(a < b || (a == b && j < k)) + p = it; + } + } + list.append(*p); + tmp.remove(p); + } +} + +class SrvResolver::Private +{ +public: + Private() {} + + QDns *qdns; +#ifndef NO_NDNS + NDns ndns; +#endif + + bool failed; + QHostAddress resultAddress; + Q_UINT16 resultPort; + + bool srvonly; + QString srv; + QValueList servers; + bool aaaa; + + QTimer t; + SafeDelete sd; +}; + +SrvResolver::SrvResolver(QObject *parent) +:QObject(parent) +{ + d = new Private; + d->qdns = 0; + +#ifndef NO_NDNS + connect(&d->ndns, SIGNAL(resultsReady()), SLOT(ndns_done())); +#endif + connect(&d->t, SIGNAL(timeout()), SLOT(t_timeout())); + stop(); +} + +SrvResolver::~SrvResolver() +{ + stop(); + delete d; +} + +void SrvResolver::resolve(const QString &server, const QString &type, const QString &proto) +{ + stop(); + + d->failed = false; + d->srvonly = false; + d->srv = QString("_") + type + "._" + proto + '.' + server; + d->t.start(15000, true); + d->qdns = new QDns; + connect(d->qdns, SIGNAL(resultsReady()), SLOT(qdns_done())); + d->qdns->setRecordType(QDns::Srv); + d->qdns->setLabel(d->srv); +} + +void SrvResolver::resolveSrvOnly(const QString &server, const QString &type, const QString &proto) +{ + stop(); + + d->failed = false; + d->srvonly = true; + d->srv = QString("_") + type + "._" + proto + '.' + server; + d->t.start(15000, true); + d->qdns = new QDns; + connect(d->qdns, SIGNAL(resultsReady()), SLOT(qdns_done())); + d->qdns->setRecordType(QDns::Srv); + d->qdns->setLabel(d->srv); +} + +void SrvResolver::next() +{ + if(d->servers.isEmpty()) + return; + + tryNext(); +} + +void SrvResolver::stop() +{ + if(d->t.isActive()) + d->t.stop(); + if(d->qdns) { + d->qdns->disconnect(this); + d->sd.deleteLater(d->qdns); + d->qdns = 0; + } +#ifndef NO_NDNS + if(d->ndns.isBusy()) + d->ndns.stop(); +#endif + d->resultAddress = QHostAddress(); + d->resultPort = 0; + d->servers.clear(); + d->srv = ""; + d->failed = true; +} + +bool SrvResolver::isBusy() const +{ +#ifndef NO_NDNS + if(d->qdns || d->ndns.isBusy()) +#else + if(d->qdns) +#endif + return true; + else + return false; +} + +QValueList SrvResolver::servers() const +{ + return d->servers; +} + +bool SrvResolver::failed() const +{ + return d->failed; +} + +QHostAddress SrvResolver::resultAddress() const +{ + return d->resultAddress; +} + +Q_UINT16 SrvResolver::resultPort() const +{ + return d->resultPort; +} + +void SrvResolver::tryNext() +{ +#ifndef NO_NDNS + d->ndns.resolve(d->servers.first().name); +#else + d->qdns = new QDns; + connect(d->qdns, SIGNAL(resultsReady()), SLOT(ndns_done())); + if(d->aaaa) + d->qdns->setRecordType(QDns::Aaaa); // IPv6 + else + d->qdns->setRecordType(QDns::A); // IPv4 + d->qdns->setLabel(d->servers.first().name); +#endif +} + +void SrvResolver::qdns_done() +{ + if(!d->qdns) + return; + + // apparently we sometimes get this signal even though the results aren't ready + if(d->qdns->isWorking()) + return; + d->t.stop(); + + SafeDeleteLock s(&d->sd); + + // grab the server list and destroy the qdns object + QValueList list; + if(d->qdns->recordType() == QDns::Srv) + list = d->qdns->servers(); + d->qdns->disconnect(this); + d->sd.deleteLater(d->qdns); + d->qdns = 0; + + if(list.isEmpty()) { + stop(); + resultsReady(); + return; + } + sortSRVList(list); + d->servers = list; + + if(d->srvonly) + resultsReady(); + else { + // kick it off + d->aaaa = true; + tryNext(); + } +} + +void SrvResolver::ndns_done() +{ +#ifndef NO_NDNS + SafeDeleteLock s(&d->sd); + + uint r = d->ndns.result(); + int port = d->servers.first().port; + d->servers.remove(d->servers.begin()); + + if(r) { + d->resultAddress = QHostAddress(d->ndns.result()); + d->resultPort = port; + resultsReady(); + } + else { + // failed? bail if last one + if(d->servers.isEmpty()) { + stop(); + resultsReady(); + return; + } + + // otherwise try the next + tryNext(); + } +#else + if(!d->qdns) + return; + + // apparently we sometimes get this signal even though the results aren't ready + if(d->qdns->isWorking()) + return; + + SafeDeleteLock s(&d->sd); + + // grab the address list and destroy the qdns object + QValueList list; + if(d->qdns->recordType() == QDns::A || d->qdns->recordType() == QDns::Aaaa) + list = d->qdns->addresses(); + d->qdns->disconnect(this); + d->sd.deleteLater(d->qdns); + d->qdns = 0; + + if(!list.isEmpty()) { + int port = d->servers.first().port; + d->servers.remove(d->servers.begin()); + d->aaaa = true; + + d->resultAddress = list.first(); + d->resultPort = port; + resultsReady(); + } + else { + if(!d->aaaa) + d->servers.remove(d->servers.begin()); + d->aaaa = !d->aaaa; + + // failed? bail if last one + if(d->servers.isEmpty()) { + stop(); + resultsReady(); + return; + } + + // otherwise try the next + tryNext(); + } +#endif +} + +void SrvResolver::t_timeout() +{ + SafeDeleteLock s(&d->sd); + + stop(); + resultsReady(); +} + +// CS_NAMESPACE_END + +#include "srvresolver.moc" diff --git a/kopete/protocols/jabber/libiris/cutestuff/network/srvresolver.h b/kopete/protocols/jabber/libiris/cutestuff/network/srvresolver.h new file mode 100644 index 00000000..6c9ac4f3 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/network/srvresolver.h @@ -0,0 +1,65 @@ +/* + * srvresolver.h - class to simplify SRV lookups + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_SRVRESOLVER_H +#define CS_SRVRESOLVER_H + +#include +#include + +// CS_NAMESPACE_BEGIN + +class SrvResolver : public QObject +{ + Q_OBJECT +public: + SrvResolver(QObject *parent=0); + ~SrvResolver(); + + void resolve(const QString &server, const QString &type, const QString &proto); + void resolveSrvOnly(const QString &server, const QString &type, const QString &proto); + void next(); + void stop(); + bool isBusy() const; + + QValueList servers() const; + + bool failed() const; + QHostAddress resultAddress() const; + Q_UINT16 resultPort() const; + +signals: + void resultsReady(); + +private slots: + void qdns_done(); + void ndns_done(); + void t_timeout(); + +private: + class Private; + Private *d; + + void tryNext(); +}; + +// CS_NAMESPACE_END + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/Makefile.am b/kopete/protocols/jabber/libiris/cutestuff/util/Makefile.am new file mode 100644 index 00000000..649c0fcf --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/Makefile.am @@ -0,0 +1,12 @@ +METASOURCES = AUTO + +noinst_LTLIBRARIES = libcutestuff_util.la +INCLUDES = $(all_includes) + +libcutestuff_util_la_SOURCES = \ + base64.cpp \ + bytestream.cpp \ + qrandom.cpp \ + safedelete.cpp \ + sha1.cpp \ + showtextdlg.cpp diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/TODO b/kopete/protocols/jabber/libiris/cutestuff/util/TODO new file mode 100644 index 00000000..42d94b7d --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/TODO @@ -0,0 +1,7 @@ +varlist +common (opening urls) +zip +showtext +format parsing +xml handling, elem2string, etc + diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/base64.cpp b/kopete/protocols/jabber/libiris/cutestuff/util/base64.cpp new file mode 100644 index 00000000..a17ac335 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/base64.cpp @@ -0,0 +1,182 @@ +/* + * base64.cpp - Base64 converting functions + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"base64.h" + +// CS_NAMESPACE_BEGIN + +//! \class Base64 base64.h +//! \brief Base64 conversion functions. +//! +//! Converts Base64 data between arrays and strings. +//! +//! \code +//! #include "base64.h" +//! +//! ... +//! +//! // encode a block of data into base64 +//! QByteArray block(1024); +//! QByteArray enc = Base64::encode(block); +//! +//! \endcode + +//! +//! Encodes array \a s and returns the result. +QByteArray Base64::encode(const QByteArray &s) +{ + int i; + int len = s.size(); + char tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + int a, b, c; + + QByteArray p((len+2)/3*4); + int at = 0; + for( i = 0; i < len; i += 3 ) { + a = ((unsigned char)s[i] & 3) << 4; + if(i + 1 < len) { + a += (unsigned char)s[i + 1] >> 4; + b = ((unsigned char)s[i + 1] & 0xF) << 2; + if(i + 2 < len) { + b += (unsigned char)s[i + 2] >> 6; + c = (unsigned char)s[i + 2] & 0x3F; + } + else + c = 64; + } + else + b = c = 64; + + p[at++] = tbl[(unsigned char)s[i] >> 2]; + p[at++] = tbl[a]; + p[at++] = tbl[b]; + p[at++] = tbl[c]; + } + return p; +} + +//! +//! Decodes array \a s and returns the result. +QByteArray Base64::decode(const QByteArray &s) +{ + // return value + QByteArray p; + + // -1 specifies invalid + // 64 specifies eof + // everything else specifies data + + char tbl[] = { + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, + 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,64,-1,-1, + -1, 0, 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,-1,-1,-1,-1,-1, + -1,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,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + }; + + // this should be a multiple of 4 + int len = s.size(); + + if(len % 4) + return p; + + p.resize(len / 4 * 3); + + int i; + int at = 0; + + int a, b, c, d; + c = d = 0; + + for( i = 0; i < len; i += 4 ) { + a = tbl[(int)s[i]]; + b = tbl[(int)s[i + 1]]; + c = tbl[(int)s[i + 2]]; + d = tbl[(int)s[i + 3]]; + if((a == 64 || b == 64) || (a < 0 || b < 0 || c < 0 || d < 0)) { + p.resize(0); + return p; + } + p[at++] = ((a & 0x3F) << 2) | ((b >> 4) & 0x03); + p[at++] = ((b & 0x0F) << 4) | ((c >> 2) & 0x0F); + p[at++] = ((c & 0x03) << 6) | ((d >> 0) & 0x3F); + } + + if(c & 64) + p.resize(at - 2); + else if(d & 64) + p.resize(at - 1); + + return p; +} + +//! +//! Encodes array \a a and returns the result as a string. +QString Base64::arrayToString(const QByteArray &a) +{ + QByteArray b = encode(a); + QCString c; + c.resize(b.size()+1); + memcpy(c.data(), b.data(), b.size()); + return QString::fromLatin1(c); +} + +//! +//! Decodes string \a s and returns the result as an array. +QByteArray Base64::stringToArray(const QString &s) +{ + if(s.isEmpty()) + return QByteArray(); + + // Unfold data + QString us(s); + us.remove('\n'); + + const char *c = us.latin1(); + int len = strlen(c); + QByteArray b(len); + memcpy(b.data(), c, len); + QByteArray a = decode(b); + return a; +} + +//! +//! Encodes string \a s and returns the result as a string. +QString Base64::encodeString(const QString &s) +{ + QCString c = s.utf8(); + int len = c.length(); + QByteArray b(len); + memcpy(b.data(), c.data(), len); + return arrayToString(b); +} + +// CS_NAMESPACE_END diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/base64.h b/kopete/protocols/jabber/libiris/cutestuff/util/base64.h new file mode 100644 index 00000000..128472c1 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/base64.h @@ -0,0 +1,40 @@ +/* + * base64.h - Base64 converting functions + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_BASE64_H +#define CS_BASE64_H + +#include + +// CS_NAMESPACE_BEGIN + +class Base64 +{ +public: + static QByteArray encode(const QByteArray &); + static QByteArray decode(const QByteArray &); + static QString arrayToString(const QByteArray &); + static QByteArray stringToArray(const QString &); + static QString encodeString(const QString &); +}; + +// CS_NAMESPACE_END + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/bytestream.cpp b/kopete/protocols/jabber/libiris/cutestuff/util/bytestream.cpp new file mode 100644 index 00000000..1eccb284 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/bytestream.cpp @@ -0,0 +1,268 @@ +/* + * bytestream.cpp - base class for bytestreams + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"bytestream.h" + +// CS_NAMESPACE_BEGIN + +//! \class ByteStream bytestream.h +//! \brief Base class for "bytestreams" +//! +//! This class provides a basic framework for a "bytestream", here defined +//! as a bi-directional, asynchronous pipe of data. It can be used to create +//! several different kinds of bytestream-applications, such as a console or +//! TCP connection, or something more abstract like a security layer or tunnel, +//! all with the same interface. The provided functions make creating such +//! classes simpler. ByteStream is a pure-virtual class, so you do not use it +//! on its own, but instead through a subclass such as \a BSocket. +//! +//! The signals connectionClosed(), delayedCloseFinished(), readyRead(), +//! bytesWritten(), and error() serve the exact same function as those from +//! QSocket. +//! +//! The simplest way to create a ByteStream is to reimplement isOpen(), close(), +//! and tryWrite(). Call appendRead() whenever you want to make data available for +//! reading. ByteStream will take care of the buffers with regards to the caller, +//! and will call tryWrite() when the write buffer gains data. It will be your +//! job to call tryWrite() whenever it is acceptable to write more data to +//! the underlying system. +//! +//! If you need more advanced control, reimplement read(), write(), bytesAvailable(), +//! and/or bytesToWrite() as necessary. +//! +//! Use appendRead(), appendWrite(), takeRead(), and takeWrite() to modify the +//! buffers. If you have more advanced requirements, the buffers can be accessed +//! directly with readBuf() and writeBuf(). +//! +//! Also available are the static convenience functions ByteStream::appendArray() +//! and ByteStream::takeArray(), which make dealing with byte queues very easy. + +class ByteStream::Private +{ +public: + Private() {} + + QByteArray readBuf, writeBuf; +}; + +//! +//! Constructs a ByteStream object with parent \a parent. +ByteStream::ByteStream(QObject *parent) +:QObject(parent) +{ + d = new Private; +} + +//! +//! Destroys the object and frees allocated resources. +ByteStream::~ByteStream() +{ + delete d; +} + +//! +//! Returns TRUE if the stream is open, meaning that you can write to it. +bool ByteStream::isOpen() const +{ + return false; +} + +//! +//! Closes the stream. If there is data in the write buffer then it will be +//! written before actually closing the stream. Once all data has been written, +//! the delayedCloseFinished() signal will be emitted. +//! \sa delayedCloseFinished() +void ByteStream::close() +{ +} + +//! +//! Writes array \a a to the stream. +void ByteStream::write(const QByteArray &a) +{ + if(!isOpen()) + return; + + bool doWrite = bytesToWrite() == 0 ? true: false; + appendWrite(a); + if(doWrite) + tryWrite(); +} + +//! +//! Reads bytes \a bytes of data from the stream and returns them as an array. If \a bytes is 0, then +//! \a read will return all available data. +QByteArray ByteStream::read(int bytes) +{ + return takeRead(bytes); +} + +//! +//! Returns the number of bytes available for reading. +int ByteStream::bytesAvailable() const +{ + return d->readBuf.size(); +} + +//! +//! Returns the number of bytes that are waiting to be written. +int ByteStream::bytesToWrite() const +{ + return d->writeBuf.size(); +} + +//! +//! Writes string \a cs to the stream. +void ByteStream::write(const QCString &cs) +{ + QByteArray block(cs.length()); + memcpy(block.data(), cs.data(), block.size()); + write(block); +} + +//! +//! Clears the read buffer. +void ByteStream::clearReadBuffer() +{ + d->readBuf.resize(0); +} + +//! +//! Clears the write buffer. +void ByteStream::clearWriteBuffer() +{ + d->writeBuf.resize(0); +} + +//! +//! Appends \a block to the end of the read buffer. +void ByteStream::appendRead(const QByteArray &block) +{ + appendArray(&d->readBuf, block); +} + +//! +//! Appends \a block to the end of the write buffer. +void ByteStream::appendWrite(const QByteArray &block) +{ + appendArray(&d->writeBuf, block); +} + +//! +//! Returns \a size bytes from the start of the read buffer. +//! If \a size is 0, then all available data will be returned. +//! If \a del is TRUE, then the bytes are also removed. +QByteArray ByteStream::takeRead(int size, bool del) +{ + return takeArray(&d->readBuf, size, del); +} + +//! +//! Returns \a size bytes from the start of the write buffer. +//! If \a size is 0, then all available data will be returned. +//! If \a del is TRUE, then the bytes are also removed. +QByteArray ByteStream::takeWrite(int size, bool del) +{ + return takeArray(&d->writeBuf, size, del); +} + +//! +//! Returns a reference to the read buffer. +QByteArray & ByteStream::readBuf() +{ + return d->readBuf; +} + +//! +//! Returns a reference to the write buffer. +QByteArray & ByteStream::writeBuf() +{ + return d->writeBuf; +} + +//! +//! Attempts to try and write some bytes from the write buffer, and returns the number +//! successfully written or -1 on error. The default implementation returns -1. +int ByteStream::tryWrite() +{ + return -1; +} + +//! +//! Append array \a b to the end of the array pointed to by \a a. +void ByteStream::appendArray(QByteArray *a, const QByteArray &b) +{ + int oldsize = a->size(); + a->resize(oldsize + b.size()); + memcpy(a->data() + oldsize, b.data(), b.size()); +} + +//! +//! Returns \a size bytes from the start of the array pointed to by \a from. +//! If \a size is 0, then all available data will be returned. +//! If \a del is TRUE, then the bytes are also removed. +QByteArray ByteStream::takeArray(QByteArray *from, int size, bool del) +{ + QByteArray a; + if(size == 0) { + a = from->copy(); + if(del) + from->resize(0); + } + else { + if(size > (int)from->size()) + size = from->size(); + a.resize(size); + char *r = from->data(); + memcpy(a.data(), r, size); + if(del) { + int newsize = from->size()-size; + memmove(r, r+size, newsize); + from->resize(newsize); + } + } + return a; +} + void connectionClosed(); + void delayedCloseFinished(); + void readyRead(); + void bytesWritten(int); + void error(int); + +//! \fn void ByteStream::connectionClosed() +//! This signal is emitted when the remote end of the stream closes. + +//! \fn void ByteStream::delayedCloseFinished() +//! This signal is emitted when all pending data has been written to the stream +//! after an attempt to close. + +//! \fn void ByteStream::readyRead() +//! This signal is emitted when data is available to be read. + +//! \fn void ByteStream::bytesWritten(int x); +//! This signal is emitted when data has been successfully written to the stream. +//! \a x is the number of bytes written. + +//! \fn void ByteStream::error(int code) +//! This signal is emitted when an error occurs in the stream. The reason for +//! error is indicated by \a code. + +// CS_NAMESPACE_END +#include "bytestream.moc" diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/bytestream.h b/kopete/protocols/jabber/libiris/cutestuff/util/bytestream.h new file mode 100644 index 00000000..c33b3976 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/bytestream.h @@ -0,0 +1,78 @@ +/* + * bytestream.h - base class for bytestreams + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_BYTESTREAM_H +#define CS_BYTESTREAM_H + +#include +#include + +// CS_NAMESPACE_BEGIN + +// CS_EXPORT_BEGIN +class ByteStream : public QObject +{ + Q_OBJECT +public: + enum Error { ErrRead, ErrWrite, ErrCustom = 10 }; + ByteStream(QObject *parent=0); + virtual ~ByteStream()=0; + + virtual bool isOpen() const; + virtual void close(); + virtual void write(const QByteArray &); + virtual QByteArray read(int bytes=0); + virtual int bytesAvailable() const; + virtual int bytesToWrite() const; + + void write(const QCString &); + + static void appendArray(QByteArray *a, const QByteArray &b); + static QByteArray takeArray(QByteArray *from, int size=0, bool del=true); + +signals: + void connectionClosed(); + void delayedCloseFinished(); + void readyRead(); + void bytesWritten(int); + void error(int); + +protected: + void clearReadBuffer(); + void clearWriteBuffer(); + void appendRead(const QByteArray &); + void appendWrite(const QByteArray &); + QByteArray takeRead(int size=0, bool del=true); + QByteArray takeWrite(int size=0, bool del=true); + QByteArray & readBuf(); + QByteArray & writeBuf(); + virtual int tryWrite(); + +private: +//! \if _hide_doc_ + class Private; + Private *d; +//! \endif +}; +// CS_EXPORT_END + +// CS_NAMESPACE_END + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/cipher.cpp b/kopete/protocols/jabber/libiris/cutestuff/util/cipher.cpp new file mode 100644 index 00000000..3e2f3a15 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/cipher.cpp @@ -0,0 +1,357 @@ +/* + * cipher.cpp - Simple wrapper to 3DES,AES128/256 CBC ciphers + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"cipher.h" + +#include +#include +#include"bytestream.h" +#include"qrandom.h" + +static bool lib_encryptArray(const EVP_CIPHER *type, const QByteArray &buf, const QByteArray &key, const QByteArray &iv, bool pad, QByteArray *out) +{ + QByteArray result(buf.size()+type->block_size); + int len; + EVP_CIPHER_CTX c; + + unsigned char *ivp = NULL; + if(!iv.isEmpty()) + ivp = (unsigned char *)iv.data(); + EVP_CIPHER_CTX_init(&c); + //EVP_CIPHER_CTX_set_padding(&c, pad ? 1: 0); + if(!EVP_EncryptInit_ex(&c, type, NULL, (unsigned char *)key.data(), ivp)) + return false; + if(!EVP_EncryptUpdate(&c, (unsigned char *)result.data(), &len, (unsigned char *)buf.data(), buf.size())) + return false; + result.resize(len); + if(pad) { + QByteArray last(type->block_size); + if(!EVP_EncryptFinal_ex(&c, (unsigned char *)last.data(), &len)) + return false; + last.resize(len); + ByteStream::appendArray(&result, last); + } + + memset(&c, 0, sizeof(EVP_CIPHER_CTX)); + *out = result; + return true; +} + +static bool lib_decryptArray(const EVP_CIPHER *type, const QByteArray &buf, const QByteArray &key, const QByteArray &iv, bool pad, QByteArray *out) +{ + QByteArray result(buf.size()+type->block_size); + int len; + EVP_CIPHER_CTX c; + + unsigned char *ivp = NULL; + if(!iv.isEmpty()) + ivp = (unsigned char *)iv.data(); + EVP_CIPHER_CTX_init(&c); + //EVP_CIPHER_CTX_set_padding(&c, pad ? 1: 0); + if(!EVP_DecryptInit_ex(&c, type, NULL, (unsigned char *)key.data(), ivp)) + return false; + if(!pad) { + if(!EVP_EncryptUpdate(&c, (unsigned char *)result.data(), &len, (unsigned char *)buf.data(), buf.size())) + return false; + } + else { + if(!EVP_DecryptUpdate(&c, (unsigned char *)result.data(), &len, (unsigned char *)buf.data(), buf.size())) + return false; + } + result.resize(len); + if(pad) { + QByteArray last(type->block_size); + if(!EVP_DecryptFinal_ex(&c, (unsigned char *)last.data(), &len)) + return false; + last.resize(len); + ByteStream::appendArray(&result, last); + } + + memset(&c, 0, sizeof(EVP_CIPHER_CTX)); + *out = result; + return true; +} + +static bool lib_generateKeyIV(const EVP_CIPHER *type, const QByteArray &data, const QByteArray &salt, QByteArray *key, QByteArray *iv) +{ + QByteArray k, i; + unsigned char *kp = 0; + unsigned char *ip = 0; + if(key) { + k.resize(type->key_len); + kp = (unsigned char *)k.data(); + } + if(iv) { + i.resize(type->iv_len); + ip = (unsigned char *)i.data(); + } + if(!EVP_BytesToKey(type, EVP_sha1(), (unsigned char *)salt.data(), (unsigned char *)data.data(), data.size(), 1, kp, ip)) + return false; + if(key) + *key = k; + if(iv) + *iv = i; + return true; +} + +static const EVP_CIPHER * typeToCIPHER(Cipher::Type t) +{ + if(t == Cipher::TripleDES) + return EVP_des_ede3_cbc(); + else if(t == Cipher::AES_128) + return EVP_aes_128_cbc(); + else if(t == Cipher::AES_256) + return EVP_aes_256_cbc(); + else + return 0; +} + +Cipher::Key Cipher::generateKey(Type t) +{ + Key k; + const EVP_CIPHER *type = typeToCIPHER(t); + if(!type) + return k; + QByteArray out; + if(!lib_generateKeyIV(type, QRandom::randomArray(128), QRandom::randomArray(2), &out, 0)) + return k; + k.setType(t); + k.setData(out); + return k; +} + +QByteArray Cipher::generateIV(Type t) +{ + const EVP_CIPHER *type = typeToCIPHER(t); + if(!type) + return QByteArray(); + QByteArray out; + if(!lib_generateKeyIV(type, QCString("Get this man an iv!"), QByteArray(), 0, &out)) + return QByteArray(); + return out; +} + +int Cipher::ivSize(Type t) +{ + const EVP_CIPHER *type = typeToCIPHER(t); + if(!type) + return -1; + return type->iv_len; +} + +QByteArray Cipher::encrypt(const QByteArray &buf, const Key &key, const QByteArray &iv, bool pad, bool *ok) +{ + if(ok) + *ok = false; + const EVP_CIPHER *type = typeToCIPHER(key.type()); + if(!type) + return QByteArray(); + QByteArray out; + if(!lib_encryptArray(type, buf, key.data(), iv, pad, &out)) + return QByteArray(); + + if(ok) + *ok = true; + return out; +} + +QByteArray Cipher::decrypt(const QByteArray &buf, const Key &key, const QByteArray &iv, bool pad, bool *ok) +{ + if(ok) + *ok = false; + const EVP_CIPHER *type = typeToCIPHER(key.type()); + if(!type) + return QByteArray(); + QByteArray out; + if(!lib_decryptArray(type, buf, key.data(), iv, pad, &out)) + return QByteArray(); + + if(ok) + *ok = true; + return out; +} + + +class RSAKey::Private +{ +public: + Private() {} + + RSA *rsa; + int ref; +}; + +RSAKey::RSAKey() +{ + d = 0; +} + +RSAKey::RSAKey(const RSAKey &from) +{ + d = 0; + *this = from; +} + +RSAKey & RSAKey::operator=(const RSAKey &from) +{ + free(); + + if(from.d) { + d = from.d; + ++d->ref; + } + + return *this; +} + +RSAKey::~RSAKey() +{ + free(); +} + +bool RSAKey::isNull() const +{ + return d ? false: true; +} + +void * RSAKey::data() const +{ + if(d) + return (void *)d->rsa; + else + return 0; +} + +void RSAKey::setData(void *p) +{ + free(); + + if(p) { + d = new Private; + d->ref = 1; + d->rsa = (RSA *)p; + } +} + +void RSAKey::free() +{ + if(!d) + return; + + --d->ref; + if(d->ref <= 0) { + RSA_free(d->rsa); + delete d; + } + d = 0; +} + +RSAKey generateRSAKey() +{ + RSA *rsa = RSA_generate_key(1024, RSA_F4, NULL, NULL); + RSAKey key; + if(rsa) + key.setData(rsa); + return key; +} + +QByteArray encryptRSA(const QByteArray &buf, const RSAKey &key, bool *ok) +{ + if(ok) + *ok = false; + + int size = RSA_size((RSA *)key.data()); + int flen = buf.size(); + if(flen >= size - 11) + flen = size - 11; + QByteArray result(size); + unsigned char *from = (unsigned char *)buf.data(); + unsigned char *to = (unsigned char *)result.data(); + int r = RSA_public_encrypt(flen, from, to, (RSA *)key.data(), RSA_PKCS1_PADDING); + if(r == -1) + return QByteArray(); + result.resize(r); + + if(ok) + *ok = true; + return result; +} + +QByteArray decryptRSA(const QByteArray &buf, const RSAKey &key, bool *ok) +{ + if(ok) + *ok = false; + + int size = RSA_size((RSA *)key.data()); + int flen = buf.size(); + QByteArray result(size); + unsigned char *from = (unsigned char *)buf.data(); + unsigned char *to = (unsigned char *)result.data(); + int r = RSA_private_decrypt(flen, from, to, (RSA *)key.data(), RSA_PKCS1_PADDING); + if(r == -1) + return QByteArray(); + result.resize(r); + + if(ok) + *ok = true; + return result; +} + +QByteArray encryptRSA2(const QByteArray &buf, const RSAKey &key, bool *ok) +{ + if(ok) + *ok = false; + + int size = RSA_size((RSA *)key.data()); + int flen = buf.size(); + if(flen >= size - 41) + flen = size - 41; + QByteArray result(size); + unsigned char *from = (unsigned char *)buf.data(); + unsigned char *to = (unsigned char *)result.data(); + int r = RSA_public_encrypt(flen, from, to, (RSA *)key.data(), RSA_PKCS1_OAEP_PADDING); + if(r == -1) + return QByteArray(); + result.resize(r); + + if(ok) + *ok = true; + return result; +} + +QByteArray decryptRSA2(const QByteArray &buf, const RSAKey &key, bool *ok) +{ + if(ok) + *ok = false; + + int size = RSA_size((RSA *)key.data()); + int flen = buf.size(); + QByteArray result(size); + unsigned char *from = (unsigned char *)buf.data(); + unsigned char *to = (unsigned char *)result.data(); + int r = RSA_private_decrypt(flen, from, to, (RSA *)key.data(), RSA_PKCS1_OAEP_PADDING); + if(r == -1) + return QByteArray(); + result.resize(r); + + if(ok) + *ok = true; + return result; +} diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/cipher.h b/kopete/protocols/jabber/libiris/cutestuff/util/cipher.h new file mode 100644 index 00000000..f162f16a --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/cipher.h @@ -0,0 +1,79 @@ +/* + * cipher.h - Simple wrapper to 3DES,AES128/256 CBC ciphers + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_CIPHER_H +#define CS_CIPHER_H + +#include +#include + +namespace Cipher +{ + enum Type { None, TripleDES, AES_128, AES_256 }; + + class Key + { + public: + Key() { v_type = None; } + + bool isValid() const { return (v_type == None ? false: true); } + void setType(Type x) { v_type = x; } + Type type() const { return v_type; } + void setData(const QByteArray &d) { v_data = d; } + const QByteArray & data() const { return v_data; } + + private: + Type v_type; + QByteArray v_data; + }; + + Key generateKey(Type); + QByteArray generateIV(Type); + int ivSize(Type); + QByteArray encrypt(const QByteArray &, const Key &, const QByteArray &iv, bool pad, bool *ok=0); + QByteArray decrypt(const QByteArray &, const Key &, const QByteArray &iv, bool pad, bool *ok=0); +} + +class RSAKey +{ +public: + RSAKey(); + RSAKey(const RSAKey &); + RSAKey & operator=(const RSAKey &); + ~RSAKey(); + + bool isNull() const; + void *data() const; + void setData(void *); + +private: + class Private; + Private *d; + + void free(); +}; + +RSAKey generateRSAKey(); +QByteArray encryptRSA(const QByteArray &buf, const RSAKey &key, bool *ok=0); +QByteArray decryptRSA(const QByteArray &buf, const RSAKey &key, bool *ok=0); +QByteArray encryptRSA2(const QByteArray &buf, const RSAKey &key, bool *ok=0); +QByteArray decryptRSA2(const QByteArray &buf, const RSAKey &key, bool *ok=0); + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/qrandom.cpp b/kopete/protocols/jabber/libiris/cutestuff/util/qrandom.cpp new file mode 100644 index 00000000..3becd7c5 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/qrandom.cpp @@ -0,0 +1,24 @@ +#include"qrandom.h" + +#include + +uchar QRandom::randomChar() +{ + return rand(); +} + +uint QRandom::randomInt() +{ + QByteArray a = randomArray(sizeof(uint)); + uint x; + memcpy(&x, a.data(), a.size()); + return x; +} + +QByteArray QRandom::randomArray(uint size) +{ + QByteArray a(size); + for(uint n = 0; n < size; ++n) + a[n] = randomChar(); + return a; +} diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/qrandom.h b/kopete/protocols/jabber/libiris/cutestuff/util/qrandom.h new file mode 100644 index 00000000..92339fb0 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/qrandom.h @@ -0,0 +1,14 @@ +#ifndef CS_QRANDOM_H +#define CS_QRANDOM_H + +#include + +class QRandom +{ +public: + static uchar randomChar(); + static uint randomInt(); + static QByteArray randomArray(uint size); +}; + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/safedelete.cpp b/kopete/protocols/jabber/libiris/cutestuff/util/safedelete.cpp new file mode 100644 index 00000000..6bd012e9 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/safedelete.cpp @@ -0,0 +1,119 @@ +#include"safedelete.h" + +#include + +//---------------------------------------------------------------------------- +// SafeDelete +//---------------------------------------------------------------------------- +SafeDelete::SafeDelete() +{ + lock = 0; +} + +SafeDelete::~SafeDelete() +{ + if(lock) + lock->dying(); +} + +void SafeDelete::deleteLater(QObject *o) +{ + if(!lock) + deleteSingle(o); + else + list.append(o); +} + +void SafeDelete::unlock() +{ + lock = 0; + deleteAll(); +} + +void SafeDelete::deleteAll() +{ + if(list.isEmpty()) + return; + + QObjectListIt it(list); + for(QObject *o; (o = it.current()); ++it) + deleteSingle(o); + list.clear(); +} + +void SafeDelete::deleteSingle(QObject *o) +{ +#if QT_VERSION < 0x030000 + // roll our own QObject::deleteLater() + SafeDeleteLater *sdl = SafeDeleteLater::ensureExists(); + sdl->deleteItLater(o); +#else + o->deleteLater(); +#endif +} + +//---------------------------------------------------------------------------- +// SafeDeleteLock +//---------------------------------------------------------------------------- +SafeDeleteLock::SafeDeleteLock(SafeDelete *sd) +{ + own = false; + if(!sd->lock) { + _sd = sd; + _sd->lock = this; + } + else + _sd = 0; +} + +SafeDeleteLock::~SafeDeleteLock() +{ + if(_sd) { + _sd->unlock(); + if(own) + delete _sd; + } +} + +void SafeDeleteLock::dying() +{ + _sd = new SafeDelete(*_sd); + own = true; +} + +//---------------------------------------------------------------------------- +// SafeDeleteLater +//---------------------------------------------------------------------------- +SafeDeleteLater *SafeDeleteLater::self = 0; + +SafeDeleteLater *SafeDeleteLater::ensureExists() +{ + if(!self) + new SafeDeleteLater(); + return self; +} + +SafeDeleteLater::SafeDeleteLater() +{ + list.setAutoDelete(true); + self = this; + QTimer::singleShot(0, this, SLOT(explode())); +} + +SafeDeleteLater::~SafeDeleteLater() +{ + list.clear(); + self = 0; +} + +void SafeDeleteLater::deleteItLater(QObject *o) +{ + list.append(o); +} + +void SafeDeleteLater::explode() +{ + delete this; +} + +#include "safedelete.moc" diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/safedelete.h b/kopete/protocols/jabber/libiris/cutestuff/util/safedelete.h new file mode 100644 index 00000000..078d36cd --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/safedelete.h @@ -0,0 +1,60 @@ +#ifndef SAFEDELETE_H +#define SAFEDELETE_H + +#include +#include + +class SafeDelete; +class SafeDeleteLock +{ +public: + SafeDeleteLock(SafeDelete *sd); + ~SafeDeleteLock(); + +private: + SafeDelete *_sd; + bool own; + friend class SafeDelete; + void dying(); +}; + +class SafeDelete +{ +public: + SafeDelete(); + ~SafeDelete(); + + void deleteLater(QObject *o); + + // same as QObject::deleteLater() + static void deleteSingle(QObject *o); + +private: + QObjectList list; + void deleteAll(); + + friend class SafeDeleteLock; + SafeDeleteLock *lock; + void unlock(); +}; + +class SafeDeleteLater : public QObject +{ + Q_OBJECT +public: + static SafeDeleteLater *ensureExists(); + void deleteItLater(QObject *o); + +private slots: + void explode(); + +private: + SafeDeleteLater(); + ~SafeDeleteLater(); + + QObjectList list; + friend class SafeDelete; + static SafeDeleteLater *self; +}; + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/sha1.cpp b/kopete/protocols/jabber/libiris/cutestuff/util/sha1.cpp new file mode 100644 index 00000000..3e3eb07c --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/sha1.cpp @@ -0,0 +1,196 @@ +/* + * sha1.cpp - Secure Hash Algorithm 1 + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"sha1.h" + +// CS_NAMESPACE_BEGIN + +/**************************************************************************** + SHA1 - from a public domain implementation by Steve Reid (steve@edmweb.com) +****************************************************************************/ + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15]^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + + +SHA1::SHA1() +{ + int wordSize; + + qSysInfo(&wordSize, &bigEndian); +} + +unsigned long SHA1::blk0(Q_UINT32 i) +{ + if(bigEndian) + return block->l[i]; + else + return (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) | (rol(block->l[i],8)&0x00FF00FF)); +} + +// Hash a single 512-bit block. This is the core of the algorithm. +void SHA1::transform(Q_UINT32 state[5], unsigned char buffer[64]) +{ + Q_UINT32 a, b, c, d, e; + + block = (CHAR64LONG16*)buffer; + + // Copy context->state[] to working vars + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + + // 4 rounds of 20 operations each. Loop unrolled. + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + + // Add the working vars back into context.state[] + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + + // Wipe variables + a = b = c = d = e = 0; +} + +// SHA1Init - Initialize new context +void SHA1::init(SHA1_CONTEXT* context) +{ + // SHA1 initialization constants + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + +// Run your data through this +void SHA1::update(SHA1_CONTEXT* context, unsigned char* data, Q_UINT32 len) +{ + Q_UINT32 i, j; + + j = (context->count[0] >> 3) & 63; + if((context->count[0] += len << 3) < (len << 3)) + context->count[1]++; + + context->count[1] += (len >> 29); + + if((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + +// Add padding and return the message digest +void SHA1::final(unsigned char digest[20], SHA1_CONTEXT* context) +{ + Q_UINT32 i, j; + unsigned char finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); // Endian independent + } + update(context, (unsigned char *)"\200", 1); + while ((context->count[0] & 504) != 448) { + update(context, (unsigned char *)"\0", 1); + } + update(context, finalcount, 8); // Should cause a transform() + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + + // Wipe variables + i = j = 0; + memset(context->buffer, 0, 64); + memset(context->state, 0, 20); + memset(context->count, 0, 8); + memset(&finalcount, 0, 8); +} + +QByteArray SHA1::hash(const QByteArray &a) +{ + SHA1_CONTEXT context; + QByteArray b(20); + + SHA1 s; + s.init(&context); + s.update(&context, (unsigned char *)a.data(), (unsigned int)a.size()); + s.final((unsigned char *)b.data(), &context); + return b; +} + +QByteArray SHA1::hashString(const QCString &cs) +{ + QByteArray a(cs.length()); + memcpy(a.data(), cs.data(), a.size()); + return SHA1::hash(a); +} + +QString SHA1::digest(const QString &in) +{ + QByteArray a = SHA1::hashString(in.utf8()); + QString out; + for(int n = 0; n < (int)a.size(); ++n) { + QString str; + str.sprintf("%02x", (uchar)a[n]); + out.append(str); + } + + return out; +} + +// CS_NAMESPACE_END diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/sha1.h b/kopete/protocols/jabber/libiris/cutestuff/util/sha1.h new file mode 100644 index 00000000..6b0453b4 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/sha1.h @@ -0,0 +1,63 @@ +/* + * sha1.h - Secure Hash Algorithm 1 + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_SHA1_H +#define CS_SHA1_H + +#include + +// CS_NAMESPACE_BEGIN + +class SHA1 +{ +public: + static QByteArray hash(const QByteArray &); + static QByteArray hashString(const QCString &); + static QString digest(const QString &); + +private: + SHA1(); + + struct SHA1_CONTEXT + { + Q_UINT32 state[5]; + Q_UINT32 count[2]; + unsigned char buffer[64]; + }; + + typedef union { + unsigned char c[64]; + Q_UINT32 l[16]; + } CHAR64LONG16; + + void transform(Q_UINT32 state[5], unsigned char buffer[64]); + void init(SHA1_CONTEXT* context); + void update(SHA1_CONTEXT* context, unsigned char* data, Q_UINT32 len); + void final(unsigned char digest[20], SHA1_CONTEXT* context); + + unsigned long blk0(Q_UINT32 i); + bool bigEndian; + + CHAR64LONG16* block; +}; + +// CS_NAMESPACE_END + +#endif diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/showtextdlg.cpp b/kopete/protocols/jabber/libiris/cutestuff/util/showtextdlg.cpp new file mode 100644 index 00000000..0b02df60 --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/showtextdlg.cpp @@ -0,0 +1,61 @@ +/* + * showtextdlg.cpp - dialog for displaying a text file + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"showtextdlg.h" + +#include +#include +#include +#include +#include + + +ShowTextDlg::ShowTextDlg(const QString &fname, bool rich, QWidget *parent, const char *name) +:QDialog(parent, name, FALSE, WDestructiveClose) +{ + QString text; + + QFile f(fname); + if(f.open(IO_ReadOnly)) { + QTextStream t(&f); + while(!t.eof()) + text += t.readLine() + '\n'; + f.close(); + } + + QVBoxLayout *vb1 = new QVBoxLayout(this, 8); + QTextEdit *te = new QTextEdit(this); + te->setReadOnly(TRUE); + te->setTextFormat(rich ? QTextEdit::RichText : QTextEdit::PlainText); + te->setText(text); + + vb1->addWidget(te); + + QHBoxLayout *hb1 = new QHBoxLayout(vb1); + hb1->addStretch(1); + QPushButton *pb = new QPushButton(tr("&OK"), this); + connect(pb, SIGNAL(clicked()), SLOT(accept())); + hb1->addWidget(pb); + hb1->addStretch(1); + + resize(560, 384); +} + +#include "showtextdlg.moc" diff --git a/kopete/protocols/jabber/libiris/cutestuff/util/showtextdlg.h b/kopete/protocols/jabber/libiris/cutestuff/util/showtextdlg.h new file mode 100644 index 00000000..f59ae32c --- /dev/null +++ b/kopete/protocols/jabber/libiris/cutestuff/util/showtextdlg.h @@ -0,0 +1,33 @@ +/* + * showtextdlg.h - dialog for displaying a text file + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CS_SHOWTEXTDLG_H +#define CS_SHOWTEXTDLG_H + +#include + +class ShowTextDlg : public QDialog +{ + Q_OBJECT +public: + ShowTextDlg(const QString &fname, bool rich=FALSE, QWidget *parent=0, const char *name=0); +}; + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/Makefile.am b/kopete/protocols/jabber/libiris/iris/Makefile.am new file mode 100644 index 00000000..03e5818f --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = include jabber xmpp-core xmpp-im diff --git a/kopete/protocols/jabber/libiris/iris/TODO b/kopete/protocols/jabber/libiris/iris/TODO new file mode 100644 index 00000000..e6cf74c6 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/TODO @@ -0,0 +1,16 @@ +- Stream::id(), Stream::lang() +- whitespace pings (but disable when using http poll) +- make stanza error conditions work for both 1.0 and old + +- xmpp-im (messages, roster, subscriptions, presence, privacy) +- document xmpp-core +- provide complete support for xmpp-core. this means all functionality from + the draft, and noting behavior issues (like IQ semantics) in the + library documentation. + +- SASL "EXTERNAL" w/ client certificate +- SASL "ANONYMOUS" ? + +credits: + MD5 algorithm by Peter Deutsch (Aladdin Enterprises) + diff --git a/kopete/protocols/jabber/libiris/iris/include/Makefile.am b/kopete/protocols/jabber/libiris/iris/include/Makefile.am new file mode 100644 index 00000000..6375392b --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/include/Makefile.am @@ -0,0 +1,7 @@ +METASOURCES = AUTO + +noinst_LTLIBRARIES = libiris.la +INCLUDES = -Ixmpp-core -Ixmpp-im -I../cutestuff/util -I../cutestuff/network -I../qca/src $(all_includes) + +libiris_la_SOURCES = \ + empty.cpp diff --git a/kopete/protocols/jabber/libiris/iris/include/empty.cpp b/kopete/protocols/jabber/libiris/iris/include/empty.cpp new file mode 100644 index 00000000..e69de29b diff --git a/kopete/protocols/jabber/libiris/iris/include/im.h b/kopete/protocols/jabber/libiris/iris/include/im.h new file mode 100644 index 00000000..832ec62a --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/include/im.h @@ -0,0 +1,721 @@ +/* + * im.h - XMPP "IM" library API + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef XMPP_IM_H +#define XMPP_IM_H + +#include +#include +#include"xmpp.h" + +namespace XMPP +{ + class Url + { + public: + Url(const QString &url="", const QString &desc=""); + Url(const Url &); + Url & operator=(const Url &); + ~Url(); + + QString url() const; + QString desc() const; + + void setUrl(const QString &); + void setDesc(const QString &); + + private: + class Private; + Private *d; + }; + + typedef QValueList UrlList; + typedef QMap StringMap; + typedef enum { OfflineEvent, DeliveredEvent, DisplayedEvent, + ComposingEvent, CancelEvent, InactiveEvent, GoneEvent } MsgEvent; + + class Message + { + public: + Message(const Jid &to=""); + Message(const Message &from); + Message & operator=(const Message &from); + ~Message(); + + Jid to() const; + Jid from() const; + QString id() const; + QString type() const; + QString lang() const; + QString subject(const QString &lang="") const; + QString body(const QString &lang="") const; + QString xHTMLBody(const QString &lang="") const; + QString thread() const; + Stanza::Error error() const; + + void setTo(const Jid &j); + void setFrom(const Jid &j); + void setId(const QString &s); + void setType(const QString &s); + void setLang(const QString &s); + void setSubject(const QString &s, const QString &lang=""); + void setBody(const QString &s, const QString &lang=""); + void setXHTMLBody(const QString &s, const QString &lang="", const QString &attr = ""); + void setThread(const QString &s); + void setError(const Stanza::Error &err); + + // JEP-0091 + QDateTime timeStamp() const; + void setTimeStamp(const QDateTime &ts); + + // JEP-0066 + UrlList urlList() const; + void urlAdd(const Url &u); + void urlsClear(); + void setUrlList(const UrlList &list); + + // JEP-0022 + QString eventId() const; + void setEventId(const QString& id); + bool containsEvents() const; + bool containsEvent(MsgEvent e) const; + void addEvent(MsgEvent e); + + // JEP-0027 + QString xencrypted() const; + void setXEncrypted(const QString &s); + + // Obsolete invitation + QString invite() const; + void setInvite(const QString &s); + + // for compatibility. delete me later + bool spooled() const; + void setSpooled(bool); + bool wasEncrypted() const; + void setWasEncrypted(bool); + + Stanza toStanza(Stream *stream) const; + bool fromStanza(const Stanza &s, int tzoffset); + + private: + class Private; + Private *d; + }; + + class Subscription + { + public: + enum SubType { None, To, From, Both, Remove }; + + Subscription(SubType type=None); + + int type() const; + + QString toString() const; + bool fromString(const QString &); + + private: + SubType value; + }; + + class Status + { + public: + Status(const QString &show="", const QString &status="", int priority=0, bool available=true); + ~Status(); + + int priority() const; + const QString & show() const; + const QString & status() const; + QDateTime timeStamp() const; + const QString & keyID() const; + bool isAvailable() const; + bool isAway() const; + bool isInvisible() const; + bool hasError() const; + int errorCode() const; + const QString & errorString() const; + + const QString & xsigned() const; + const QString & songTitle() const; + const QString & capsNode() const; + const QString & capsVersion() const; + const QString & capsExt() const; + + void setPriority(int); + void setShow(const QString &); + void setStatus(const QString &); + void setTimeStamp(const QDateTime &); + void setKeyID(const QString &); + void setIsAvailable(bool); + void setIsInvisible(bool); + void setError(int, const QString &); + void setCapsNode(const QString&); + void setCapsVersion(const QString&); + void setCapsExt(const QString&); + + void setXSigned(const QString &); + void setSongTitle(const QString &); + + private: + int v_priority; + QString v_show, v_status, v_key; + QDateTime v_timeStamp; + bool v_isAvailable; + bool v_isInvisible; + + QString v_xsigned; + // gabber song extension + QString v_songTitle; + QString v_capsNode, v_capsVersion, v_capsExt; + + int ecode; + QString estr; + + class Private; + Private *d; + }; + + class Resource + { + public: + Resource(const QString &name="", const Status &s=Status()); + ~Resource(); + + const QString & name() const; + int priority() const; + const Status & status() const; + + void setName(const QString &); + void setStatus(const Status &); + + private: + QString v_name; + Status v_status; + + class ResourcePrivate *d; + }; + + class ResourceList : public QValueList + { + public: + ResourceList(); + ~ResourceList(); + + ResourceList::Iterator find(const QString &); + ResourceList::Iterator priority(); + + ResourceList::ConstIterator find(const QString &) const; + ResourceList::ConstIterator priority() const; + + private: + class ResourceListPrivate *d; + }; + + class RosterItem + { + public: + RosterItem(const Jid &jid=""); + virtual ~RosterItem(); + + const Jid & jid() const; + const QString & name() const; + const QStringList & groups() const; + const Subscription & subscription() const; + const QString & ask() const; + bool isPush() const; + bool inGroup(const QString &) const; + + virtual void setJid(const Jid &); + void setName(const QString &); + void setGroups(const QStringList &); + void setSubscription(const Subscription &); + void setAsk(const QString &); + void setIsPush(bool); + bool addGroup(const QString &); + bool removeGroup(const QString &); + + QDomElement toXml(QDomDocument *) const; + bool fromXml(const QDomElement &); + + private: + Jid v_jid; + QString v_name; + QStringList v_groups; + Subscription v_subscription; + QString v_ask; + bool v_push; + + class RosterItemPrivate *d; + }; + + class Roster : public QValueList + { + public: + Roster(); + ~Roster(); + + Roster::Iterator find(const Jid &); + Roster::ConstIterator find(const Jid &) const; + + private: + class RosterPrivate *d; + }; + + class Features + { + public: + Features(); + Features(const QStringList &); + Features(const QString &); + ~Features(); + + QStringList list() const; // actual featurelist + void setList(const QStringList &); + + // features + bool canRegister() const; + bool canSearch() const; + bool canGroupchat() const; + bool canVoice() const; + bool canDisco() const; + bool canXHTML() const; + bool isGateway() const; + bool haveVCard() const; + + enum FeatureID { + FID_Invalid = -1, + FID_None, + FID_Register, + FID_Search, + FID_Groupchat, + FID_Disco, + FID_Gateway, + FID_VCard, + FID_Xhtml, + + // private Psi actions + FID_Add + }; + + // useful functions + bool test(const QStringList &) const; + + QString name() const; + static QString name(long id); + static QString name(const QString &feature); + + long id() const; + static long id(const QString &feature); + static QString feature(long id); + + class FeatureName; + private: + QStringList _list; + }; + + class AgentItem + { + public: + AgentItem() { } + + const Jid & jid() const { return v_jid; } + const QString & name() const { return v_name; } + const QString & category() const { return v_category; } + const QString & type() const { return v_type; } + const Features & features() const { return v_features; } + + void setJid(const Jid &j) { v_jid = j; } + void setName(const QString &n) { v_name = n; } + void setCategory(const QString &c) { v_category = c; } + void setType(const QString &t) { v_type = t; } + void setFeatures(const Features &f) { v_features = f; } + + private: + Jid v_jid; + QString v_name, v_category, v_type; + Features v_features; + }; + + typedef QValueList AgentList; + + class DiscoItem + { + public: + DiscoItem(); + ~DiscoItem(); + + const Jid &jid() const; + const QString &node() const; + const QString &name() const; + + void setJid(const Jid &); + void setName(const QString &); + void setNode(const QString &); + + enum Action { + None = 0, + Remove, + Update + }; + + Action action() const; + void setAction(Action); + + const Features &features() const; + void setFeatures(const Features &); + + struct Identity + { + QString category; + QString name; + QString type; + }; + + typedef QValueList Identities; + + const Identities &identities() const; + void setIdentities(const Identities &); + + // some useful helper functions + static Action string2action(QString s); + static QString action2string(Action a); + + DiscoItem & operator= (const DiscoItem &); + DiscoItem(const DiscoItem &); + + operator AgentItem() const { return toAgentItem(); } + AgentItem toAgentItem() const; + void fromAgentItem(const AgentItem &); + + private: + class Private; + Private *d; + }; + + typedef QValueList DiscoList; + + class FormField + { + public: + enum { username, nick, password, name, first, last, email, address, city, state, zip, phone, url, date, misc }; + FormField(const QString &type="", const QString &value=""); + ~FormField(); + + int type() const; + QString fieldName() const; + QString realName() const; + bool isSecret() const; + const QString & value() const; + void setType(int); + bool setType(const QString &); + void setValue(const QString &); + + private: + int tagNameToType(const QString &) const; + QString typeToTagName(int) const; + + int v_type; + QString v_value; + + class Private; + Private *d; + }; + + class Form : public QValueList + { + public: + Form(const Jid &j=""); + ~Form(); + + Jid jid() const; + QString instructions() const; + QString key() const; + void setJid(const Jid &); + void setInstructions(const QString &); + void setKey(const QString &); + + private: + Jid v_jid; + QString v_instructions, v_key; + + class Private; + Private *d; + }; + + class SearchResult + { + public: + SearchResult(const Jid &jid=""); + ~SearchResult(); + + const Jid & jid() const; + const QString & nick() const; + const QString & first() const; + const QString & last() const; + const QString & email() const; + + void setJid(const Jid &); + void setNick(const QString &); + void setFirst(const QString &); + void setLast(const QString &); + void setEmail(const QString &); + + private: + Jid v_jid; + QString v_nick, v_first, v_last, v_email; + }; + + class Client; + class LiveRosterItem; + class LiveRoster; + class S5BManager; + class IBBManager; + class JidLinkManager; + class FileTransferManager; + + class Task : public QObject + { + Q_OBJECT + public: + enum { ErrDisc }; + Task(Task *parent); + Task(Client *, bool isRoot); + virtual ~Task(); + + Task *parent() const; + Client *client() const; + QDomDocument *doc() const; + QString id() const; + + bool success() const; + int statusCode() const; + const QString & statusString() const; + + void go(bool autoDelete=false); + virtual bool take(const QDomElement &); + void safeDelete(); + + signals: + void finished(); + + protected: + virtual void onGo(); + virtual void onDisconnect(); + void send(const QDomElement &); + void setSuccess(int code=0, const QString &str=""); + void setError(const QDomElement &); + void setError(int code=0, const QString &str=""); + void debug(const char *, ...); + void debug(const QString &); + bool iqVerify(const QDomElement &x, const Jid &to, const QString &id, const QString &xmlns=""); + + private slots: + void clientDisconnected(); + void done(); + + private: + void init(); + + class TaskPrivate; + TaskPrivate *d; + }; + + class Client : public QObject + { + Q_OBJECT + + public: + Client(QObject *parent=0); + ~Client(); + + bool isActive() const; + void connectToServer(ClientStream *s, const Jid &j, bool auth=true); + void start(const QString &host, const QString &user, const QString &pass, const QString &resource); + void close(bool fast=false); + + Stream & stream(); + const LiveRoster & roster() const; + const ResourceList & resourceList() const; + + void send(const QDomElement &); + void send(const QString &); + + QString host() const; + QString user() const; + QString pass() const; + QString resource() const; + Jid jid() const; + + void rosterRequest(); + void sendMessage(const Message &); + void sendSubscription(const Jid &, const QString &); + void setPresence(const Status &); + + void debug(const QString &); + QString genUniqueId(); + Task *rootTask(); + QDomDocument *doc() const; + + QString OSName() const; + QString timeZone() const; + int timeZoneOffset() const; + QString clientName() const; + QString clientVersion() const; + QString capsNode() const; + QString capsVersion() const; + QString capsExt() const; + + void setOSName(const QString &); + void setTimeZone(const QString &, int); + void setClientName(const QString &); + void setClientVersion(const QString &); + void setCapsNode(const QString &); + void setCapsVersion(const QString &); + + void setIdentity(DiscoItem::Identity); + DiscoItem::Identity identity(); + + void addExtension(const QString& ext, const Features& f); + void removeExtension(const QString& ext); + const Features& extension(const QString& ext) const; + QStringList extensions() const; + + S5BManager *s5bManager() const; + IBBManager *ibbManager() const; + JidLinkManager *jidLinkManager() const; + + void setFileTransferEnabled(bool b); + FileTransferManager *fileTransferManager() const; + + bool groupChatJoin(const QString &host, const QString &room, const QString &nick); + bool groupChatJoin(const QString &host, const QString &room, const QString &nick, const QString &password); + void groupChatSetStatus(const QString &host, const QString &room, const Status &); + void groupChatChangeNick(const QString &host, const QString &room, const QString &nick, const Status &); + void groupChatLeave(const QString &host, const QString &room); + + signals: + void activated(); + void disconnected(); + //void authFinished(bool, int, const QString &); + void rosterRequestFinished(bool, int, const QString &); + void rosterItemAdded(const RosterItem &); + void rosterItemUpdated(const RosterItem &); + void rosterItemRemoved(const RosterItem &); + void resourceAvailable(const Jid &, const Resource &); + void resourceUnavailable(const Jid &, const Resource &); + void presenceError(const Jid &, int, const QString &); + void subscription(const Jid &, const QString &); + void messageReceived(const Message &); + void debugText(const QString &); + void xmlIncoming(const QString &); + void xmlOutgoing(const QString &); + void groupChatJoined(const Jid &); + void groupChatLeft(const Jid &); + void groupChatPresence(const Jid &, const Status &); + void groupChatError(const Jid &, int, const QString &); + + void incomingJidLink(); + + private slots: + //void streamConnected(); + //void streamHandshaken(); + //void streamError(const StreamError &); + //void streamSSLCertificateReady(const QSSLCert &); + //void streamCloseFinished(); + void streamError(int); + void streamReadyRead(); + void streamIncomingXml(const QString &); + void streamOutgoingXml(const QString &); + + void slotRosterRequestFinished(); + + // basic daemons + void ppSubscription(const Jid &, const QString &); + void ppPresence(const Jid &, const Status &); + void pmMessage(const Message &); + void prRoster(const Roster &); + + void s5b_incomingReady(); + void ibb_incomingReady(); + + public: + class GroupChat; + private: + void cleanup(); + void distribute(const QDomElement &); + void importRoster(const Roster &); + void importRosterItem(const RosterItem &); + void updateSelfPresence(const Jid &, const Status &); + void updatePresence(LiveRosterItem *, const Jid &, const Status &); + + class ClientPrivate; + ClientPrivate *d; + }; + + class LiveRosterItem : public RosterItem + { + public: + LiveRosterItem(const Jid &j=""); + LiveRosterItem(const RosterItem &); + ~LiveRosterItem(); + + void setRosterItem(const RosterItem &); + + ResourceList & resourceList(); + ResourceList::Iterator priority(); + + const ResourceList & resourceList() const; + ResourceList::ConstIterator priority() const; + + bool isAvailable() const; + const Status & lastUnavailableStatus() const; + bool flagForDelete() const; + + void setLastUnavailableStatus(const Status &); + void setFlagForDelete(bool); + + private: + ResourceList v_resourceList; + Status v_lastUnavailableStatus; + bool v_flagForDelete; + + class LiveRosterItemPrivate; + LiveRosterItemPrivate *d; + }; + + class LiveRoster : public QValueList + { + public: + LiveRoster(); + ~LiveRoster(); + + void flagAllForDelete(); + LiveRoster::Iterator find(const Jid &, bool compareRes=true); + LiveRoster::ConstIterator find(const Jid &, bool compareRes=true) const; + + private: + class LiveRosterPrivate; + LiveRosterPrivate *d; + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/include/xmpp.h b/kopete/protocols/jabber/libiris/iris/include/xmpp.h new file mode 100644 index 00000000..5636f963 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/include/xmpp.h @@ -0,0 +1,553 @@ +/* + * xmpp.h - XMPP "core" library API + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef XMPP_H +#define XMPP_H + +#include +#include +#include +#include +#include +#include +#include + +namespace QCA +{ + class TLS; +} + +#ifndef CS_XMPP +class ByteStream; +#endif + +namespace XMPP +{ + // CS_IMPORT_BEGIN cutestuff/bytestream.h +#ifdef CS_XMPP + class ByteStream; +#endif + // CS_IMPORT_END + + class Debug + { + public: + virtual ~Debug(); + + virtual void msg(const QString &)=0; + virtual void outgoingTag(const QString &)=0; + virtual void incomingTag(const QString &)=0; + virtual void outgoingXml(const QDomElement &)=0; + virtual void incomingXml(const QDomElement &)=0; + }; + + void setDebug(Debug *); + + class Connector : public QObject + { + Q_OBJECT + public: + Connector(QObject *parent=0); + virtual ~Connector(); + + virtual void connectToServer(const QString &server)=0; + virtual ByteStream *stream() const=0; + virtual void done()=0; + + bool useSSL() const; + bool havePeerAddress() const; + QHostAddress peerAddress() const; + Q_UINT16 peerPort() const; + + signals: + void connected(); + void error(); + + protected: + void setUseSSL(bool b); + void setPeerAddressNone(); + void setPeerAddress(const QHostAddress &addr, Q_UINT16 port); + + private: + bool ssl; + bool haveaddr; + QHostAddress addr; + Q_UINT16 port; + }; + + class AdvancedConnector : public Connector + { + Q_OBJECT + public: + enum Error { ErrConnectionRefused, ErrHostNotFound, ErrProxyConnect, ErrProxyNeg, ErrProxyAuth, ErrStream }; + AdvancedConnector(QObject *parent=0); + virtual ~AdvancedConnector(); + + class Proxy + { + public: + enum { None, HttpConnect, HttpPoll, Socks }; + Proxy(); + ~Proxy(); + + int type() const; + QString host() const; + Q_UINT16 port() const; + QString url() const; + QString user() const; + QString pass() const; + int pollInterval() const; + + void setHttpConnect(const QString &host, Q_UINT16 port); + void setHttpPoll(const QString &host, Q_UINT16 port, const QString &url); + void setSocks(const QString &host, Q_UINT16 port); + void setUserPass(const QString &user, const QString &pass); + void setPollInterval(int secs); + + private: + int t; + QString v_host, v_url; + Q_UINT16 v_port; + QString v_user, v_pass; + int v_poll; + }; + + void setProxy(const Proxy &proxy); + void setOptHostPort(const QString &host, Q_UINT16 port); + void setOptProbe(bool); + void setOptSSL(bool); + + void changePollInterval(int secs); + + void connectToServer(const QString &server); + ByteStream *stream() const; + void done(); + + int errorCode() const; + + signals: + void srvLookup(const QString &server); + void srvResult(bool success); + void httpSyncStarted(); + void httpSyncFinished(); + + private slots: + void dns_done(); + void srv_done(); + void bs_connected(); + void bs_error(int); + void http_syncStarted(); + void http_syncFinished(); + + private: + class Private; + Private *d; + + void cleanup(); + void do_resolve(); + void do_connect(); + void tryNextSrv(); + }; + + class TLSHandler : public QObject + { + Q_OBJECT + public: + TLSHandler(QObject *parent=0); + virtual ~TLSHandler(); + + virtual void reset()=0; + virtual void startClient(const QString &host)=0; + virtual void write(const QByteArray &a)=0; + virtual void writeIncoming(const QByteArray &a)=0; + + signals: + void success(); + void fail(); + void closed(); + void readyRead(const QByteArray &a); + void readyReadOutgoing(const QByteArray &a, int plainBytes); + }; + + class QCATLSHandler : public TLSHandler + { + Q_OBJECT + public: + QCATLSHandler(QCA::TLS *parent); + ~QCATLSHandler(); + + QCA::TLS *tls() const; + int tlsError() const; + + void reset(); + void startClient(const QString &host); + void write(const QByteArray &a); + void writeIncoming(const QByteArray &a); + + signals: + void tlsHandshaken(); + + public slots: + void continueAfterHandshake(); + + private slots: + void tls_handshaken(); + void tls_readyRead(); + void tls_readyReadOutgoing(int); + void tls_closed(); + void tls_error(int); + + private: + class Private; + Private *d; + }; + + class Jid + { + public: + Jid(); + ~Jid(); + + Jid(const QString &s); + Jid(const char *s); + Jid & operator=(const QString &s); + Jid & operator=(const char *s); + + void set(const QString &s); + void set(const QString &domain, const QString &node, const QString &resource=""); + + void setDomain(const QString &s); + void setNode(const QString &s); + void setResource(const QString &s); + + const QString & domain() const { return d; } + const QString & node() const { return n; } + const QString & resource() const { return r; } + const QString & bare() const { return b; } + const QString & full() const { return f; } + + Jid withNode(const QString &s) const; + Jid withResource(const QString &s) const; + + bool isValid() const; + bool isEmpty() const; + bool compare(const Jid &a, bool compareRes=true) const; + + static bool validDomain(const QString &s, QString *norm=0); + static bool validNode(const QString &s, QString *norm=0); + static bool validResource(const QString &s, QString *norm=0); + + // TODO: kill these later + const QString & host() const { return d; } + const QString & user() const { return n; } + const QString & userHost() const { return b; } + + private: + void reset(); + void update(); + + QString f, b, d, n, r; + bool valid; + }; + + class Stream; + class Stanza + { + public: + enum Kind { Message, Presence, IQ }; + enum ErrorType { Cancel, Continue, Modify, Auth, Wait }; + enum ErrorCond + { + BadRequest, + Conflict, + FeatureNotImplemented, + Forbidden, + InternalServerError, + ItemNotFound, + JidMalformed, + NotAllowed, + PaymentRequired, + RecipientUnavailable, + RegistrationRequired, + ServerNotFound, + ServerTimeout, + ResourceConstraint, + ServiceUnavailable, + SubscriptionRequired, + UndefinedCondition, + UnexpectedRequest + }; + + Stanza(); + Stanza(const Stanza &from); + Stanza & operator=(const Stanza &from); + virtual ~Stanza(); + + class Error + { + public: + Error(int type=Cancel, int condition=UndefinedCondition, const QString &text="", const QDomElement &appSpec=QDomElement()); + + int type; + int condition; + QString text; + QDomElement appSpec; + }; + + bool isNull() const; + + QDomElement element() const; + QString toString() const; + + QDomDocument & doc() const; + QString baseNS() const; + QString xhtmlImNS() const; + QString xhtmlNS() const; + QDomElement createElement(const QString &ns, const QString &tagName); + QDomElement createTextElement(const QString &ns, const QString &tagName, const QString &text); + QDomElement createXHTMLElement(const QString &xHTML); + void appendChild(const QDomElement &e); + + Kind kind() const; + void setKind(Kind k); + + Jid to() const; + Jid from() const; + QString id() const; + QString type() const; + QString lang() const; + + void setTo(const Jid &j); + void setFrom(const Jid &j); + void setId(const QString &id); + void setType(const QString &type); + void setLang(const QString &lang); + + Error error() const; + void setError(const Error &err); + void clearError(); + + private: + friend class Stream; + Stanza(Stream *s, Kind k, const Jid &to, const QString &type, const QString &id); + Stanza(Stream *s, const QDomElement &e); + + class Private; + Private *d; + }; + + class Stream : public QObject + { + Q_OBJECT + public: + enum Error { ErrParse, ErrProtocol, ErrStream, ErrCustom = 10 }; + enum StreamCond { + GenericStreamError, + Conflict, + ConnectionTimeout, + InternalServerError, + InvalidFrom, + InvalidXml, + PolicyViolation, + ResourceConstraint, + SystemShutdown + }; + + Stream(QObject *parent=0); + virtual ~Stream(); + + virtual QDomDocument & doc() const=0; + virtual QString baseNS() const=0; + virtual QString xhtmlImNS() const=0; + virtual QString xhtmlNS() const=0; + virtual bool old() const=0; + + virtual void close()=0; + virtual bool stanzaAvailable() const=0; + virtual Stanza read()=0; + virtual void write(const Stanza &s)=0; + + virtual int errorCondition() const=0; + virtual QString errorText() const=0; + virtual QDomElement errorAppSpec() const=0; + + Stanza createStanza(Stanza::Kind k, const Jid &to="", const QString &type="", const QString &id=""); + Stanza createStanza(const QDomElement &e); + + static QString xmlToString(const QDomElement &e, bool clip=false); + + signals: + void connectionClosed(); + void delayedCloseFinished(); + void readyRead(); + void stanzaWritten(); + void error(int); + }; + + class ClientStream : public Stream + { + Q_OBJECT + public: + enum Error { + ErrConnection = ErrCustom, // Connection error, ask Connector-subclass what's up + ErrNeg, // Negotiation error, see condition + ErrTLS, // TLS error, see condition + ErrAuth, // Auth error, see condition + ErrSecurityLayer, // broken SASL security layer + ErrBind // Resource binding error + }; + enum Warning { + WarnOldVersion, // server uses older XMPP/Jabber "0.9" protocol + WarnNoTLS // there is no chance for TLS at this point + }; + enum NegCond { + HostGone, // host no longer hosted + HostUnknown, // unknown host + RemoteConnectionFailed, // unable to connect to a required remote resource + SeeOtherHost, // a 'redirect', see errorText() for other host + UnsupportedVersion // unsupported XMPP version + }; + enum TLSCond { + TLSStart, // server rejected STARTTLS + TLSFail // TLS failed, ask TLSHandler-subclass what's up + }; + enum SecurityLayer { + LayerTLS, + LayerSASL + }; + enum AuthCond { + GenericAuthError, // all-purpose "can't login" error + NoMech, // No appropriate auth mech available + BadProto, // Bad SASL auth protocol + BadServ, // Server failed mutual auth + EncryptionRequired, // can't use mech without TLS + InvalidAuthzid, // bad input JID + InvalidMech, // bad mechanism + InvalidRealm, // bad realm + MechTooWeak, // can't use mech with this authzid + NotAuthorized, // bad user, bad password, bad creditials + TemporaryAuthFailure // please try again later! + }; + enum BindCond { + BindNotAllowed, // not allowed to bind a resource + BindConflict // resource in-use + }; + + ClientStream(Connector *conn, TLSHandler *tlsHandler=0, QObject *parent=0); + ClientStream(const QString &host, const QString &defRealm, ByteStream *bs, QCA::TLS *tls=0, QObject *parent=0); // server + ~ClientStream(); + + Jid jid() const; + void connectToServer(const Jid &jid, bool auth=true); + void accept(); // server + bool isActive() const; + bool isAuthenticated() const; + + // login params + void setUsername(const QString &s); + void setPassword(const QString &s); + void setRealm(const QString &s); + void continueAfterParams(); + + // SASL information + QString saslMechanism() const; + int saslSSF() const; + + // binding + void setResourceBinding(bool); + + // security options (old protocol only uses the first !) + void setAllowPlain(bool); + void setRequireMutualAuth(bool); + void setSSFRange(int low, int high); + void setOldOnly(bool); + void setSASLMechanism(const QString &s); + void setLocalAddr(const QHostAddress &addr, Q_UINT16 port); + + // reimplemented + QDomDocument & doc() const; + QString baseNS() const; + QString xhtmlImNS() const; + QString xhtmlNS() const; + bool old() const; + + void close(); + bool stanzaAvailable() const; + Stanza read(); + void write(const Stanza &s); + + int errorCondition() const; + QString errorText() const; + QDomElement errorAppSpec() const; + + // extra + void writeDirect(const QString &s); + void setNoopTime(int mills); + + signals: + void connected(); + void securityLayerActivated(int); + void needAuthParams(bool user, bool pass, bool realm); + void authenticated(); + void warning(int); + void incomingXml(const QString &s); + void outgoingXml(const QString &s); + + public slots: + void continueAfterWarning(); + + private slots: + void cr_connected(); + void cr_error(); + + void bs_connectionClosed(); + void bs_delayedCloseFinished(); + void bs_error(int); // server only + + void ss_readyRead(); + void ss_bytesWritten(int); + void ss_tlsHandshaken(); + void ss_tlsClosed(); + void ss_error(int); + + void sasl_clientFirstStep(const QString &mech, const QByteArray *clientInit); + void sasl_nextStep(const QByteArray &stepData); + void sasl_needParams(bool user, bool authzid, bool pass, bool realm); + void sasl_authCheck(const QString &user, const QString &authzid); + void sasl_authenticated(); + void sasl_error(int); + + void doNoop(); + void doReadyRead(); + + private: + class Private; + Private *d; + + void reset(bool all=false); + void processNext(); + int convertedSASLCond() const; + bool handleNeed(); + void handleError(); + void srvProcessNext(); + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/jabber/Makefile.am b/kopete/protocols/jabber/libiris/iris/jabber/Makefile.am new file mode 100644 index 00000000..d480984d --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/Makefile.am @@ -0,0 +1,15 @@ +# we deal with s5b.moc separately since KDE's build system can't cope with Q_OBJECT in .cpp files +METASOURCES = filetransfer.moc xmpp_ibb.moc xmpp_jidlink.moc + +noinst_LTLIBRARIES = libiris_jabber.la +INCLUDES = -I$(srcdir)/../include -I$(srcdir)/../xmpp-core -I$(srcdir)/../xmpp-im -I$(srcdir)/../../cutestuff/util -I$(srcdir)/../../cutestuff/network -I$(srcdir)/../../qca/src $(all_includes) + +libiris_jabber_la_SOURCES = \ + filetransfer.cpp s5b.cpp xmpp_ibb.cpp xmpp_jidlink.cpp all_mocs.cpp + +s5b.lo: s5b.moc + +CLEANFILES = s5b.moc +s5b.moc: $(srcdir)/s5b.cpp $(srcdir)/s5b.h + ${MOC} $(srcdir)/s5b.h > $@ + ${MOC} $(srcdir)/s5b.cpp >> $@ diff --git a/kopete/protocols/jabber/libiris/iris/jabber/all_mocs.cpp b/kopete/protocols/jabber/libiris/iris/jabber/all_mocs.cpp new file mode 100644 index 00000000..f962a854 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/all_mocs.cpp @@ -0,0 +1,23 @@ +/* + * all_mocs.cpp - #include all .moc files in this directory + * Copyright (C) 2004 Richard Smith + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "filetransfer.moc" +#include "xmpp_ibb.moc" +#include "xmpp_jidlink.moc" diff --git a/kopete/protocols/jabber/libiris/iris/jabber/filetransfer.cpp b/kopete/protocols/jabber/libiris/iris/jabber/filetransfer.cpp new file mode 100644 index 00000000..1697b6a2 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/filetransfer.cpp @@ -0,0 +1,770 @@ +/* + * filetransfer.cpp - File Transfer + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"filetransfer.h" + +#include +#include +#include +#include +#include"xmpp_xmlcommon.h" +#include"s5b.h" + +#define SENDBUFSIZE 65536 + +using namespace XMPP; + +// firstChildElement +// +// Get an element's first child element +static QDomElement firstChildElement(const QDomElement &e) +{ + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + if(n.isElement()) + return n.toElement(); + } + return QDomElement(); +} + +//---------------------------------------------------------------------------- +// FileTransfer +//---------------------------------------------------------------------------- +class FileTransfer::Private +{ +public: + FileTransferManager *m; + JT_FT *ft; + Jid peer; + QString fname; + Q_LLONG size; + Q_LLONG sent; + QString desc; + bool rangeSupported; + Q_LLONG rangeOffset, rangeLength, length; + QString streamType; + bool needStream; + QString id, iq_id; + S5BConnection *c; + Jid proxy; + int state; + bool sender; +}; + +FileTransfer::FileTransfer(FileTransferManager *m, QObject *parent) +:QObject(parent) +{ + d = new Private; + d->m = m; + d->ft = 0; + d->c = 0; + reset(); +} + +FileTransfer::~FileTransfer() +{ + reset(); + delete d; +} + +void FileTransfer::reset() +{ + d->m->unlink(this); + + delete d->ft; + d->ft = 0; + + delete d->c; + d->c = 0; + + d->state = Idle; + d->needStream = false; + d->sent = 0; + d->sender = false; +} + +void FileTransfer::setProxy(const Jid &proxy) +{ + d->proxy = proxy; +} + +void FileTransfer::sendFile(const Jid &to, const QString &fname, Q_LLONG size, const QString &desc) +{ + d->state = Requesting; + d->peer = to; + d->fname = fname; + d->size = size; + d->desc = desc; + d->sender = true; + d->id = d->m->link(this); + + d->ft = new JT_FT(d->m->client()->rootTask()); + connect(d->ft, SIGNAL(finished()), SLOT(ft_finished())); + QStringList list; + list += "http://jabber.org/protocol/bytestreams"; + d->ft->request(to, d->id, fname, size, desc, list); + d->ft->go(true); +} + +int FileTransfer::dataSizeNeeded() const +{ + int pending = d->c->bytesToWrite(); + if(pending >= SENDBUFSIZE) + return 0; + Q_LLONG left = d->length - (d->sent + pending); + int size = SENDBUFSIZE - pending; + if((Q_LLONG)size > left) + size = (int)left; + return size; +} + +void FileTransfer::writeFileData(const QByteArray &a) +{ + int pending = d->c->bytesToWrite(); + Q_LLONG left = d->length - (d->sent + pending); + if(left == 0) + return; + + QByteArray block; + if((Q_LLONG)a.size() > left) { + block = a.copy(); + block.resize((uint)left); + } + else + block = a; + d->c->write(block); +} + +Jid FileTransfer::peer() const +{ + return d->peer; +} + +QString FileTransfer::fileName() const +{ + return d->fname; +} + +Q_LLONG FileTransfer::fileSize() const +{ + return d->size; +} + +QString FileTransfer::description() const +{ + return d->desc; +} + +bool FileTransfer::rangeSupported() const +{ + return d->rangeSupported; +} + +Q_LLONG FileTransfer::offset() const +{ + return d->rangeOffset; +} + +Q_LLONG FileTransfer::length() const +{ + return d->length; +} + +void FileTransfer::accept(Q_LLONG offset, Q_LLONG length) +{ + d->state = Connecting; + d->rangeOffset = offset; + d->rangeLength = length; + if(length > 0) + d->length = length; + else + d->length = d->size; + d->streamType = "http://jabber.org/protocol/bytestreams"; + d->m->con_accept(this); +} + +void FileTransfer::close() +{ + if(d->state == Idle) + return; + if(d->state == WaitingForAccept) + d->m->con_reject(this); + else if(d->state == Active) + d->c->close(); + reset(); +} + +S5BConnection *FileTransfer::s5bConnection() const +{ + return d->c; +} + +void FileTransfer::ft_finished() +{ + JT_FT *ft = d->ft; + d->ft = 0; + + if(ft->success()) { + d->state = Connecting; + d->rangeOffset = ft->rangeOffset(); + d->length = ft->rangeLength(); + if(d->length == 0) + d->length = d->size - d->rangeOffset; + d->streamType = ft->streamType(); + d->c = d->m->client()->s5bManager()->createConnection(); + connect(d->c, SIGNAL(connected()), SLOT(s5b_connected())); + connect(d->c, SIGNAL(connectionClosed()), SLOT(s5b_connectionClosed())); + connect(d->c, SIGNAL(bytesWritten(int)), SLOT(s5b_bytesWritten(int))); + connect(d->c, SIGNAL(error(int)), SLOT(s5b_error(int))); + + if(d->proxy.isValid()) + d->c->setProxy(d->proxy); + d->c->connectToJid(d->peer, d->id); + accepted(); + } + else { + reset(); + if(ft->statusCode() == 403) + error(ErrReject); + else + error(ErrNeg); + } +} + +void FileTransfer::takeConnection(S5BConnection *c) +{ + d->c = c; + connect(d->c, SIGNAL(connected()), SLOT(s5b_connected())); + connect(d->c, SIGNAL(connectionClosed()), SLOT(s5b_connectionClosed())); + connect(d->c, SIGNAL(readyRead()), SLOT(s5b_readyRead())); + connect(d->c, SIGNAL(error(int)), SLOT(s5b_error(int))); + if(d->proxy.isValid()) + d->c->setProxy(d->proxy); + accepted(); + QTimer::singleShot(0, this, SLOT(doAccept())); +} + +void FileTransfer::s5b_connected() +{ + d->state = Active; + connected(); +} + +void FileTransfer::s5b_connectionClosed() +{ + reset(); + error(ErrStream); +} + +void FileTransfer::s5b_readyRead() +{ + QByteArray a = d->c->read(); + Q_LLONG need = d->length - d->sent; + if((Q_LLONG)a.size() > need) + a.resize((uint)need); + d->sent += a.size(); + if(d->sent == d->length) + reset(); + readyRead(a); +} + +void FileTransfer::s5b_bytesWritten(int x) +{ + d->sent += x; + if(d->sent == d->length) + reset(); + bytesWritten(x); +} + +void FileTransfer::s5b_error(int x) +{ + reset(); + if(x == S5BConnection::ErrRefused || x == S5BConnection::ErrConnect) + error(ErrConnect); + else if(x == S5BConnection::ErrProxy) + error(ErrProxy); + else + error(ErrStream); +} + +void FileTransfer::man_waitForAccept(const FTRequest &req) +{ + d->state = WaitingForAccept; + d->peer = req.from; + d->id = req.id; + d->iq_id = req.iq_id; + d->fname = req.fname; + d->size = req.size; + d->desc = req.desc; + d->rangeSupported = req.rangeSupported; +} + +void FileTransfer::doAccept() +{ + d->c->accept(); +} + +//---------------------------------------------------------------------------- +// FileTransferManager +//---------------------------------------------------------------------------- +class FileTransferManager::Private +{ +public: + Client *client; + QPtrList list, incoming; + JT_PushFT *pft; +}; + +FileTransferManager::FileTransferManager(Client *client) +:QObject(client) +{ + d = new Private; + d->client = client; + + d->pft = new JT_PushFT(d->client->rootTask()); + connect(d->pft, SIGNAL(incoming(const FTRequest &)), SLOT(pft_incoming(const FTRequest &))); +} + +FileTransferManager::~FileTransferManager() +{ + d->incoming.setAutoDelete(true); + d->incoming.clear(); + delete d->pft; + delete d; +} + +Client *FileTransferManager::client() const +{ + return d->client; +} + +FileTransfer *FileTransferManager::createTransfer() +{ + FileTransfer *ft = new FileTransfer(this); + return ft; +} + +FileTransfer *FileTransferManager::takeIncoming() +{ + if(d->incoming.isEmpty()) + return 0; + + FileTransfer *ft = d->incoming.getFirst(); + d->incoming.removeRef(ft); + + // move to active list + d->list.append(ft); + return ft; +} + +void FileTransferManager::pft_incoming(const FTRequest &req) +{ + bool found = false; + for(QStringList::ConstIterator it = req.streamTypes.begin(); it != req.streamTypes.end(); ++it) { + if((*it) == "http://jabber.org/protocol/bytestreams") { + found = true; + break; + } + } + if(!found) { + d->pft->respondError(req.from, req.iq_id, 400, "No valid stream types"); + return; + } + if(!d->client->s5bManager()->isAcceptableSID(req.from, req.id)) { + d->pft->respondError(req.from, req.iq_id, 400, "SID in use"); + return; + } + + FileTransfer *ft = new FileTransfer(this); + ft->man_waitForAccept(req); + d->incoming.append(ft); + incomingReady(); +} + +void FileTransferManager::s5b_incomingReady(S5BConnection *c) +{ + QPtrListIterator it(d->list); + FileTransfer *ft = 0; + for(FileTransfer *i; (i = it.current()); ++it) { + if(i->d->needStream && i->d->peer.compare(c->peer()) && i->d->id == c->sid()) { + ft = i; + break; + } + } + if(!ft) { + c->close(); + delete c; + return; + } + ft->takeConnection(c); +} + +QString FileTransferManager::link(FileTransfer *ft) +{ + d->list.append(ft); + return d->client->s5bManager()->genUniqueSID(ft->d->peer); +} + +void FileTransferManager::con_accept(FileTransfer *ft) +{ + ft->d->needStream = true; + d->pft->respondSuccess(ft->d->peer, ft->d->iq_id, ft->d->rangeOffset, ft->d->rangeLength, ft->d->streamType); +} + +void FileTransferManager::con_reject(FileTransfer *ft) +{ + d->pft->respondError(ft->d->peer, ft->d->iq_id, 403, "Declined"); +} + +void FileTransferManager::unlink(FileTransfer *ft) +{ + d->list.removeRef(ft); +} + +//---------------------------------------------------------------------------- +// JT_FT +//---------------------------------------------------------------------------- +class JT_FT::Private +{ +public: + QDomElement iq; + Jid to; + Q_LLONG size, rangeOffset, rangeLength; + QString streamType; + QStringList streamTypes; +}; + +JT_FT::JT_FT(Task *parent) +:Task(parent) +{ + d = new Private; +} + +JT_FT::~JT_FT() +{ + delete d; +} + +void JT_FT::request(const Jid &to, const QString &_id, const QString &fname, Q_LLONG size, const QString &desc, const QStringList &streamTypes) +{ + QDomElement iq; + d->to = to; + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement si = doc()->createElement("si"); + si.setAttribute("xmlns", "http://jabber.org/protocol/si"); + si.setAttribute("id", _id); + si.setAttribute("profile", "http://jabber.org/protocol/si/profile/file-transfer"); + + QDomElement file = doc()->createElement("file"); + file.setAttribute("xmlns", "http://jabber.org/protocol/si/profile/file-transfer"); + file.setAttribute("name", fname); + file.setAttribute("size", QString::number(size)); + if(!desc.isEmpty()) { + QDomElement de = doc()->createElement("desc"); + de.appendChild(doc()->createTextNode(desc)); + file.appendChild(de); + } + QDomElement range = doc()->createElement("range"); + file.appendChild(range); + si.appendChild(file); + + QDomElement feature = doc()->createElement("feature"); + feature.setAttribute("xmlns", "http://jabber.org/protocol/feature-neg"); + QDomElement x = doc()->createElement("x"); + x.setAttribute("xmlns", "jabber:x:data"); + x.setAttribute("type", "form"); + + QDomElement field = doc()->createElement("field"); + field.setAttribute("var", "stream-method"); + field.setAttribute("type", "list-single"); + for(QStringList::ConstIterator it = streamTypes.begin(); it != streamTypes.end(); ++it) { + QDomElement option = doc()->createElement("option"); + QDomElement value = doc()->createElement("value"); + value.appendChild(doc()->createTextNode(*it)); + option.appendChild(value); + field.appendChild(option); + } + + x.appendChild(field); + feature.appendChild(x); + + si.appendChild(feature); + iq.appendChild(si); + + d->streamTypes = streamTypes; + d->size = size; + d->iq = iq; +} + +Q_LLONG JT_FT::rangeOffset() const +{ + return d->rangeOffset; +} + +Q_LLONG JT_FT::rangeLength() const +{ + return d->rangeLength; +} + +QString JT_FT::streamType() const +{ + return d->streamType; +} + +void JT_FT::onGo() +{ + send(d->iq); +} + +bool JT_FT::take(const QDomElement &x) +{ + if(!iqVerify(x, d->to, id())) + return false; + + if(x.attribute("type") == "result") { + QDomElement si = firstChildElement(x); + if(si.attribute("xmlns") != "http://jabber.org/protocol/si" || si.tagName() != "si") { + setError(900, ""); + return true; + } + + QString id = si.attribute("id"); + + Q_LLONG range_offset = 0; + Q_LLONG range_length = 0; + + QDomElement file = si.elementsByTagName("file").item(0).toElement(); + if(!file.isNull()) { + QDomElement range = file.elementsByTagName("range").item(0).toElement(); + if(!range.isNull()) { + int x; + bool ok; + if(range.hasAttribute("offset")) { +#if QT_VERSION >= 0x030200 + x = range.attribute("offset").toLongLong(&ok); +#else + x = range.attribute("offset").toLong(&ok); +#endif + if(!ok || x < 0) { + setError(900, ""); + return true; + } + range_offset = x; + } + if(range.hasAttribute("length")) { +#if QT_VERSION >= 0x030200 + x = range.attribute("length").toLongLong(&ok); +#else + x = range.attribute("length").toLong(&ok); +#endif + if(!ok || x < 0) { + setError(900, ""); + return true; + } + range_length = x; + } + } + } + + if(range_offset > d->size || (range_length > (d->size - range_offset))) { + setError(900, ""); + return true; + } + + QString streamtype; + QDomElement feature = si.elementsByTagName("feature").item(0).toElement(); + if(!feature.isNull() && feature.attribute("xmlns") == "http://jabber.org/protocol/feature-neg") { + QDomElement x = feature.elementsByTagName("x").item(0).toElement(); + if(!x.isNull() && x.attribute("type") == "submit") { + QDomElement field = x.elementsByTagName("field").item(0).toElement(); + if(!field.isNull() && field.attribute("var") == "stream-method") { + QDomElement value = field.elementsByTagName("value").item(0).toElement(); + if(!value.isNull()) + streamtype = value.text(); + } + } + } + + // must be one of the offered streamtypes + bool found = false; + for(QStringList::ConstIterator it = d->streamTypes.begin(); it != d->streamTypes.end(); ++it) { + if((*it) == streamtype) { + found = true; + break; + } + } + if(!found) + return true; + + d->rangeOffset = range_offset; + d->rangeLength = range_length; + d->streamType = streamtype; + setSuccess(); + } + else { + setError(x); + } + + return true; +} + +//---------------------------------------------------------------------------- +// JT_PushFT +//---------------------------------------------------------------------------- +JT_PushFT::JT_PushFT(Task *parent) +:Task(parent) +{ +} + +JT_PushFT::~JT_PushFT() +{ +} + +void JT_PushFT::respondSuccess(const Jid &to, const QString &id, Q_LLONG rangeOffset, Q_LLONG rangeLength, const QString &streamType) +{ + QDomElement iq = createIQ(doc(), "result", to.full(), id); + QDomElement si = doc()->createElement("si"); + si.setAttribute("xmlns", "http://jabber.org/protocol/si"); + + if(rangeOffset != 0 || rangeLength != 0) { + QDomElement file = doc()->createElement("file"); + file.setAttribute("xmlns", "http://jabber.org/protocol/si/profile/file-transfer"); + QDomElement range = doc()->createElement("range"); + if(rangeOffset > 0) + range.setAttribute("offset", QString::number(rangeOffset)); + if(rangeLength > 0) + range.setAttribute("length", QString::number(rangeLength)); + file.appendChild(range); + si.appendChild(file); + } + + QDomElement feature = doc()->createElement("feature"); + feature.setAttribute("xmlns", "http://jabber.org/protocol/feature-neg"); + QDomElement x = doc()->createElement("x"); + x.setAttribute("xmlns", "jabber:x:data"); + x.setAttribute("type", "submit"); + + QDomElement field = doc()->createElement("field"); + field.setAttribute("var", "stream-method"); + QDomElement value = doc()->createElement("value"); + value.appendChild(doc()->createTextNode(streamType)); + field.appendChild(value); + + x.appendChild(field); + feature.appendChild(x); + + si.appendChild(feature); + iq.appendChild(si); + send(iq); +} + +void JT_PushFT::respondError(const Jid &to, const QString &id, int code, const QString &str) +{ + QDomElement iq = createIQ(doc(), "error", to.full(), id); + QDomElement err = textTag(doc(), "error", str); + err.setAttribute("code", QString::number(code)); + iq.appendChild(err); + send(iq); +} + +bool JT_PushFT::take(const QDomElement &e) +{ + // must be an iq-set tag + if(e.tagName() != "iq") + return false; + if(e.attribute("type") != "set") + return false; + + QDomElement si = firstChildElement(e); + if(si.attribute("xmlns") != "http://jabber.org/protocol/si" || si.tagName() != "si") + return false; + if(si.attribute("profile") != "http://jabber.org/protocol/si/profile/file-transfer") + return false; + + Jid from(e.attribute("from")); + QString id = si.attribute("id"); + + QDomElement file = si.elementsByTagName("file").item(0).toElement(); + if(file.isNull()) + return true; + + QString fname = file.attribute("name"); + if(fname.isEmpty()) { + respondError(from, id, 400, "Bad file name"); + return true; + } + + // ensure kosher + { + QFileInfo fi(fname); + fname = fi.fileName(); + } + + bool ok; +#if QT_VERSION >= 0x030200 + Q_LLONG size = file.attribute("size").toLongLong(&ok); +#else + Q_LLONG size = file.attribute("size").toLong(&ok); +#endif + if(!ok || size < 0) { + respondError(from, id, 400, "Bad file size"); + return true; + } + + QString desc; + QDomElement de = file.elementsByTagName("desc").item(0).toElement(); + if(!de.isNull()) + desc = de.text(); + + bool rangeSupported = false; + QDomElement range = file.elementsByTagName("range").item(0).toElement(); + if(!range.isNull()) + rangeSupported = true; + + QStringList streamTypes; + QDomElement feature = si.elementsByTagName("feature").item(0).toElement(); + if(!feature.isNull() && feature.attribute("xmlns") == "http://jabber.org/protocol/feature-neg") { + QDomElement x = feature.elementsByTagName("x").item(0).toElement(); + if(!x.isNull() /*&& x.attribute("type") == "form"*/) { + QDomElement field = x.elementsByTagName("field").item(0).toElement(); + if(!field.isNull() && field.attribute("var") == "stream-method" && field.attribute("type") == "list-single") { + QDomNodeList nl = field.elementsByTagName("option"); + for(uint n = 0; n < nl.count(); ++n) { + QDomElement e = nl.item(n).toElement(); + QDomElement value = e.elementsByTagName("value").item(0).toElement(); + if(!value.isNull()) + streamTypes += value.text(); + } + } + } + } + + FTRequest r; + r.from = from; + r.iq_id = e.attribute("id"); + r.id = id; + r.fname = fname; + r.size = size; + r.desc = desc; + r.rangeSupported = rangeSupported; + r.streamTypes = streamTypes; + + incoming(r); + return true; +} diff --git a/kopete/protocols/jabber/libiris/iris/jabber/filetransfer.h b/kopete/protocols/jabber/libiris/iris/jabber/filetransfer.h new file mode 100644 index 00000000..9ad4d403 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/filetransfer.h @@ -0,0 +1,170 @@ +/* + * filetransfer.h - File Transfer + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef XMPP_FILETRANSFER_H +#define XMPP_FILETRANSFER_H + +#include"im.h" + +#if QT_VERSION < 0x030200 +typedef long int Q_LLONG; +#endif + +namespace XMPP +{ + class S5BConnection; + struct FTRequest; + + class FileTransfer : public QObject + { + Q_OBJECT + public: + enum { ErrReject, ErrNeg, ErrConnect, ErrProxy, ErrStream }; + enum { Idle, Requesting, Connecting, WaitingForAccept, Active }; + ~FileTransfer(); + + void setProxy(const Jid &proxy); + + // send + void sendFile(const Jid &to, const QString &fname, Q_LLONG size, const QString &desc); + Q_LLONG offset() const; + Q_LLONG length() const; + int dataSizeNeeded() const; + void writeFileData(const QByteArray &a); + + // receive + Jid peer() const; + QString fileName() const; + Q_LLONG fileSize() const; + QString description() const; + bool rangeSupported() const; + void accept(Q_LLONG offset=0, Q_LLONG length=0); + + // both + void close(); // reject, or stop sending/receiving + S5BConnection *s5bConnection() const; // active link + + signals: + void accepted(); // indicates S5BConnection has started + void connected(); + void readyRead(const QByteArray &a); + void bytesWritten(int); + void error(int); + + private slots: + void ft_finished(); + void s5b_connected(); + void s5b_connectionClosed(); + void s5b_readyRead(); + void s5b_bytesWritten(int); + void s5b_error(int); + void doAccept(); + + private: + class Private; + Private *d; + + void reset(); + + friend class FileTransferManager; + FileTransfer(FileTransferManager *, QObject *parent=0); + void man_waitForAccept(const FTRequest &req); + void takeConnection(S5BConnection *c); + }; + + class FileTransferManager : public QObject + { + Q_OBJECT + public: + FileTransferManager(Client *); + ~FileTransferManager(); + + Client *client() const; + FileTransfer *createTransfer(); + FileTransfer *takeIncoming(); + + signals: + void incomingReady(); + + private slots: + void pft_incoming(const FTRequest &req); + + private: + class Private; + Private *d; + + friend class Client; + void s5b_incomingReady(S5BConnection *); + + friend class FileTransfer; + QString link(FileTransfer *); + void con_accept(FileTransfer *); + void con_reject(FileTransfer *); + void unlink(FileTransfer *); + }; + + class JT_FT : public Task + { + Q_OBJECT + public: + JT_FT(Task *parent); + ~JT_FT(); + + void request(const Jid &to, const QString &id, const QString &fname, Q_LLONG size, const QString &desc, const QStringList &streamTypes); + Q_LLONG rangeOffset() const; + Q_LLONG rangeLength() const; + QString streamType() const; + + void onGo(); + bool take(const QDomElement &); + + private: + class Private; + Private *d; + }; + + struct FTRequest + { + Jid from; + QString iq_id, id; + QString fname; + Q_LLONG size; + QString desc; + bool rangeSupported; + QStringList streamTypes; + }; + class JT_PushFT : public Task + { + Q_OBJECT + public: + JT_PushFT(Task *parent); + ~JT_PushFT(); + + void respondSuccess(const Jid &to, const QString &id, Q_LLONG rangeOffset, Q_LLONG rangeLength, const QString &streamType); + void respondError(const Jid &to, const QString &id, int code, const QString &str); + + bool take(const QDomElement &); + + signals: + void incoming(const FTRequest &req); + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/jabber/s5b.cpp b/kopete/protocols/jabber/libiris/iris/jabber/s5b.cpp new file mode 100644 index 00000000..b4b9be44 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/s5b.cpp @@ -0,0 +1,2538 @@ +/* + * s5b.cpp - direct connection protocol via tcp + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include + +#include"s5b.h" + +#include +#include +#include +#include +#include"xmpp_xmlcommon.h" +#include"hash.h" +#include"socks.h" +#include"safedelete.h" + +#ifdef Q_OS_WIN +# include +#else +# ifdef HAVE_SYS_TYPES_H +# include +# endif +# include +#endif + +#define MAXSTREAMHOSTS 5 + +//#define S5B_DEBUG + +namespace XMPP { + +static QString makeKey(const QString &sid, const Jid &initiator, const Jid &target) +{ + QString str = sid + initiator.full() + target.full(); + return QCA::SHA1::hashToString(str.utf8()); +} + +static bool haveHost(const StreamHostList &list, const Jid &j) +{ + for(StreamHostList::ConstIterator it = list.begin(); it != list.end(); ++it) { + if((*it).jid().compare(j)) + return true; + } + return false; +} + +class S5BManager::Item : public QObject +{ + Q_OBJECT +public: + enum { Idle, Initiator, Target, Active }; + enum { ErrRefused, ErrConnect, ErrWrongHost, ErrProxy }; + enum { Unknown, Fast, NotFast }; + S5BManager *m; + int state; + QString sid, key, out_key, out_id, in_id; + Jid self, peer; + StreamHostList in_hosts; + JT_S5B *task, *proxy_task; + SocksClient *client, *client_out; + SocksUDP *client_udp, *client_out_udp; + S5BConnector *conn, *proxy_conn; + bool wantFast; + StreamHost proxy; + int targetMode; // initiator sets this once it figures it out + bool fast; // target sets this + bool activated; + bool lateProxy; + bool connSuccess; + bool localFailed, remoteFailed; + bool allowIncoming; + bool udp; + int statusCode; + Jid activatedStream; + + Item(S5BManager *manager); + ~Item(); + + void reset(); + void startInitiator(const QString &_sid, const Jid &_self, const Jid &_peer, bool fast, bool udp); + void startTarget(const QString &_sid, const Jid &_self, const Jid &_peer, const StreamHostList &hosts, const QString &iq_id, bool fast, bool udp); + void handleFast(const StreamHostList &hosts, const QString &iq_id); + + void doOutgoing(); + void doIncoming(); + void setIncomingClient(SocksClient *sc); + void incomingActivate(const Jid &streamHost); + +signals: + void accepted(); + void tryingHosts(const StreamHostList &list); + void proxyConnect(); + void waitingForActivation(); + void connected(); + void error(int); + +private slots: + void jt_finished(); + void conn_result(bool b); + void proxy_result(bool b); + void proxy_finished(); + void sc_readyRead(); + void sc_bytesWritten(int); + void sc_error(int); + +private: + void doConnectError(); + void tryActivation(); + void checkForActivation(); + void checkFailure(); + void finished(); +}; + +//---------------------------------------------------------------------------- +// S5BDatagram +//---------------------------------------------------------------------------- +S5BDatagram::S5BDatagram() +{ + _source = 0; + _dest = 0; +} + +S5BDatagram::S5BDatagram(int source, int dest, const QByteArray &data) +{ + _source = source; + _dest = dest; + _buf = data; +} + +int S5BDatagram::sourcePort() const +{ + return _source; +} + +int S5BDatagram::destPort() const +{ + return _dest; +} + +QByteArray S5BDatagram::data() const +{ + return _buf; +} + +//---------------------------------------------------------------------------- +// S5BConnection +//---------------------------------------------------------------------------- +class S5BConnection::Private +{ +public: + S5BManager *m; + SocksClient *sc; + SocksUDP *su; + int state; + Jid peer; + QString sid; + bool remote; + bool switched; + bool notifyRead, notifyClose; + int id; + S5BRequest req; + Jid proxy; + Mode mode; + QPtrList dglist; +}; + +static int id_conn = 0; +static int num_conn = 0; + +S5BConnection::S5BConnection(S5BManager *m, QObject *parent) +:ByteStream(parent) +{ + d = new Private; + d->m = m; + d->sc = 0; + d->su = 0; + + ++num_conn; + d->id = id_conn++; +#ifdef S5B_DEBUG + printf("S5BConnection[%d]: constructing, count=%d, %p\n", d->id, num_conn, this); +#endif + + reset(); +} + +S5BConnection::~S5BConnection() +{ + reset(true); + + --num_conn; +#ifdef S5B_DEBUG + printf("S5BConnection[%d]: destructing, count=%d\n", d->id, num_conn); +#endif + + delete d; +} + +void S5BConnection::reset(bool clear) +{ + d->m->con_unlink(this); + if(clear && d->sc) { + delete d->sc; + d->sc = 0; + } + delete d->su; + d->su = 0; + if(clear) { + d->dglist.setAutoDelete(true); + d->dglist.clear(); + d->dglist.setAutoDelete(false); + } + d->state = Idle; + d->peer = Jid(); + d->sid = QString(); + d->remote = false; + d->switched = false; + d->notifyRead = false; + d->notifyClose = false; +} + +Jid S5BConnection::proxy() const +{ + return d->proxy; +} + +void S5BConnection::setProxy(const Jid &proxy) +{ + d->proxy = proxy; +} + +void S5BConnection::connectToJid(const Jid &peer, const QString &sid, Mode m) +{ + reset(true); + if(!d->m->isAcceptableSID(peer, sid)) + return; + + d->peer = peer; + d->sid = sid; + d->state = Requesting; + d->mode = m; +#ifdef S5B_DEBUG + printf("S5BConnection[%d]: connecting %s [%s]\n", d->id, d->peer.full().latin1(), d->sid.latin1()); +#endif + d->m->con_connect(this); +} + +void S5BConnection::accept() +{ + if(d->state != WaitingForAccept) + return; + + d->state = Connecting; +#ifdef S5B_DEBUG + printf("S5BConnection[%d]: accepting %s [%s]\n", d->id, d->peer.full().latin1(), d->sid.latin1()); +#endif + d->m->con_accept(this); +} + +void S5BConnection::close() +{ + if(d->state == Idle) + return; + + if(d->state == WaitingForAccept) + d->m->con_reject(this); + else if(d->state == Active) + d->sc->close(); +#ifdef S5B_DEBUG + printf("S5BConnection[%d]: closing %s [%s]\n", d->id, d->peer.full().latin1(), d->sid.latin1()); +#endif + reset(); +} + +Jid S5BConnection::peer() const +{ + return d->peer; +} + +QString S5BConnection::sid() const +{ + return d->sid; +} + +bool S5BConnection::isRemote() const +{ + return d->remote; +} + +S5BConnection::Mode S5BConnection::mode() const +{ + return d->mode; +} + +int S5BConnection::state() const +{ + return d->state; +} + +bool S5BConnection::isOpen() const +{ + if(d->state == Active) + return true; + else + return false; +} + +void S5BConnection::write(const QByteArray &buf) +{ + if(d->state == Active && d->mode == Stream) + d->sc->write(buf); +} + +QByteArray S5BConnection::read(int bytes) +{ + if(d->sc) + return d->sc->read(bytes); + else + return QByteArray(); +} + +int S5BConnection::bytesAvailable() const +{ + if(d->sc) + return d->sc->bytesAvailable(); + else + return 0; +} + +int S5BConnection::bytesToWrite() const +{ + if(d->state == Active) + return d->sc->bytesToWrite(); + else + return 0; +} + +void S5BConnection::writeDatagram(const S5BDatagram &i) +{ + QByteArray buf(i.data().size() + 4); + ushort ssp = htons(i.sourcePort()); + ushort sdp = htons(i.destPort()); + QByteArray data = i.data(); + memcpy(buf.data(), &ssp, 2); + memcpy(buf.data() + 2, &sdp, 2); + memcpy(buf.data() + 4, data.data(), data.size()); + sendUDP(buf); +} + +S5BDatagram S5BConnection::readDatagram() +{ + if(d->dglist.isEmpty()) + return S5BDatagram(); + S5BDatagram *i = d->dglist.getFirst(); + d->dglist.removeRef(i); + S5BDatagram val = *i; + delete i; + return val; +} + +int S5BConnection::datagramsAvailable() const +{ + return d->dglist.count(); +} + +void S5BConnection::man_waitForAccept(const S5BRequest &r) +{ + d->state = WaitingForAccept; + d->remote = true; + d->req = r; + d->peer = r.from; + d->sid = r.sid; + d->mode = r.udp ? Datagram : Stream; +} + +void S5BConnection::man_clientReady(SocksClient *sc, SocksUDP *sc_udp) +{ + d->sc = sc; + connect(d->sc, SIGNAL(connectionClosed()), SLOT(sc_connectionClosed())); + connect(d->sc, SIGNAL(delayedCloseFinished()), SLOT(sc_delayedCloseFinished())); + connect(d->sc, SIGNAL(readyRead()), SLOT(sc_readyRead())); + connect(d->sc, SIGNAL(bytesWritten(int)), SLOT(sc_bytesWritten(int))); + connect(d->sc, SIGNAL(error(int)), SLOT(sc_error(int))); + + if(sc_udp) { + d->su = sc_udp; + connect(d->su, SIGNAL(packetReady(const QByteArray &)), SLOT(su_packetReady(const QByteArray &))); + } + + d->state = Active; +#ifdef S5B_DEBUG + printf("S5BConnection[%d]: %s [%s] <<< success >>>\n", d->id, d->peer.full().latin1(), d->sid.latin1()); +#endif + + // bytes already in the stream? + if(d->sc->bytesAvailable()) { +#ifdef S5B_DEBUG + printf("Stream has %d bytes in it.\n", d->sc->bytesAvailable()); +#endif + d->notifyRead = true; + } + // closed before it got here? + if(!d->sc->isOpen()) { +#ifdef S5B_DEBUG + printf("Stream was closed before S5B request finished?\n"); +#endif + d->notifyClose = true; + } + if(d->notifyRead || d->notifyClose) + QTimer::singleShot(0, this, SLOT(doPending())); + connected(); +} + +void S5BConnection::doPending() +{ + if(d->notifyRead) { + if(d->notifyClose) + QTimer::singleShot(0, this, SLOT(doPending())); + sc_readyRead(); + } + else if(d->notifyClose) + sc_connectionClosed(); +} + +void S5BConnection::man_udpReady(const QByteArray &buf) +{ + handleUDP(buf); +} + +void S5BConnection::man_failed(int x) +{ + reset(true); + if(x == S5BManager::Item::ErrRefused) + error(ErrRefused); + if(x == S5BManager::Item::ErrConnect) + error(ErrConnect); + if(x == S5BManager::Item::ErrWrongHost) + error(ErrConnect); + if(x == S5BManager::Item::ErrProxy) + error(ErrProxy); +} + +void S5BConnection::sc_connectionClosed() +{ + // if we have a pending read notification, postpone close + if(d->notifyRead) { +#ifdef S5B_DEBUG + printf("closed while pending read\n"); +#endif + d->notifyClose = true; + return; + } + d->notifyClose = false; + reset(); + connectionClosed(); +} + +void S5BConnection::sc_delayedCloseFinished() +{ + // echo + delayedCloseFinished(); +} + +void S5BConnection::sc_readyRead() +{ + if(d->mode == Datagram) { + // throw the data away + d->sc->read(); + return; + } + + d->notifyRead = false; + // echo + readyRead(); +} + +void S5BConnection::sc_bytesWritten(int x) +{ + // echo + bytesWritten(x); +} + +void S5BConnection::sc_error(int) +{ + reset(); + error(ErrSocket); +} + +void S5BConnection::su_packetReady(const QByteArray &buf) +{ + handleUDP(buf); +} + +void S5BConnection::handleUDP(const QByteArray &buf) +{ + // must be at least 4 bytes, to accomodate virtual ports + if(buf.size() < 4) + return; // drop + + ushort ssp, sdp; + memcpy(&ssp, buf.data(), 2); + memcpy(&sdp, buf.data() + 2, 2); + int source = ntohs(ssp); + int dest = ntohs(sdp); + QByteArray data(buf.size() - 4); + memcpy(data.data(), buf.data() + 4, data.size()); + d->dglist.append(new S5BDatagram(source, dest, data)); + + datagramReady(); +} + +void S5BConnection::sendUDP(const QByteArray &buf) +{ + if(d->su) + d->su->write(buf); + else + d->m->con_sendUDP(this, buf); +} + +//---------------------------------------------------------------------------- +// S5BManager +//---------------------------------------------------------------------------- +class S5BManager::Entry +{ +public: + Entry() + { + i = 0; + query = 0; + udp_init = false; + } + + ~Entry() + { + delete query; + } + + S5BConnection *c; + Item *i; + QString sid; + JT_S5B *query; + StreamHost proxyInfo; + QGuardedPtr relatedServer; + + bool udp_init; + QHostAddress udp_addr; + int udp_port; +}; + +class S5BManager::Private +{ +public: + Client *client; + S5BServer *serv; + QPtrList activeList; + S5BConnectionList incomingConns; + JT_PushS5B *ps; +}; + +S5BManager::S5BManager(Client *parent) +:QObject(parent) +{ + // S5B needs SHA1 + if(!QCA::isSupported(QCA::CAP_SHA1)) + QCA::insertProvider(createProviderHash()); + + d = new Private; + d->client = parent; + d->serv = 0; + d->activeList.setAutoDelete(true); + + d->ps = new JT_PushS5B(d->client->rootTask()); + connect(d->ps, SIGNAL(incoming(const S5BRequest &)), SLOT(ps_incoming(const S5BRequest &))); + connect(d->ps, SIGNAL(incomingUDPSuccess(const Jid &, const QString &)), SLOT(ps_incomingUDPSuccess(const Jid &, const QString &))); + connect(d->ps, SIGNAL(incomingActivate(const Jid &, const QString &, const Jid &)), SLOT(ps_incomingActivate(const Jid &, const QString &, const Jid &))); +} + +S5BManager::~S5BManager() +{ + setServer(0); + d->incomingConns.setAutoDelete(true); + d->incomingConns.clear(); + delete d->ps; + delete d; +} + +Client *S5BManager::client() const +{ + return d->client; +} + +S5BServer *S5BManager::server() const +{ + return d->serv; +} + +void S5BManager::setServer(S5BServer *serv) +{ + if(d->serv) { + d->serv->unlink(this); + d->serv = 0; + } + + if(serv) { + d->serv = serv; + d->serv->link(this); + } +} + +S5BConnection *S5BManager::createConnection() +{ + S5BConnection *c = new S5BConnection(this); + return c; +} + +S5BConnection *S5BManager::takeIncoming() +{ + if(d->incomingConns.isEmpty()) + return 0; + + S5BConnection *c = d->incomingConns.getFirst(); + d->incomingConns.removeRef(c); + + // move to activeList + Entry *e = new Entry; + e->c = c; + e->sid = c->d->sid; + d->activeList.append(e); + + return c; +} + +void S5BManager::ps_incoming(const S5BRequest &req) +{ +#ifdef S5B_DEBUG + printf("S5BManager: incoming from %s\n", req.from.full().latin1()); +#endif + + bool ok = false; + // ensure we don't already have an incoming connection from this peer+sid + S5BConnection *c = findIncoming(req.from, req.sid); + if(!c) { + // do we have an active entry with this sid already? + Entry *e = findEntryBySID(req.from, req.sid); + if(e) { + if(e->i) { + // loopback + if(req.from.compare(d->client->jid()) && (req.id == e->i->out_id)) { +#ifdef S5B_DEBUG + printf("ALLOWED: loopback\n"); +#endif + ok = true; + } + // allowed by 'fast mode' + else if(e->i->state == Item::Initiator && e->i->targetMode == Item::Unknown) { +#ifdef S5B_DEBUG + printf("ALLOWED: fast-mode\n"); +#endif + e->i->handleFast(req.hosts, req.id); + return; + } + } + } + else { +#ifdef S5B_DEBUG + printf("ALLOWED: we don't have it\n"); +#endif + ok = true; + } + } + if(!ok) { + d->ps->respondError(req.from, req.id, 406, "SID in use"); + return; + } + + // create an incoming connection + c = new S5BConnection(this); + c->man_waitForAccept(req); + d->incomingConns.append(c); + incomingReady(); +} + +void S5BManager::ps_incomingUDPSuccess(const Jid &from, const QString &key) +{ + Entry *e = findEntryByHash(key); + if(e && e->i) { + if(e->i->conn) + e->i->conn->man_udpSuccess(from); + else if(e->i->proxy_conn) + e->i->proxy_conn->man_udpSuccess(from); + } +} + +void S5BManager::ps_incomingActivate(const Jid &from, const QString &sid, const Jid &streamHost) +{ + Entry *e = findEntryBySID(from, sid); + if(e && e->i) + e->i->incomingActivate(streamHost); +} + +void S5BManager::doSuccess(const Jid &peer, const QString &id, const Jid &streamHost) +{ + d->ps->respondSuccess(peer, id, streamHost); +} + +void S5BManager::doError(const Jid &peer, const QString &id, int code, const QString &str) +{ + d->ps->respondError(peer, id, code, str); +} + +void S5BManager::doActivate(const Jid &peer, const QString &sid, const Jid &streamHost) +{ + d->ps->sendActivate(peer, sid, streamHost); +} + +QString S5BManager::genUniqueSID(const Jid &peer) const +{ + // get unused key + QString sid; + do { + sid = "s5b_"; + for(int i = 0; i < 4; ++i) { + int word = rand() & 0xffff; + for(int n = 0; n < 4; ++n) { + QString s; + s.sprintf("%x", (word >> (n * 4)) & 0xf); + sid.append(s); + } + } + } while(!isAcceptableSID(peer, sid)); + return sid; +} + +bool S5BManager::isAcceptableSID(const Jid &peer, const QString &sid) const +{ + QString key = makeKey(sid, d->client->jid(), peer); + QString key_out = makeKey(sid, peer, d->client->jid()); + + // if we have a server, then check through it + if(d->serv) { + if(findServerEntryByHash(key) || findServerEntryByHash(key_out)) + return false; + } + else { + if(findEntryByHash(key) || findEntryByHash(key_out)) + return false; + } + return true; +} + +S5BConnection *S5BManager::findIncoming(const Jid &from, const QString &sid) const +{ + QPtrListIterator it(d->incomingConns); + for(S5BConnection *c; (c = it.current()); ++it) { + if(c->d->peer.compare(from) && c->d->sid == sid) + return c; + } + return 0; +} + +S5BManager::Entry *S5BManager::findEntry(S5BConnection *c) const +{ + QPtrListIterator it(d->activeList); + for(Entry *e; (e = it.current()); ++it) { + if(e->c == c) + return e; + } + return 0; +} + +S5BManager::Entry *S5BManager::findEntry(Item *i) const +{ + QPtrListIterator it(d->activeList); + for(Entry *e; (e = it.current()); ++it) { + if(e->i == i) + return e; + } + return 0; +} + +S5BManager::Entry *S5BManager::findEntryByHash(const QString &key) const +{ + QPtrListIterator it(d->activeList); + for(Entry *e; (e = it.current()); ++it) { + if(e->i && e->i->key == key) + return e; + } + return 0; +} + +S5BManager::Entry *S5BManager::findEntryBySID(const Jid &peer, const QString &sid) const +{ + QPtrListIterator it(d->activeList); + for(Entry *e; (e = it.current()); ++it) { + if(e->i && e->i->peer.compare(peer) && e->sid == sid) + return e; + } + return 0; +} + +S5BManager::Entry *S5BManager::findServerEntryByHash(const QString &key) const +{ + const QPtrList &manList = d->serv->managerList(); + QPtrListIterator it(manList); + for(S5BManager *m; (m = it.current()); ++it) { + Entry *e = m->findEntryByHash(key); + if(e) + return e; + } + return 0; +} + +bool S5BManager::srv_ownsHash(const QString &key) const +{ + if(findEntryByHash(key)) + return true; + return false; +} + +void S5BManager::srv_incomingReady(SocksClient *sc, const QString &key) +{ + Entry *e = findEntryByHash(key); + if(!e->i->allowIncoming) { + sc->requestDeny(); + SafeDelete::deleteSingle(sc); + return; + } + if(e->c->d->mode == S5BConnection::Datagram) + sc->grantUDPAssociate("", 0); + else + sc->grantConnect(); + e->relatedServer = (S5BServer *)sender(); + e->i->setIncomingClient(sc); +} + +void S5BManager::srv_incomingUDP(bool init, const QHostAddress &addr, int port, const QString &key, const QByteArray &data) +{ + Entry *e = findEntryByHash(key); + if(!e->c->d->mode != S5BConnection::Datagram) + return; // this key isn't in udp mode? drop! + + if(init) { + if(e->udp_init) + return; // only init once + + // lock on to this sender + e->udp_addr = addr; + e->udp_port = port; + e->udp_init = true; + + // reply that initialization was successful + d->ps->sendUDPSuccess(e->c->d->peer, key); + return; + } + + // not initialized yet? something went wrong + if(!e->udp_init) + return; + + // must come from same source as when initialized + if(addr.toString() != e->udp_addr.toString() || port != e->udp_port) + return; + + e->c->man_udpReady(data); +} + +void S5BManager::srv_unlink() +{ + d->serv = 0; +} + +void S5BManager::con_connect(S5BConnection *c) +{ + if(findEntry(c)) + return; + Entry *e = new Entry; + e->c = c; + e->sid = c->d->sid; + d->activeList.append(e); + + if(c->d->proxy.isValid()) { + queryProxy(e); + return; + } + entryContinue(e); +} + +void S5BManager::con_accept(S5BConnection *c) +{ + Entry *e = findEntry(c); + if(!e) + return; + + if(e->c->d->req.fast) { + if(targetShouldOfferProxy(e)) { + queryProxy(e); + return; + } + } + entryContinue(e); +} + +void S5BManager::con_reject(S5BConnection *c) +{ + d->ps->respondError(c->d->peer, c->d->req.id, 406, "Not acceptable"); +} + +void S5BManager::con_unlink(S5BConnection *c) +{ + Entry *e = findEntry(c); + if(!e) + return; + + // active incoming request? cancel it + if(e->i && e->i->conn) + d->ps->respondError(e->i->peer, e->i->out_id, 406, "Not acceptable"); + delete e->i; + d->activeList.removeRef(e); +} + +void S5BManager::con_sendUDP(S5BConnection *c, const QByteArray &buf) +{ + Entry *e = findEntry(c); + if(!e) + return; + if(!e->udp_init) + return; + + if(e->relatedServer) + e->relatedServer->writeUDP(e->udp_addr, e->udp_port, buf); +} + +void S5BManager::item_accepted() +{ + Item *i = (Item *)sender(); + Entry *e = findEntry(i); + + e->c->accepted(); // signal +} + +void S5BManager::item_tryingHosts(const StreamHostList &list) +{ + Item *i = (Item *)sender(); + Entry *e = findEntry(i); + + e->c->tryingHosts(list); // signal +} + +void S5BManager::item_proxyConnect() +{ + Item *i = (Item *)sender(); + Entry *e = findEntry(i); + + e->c->proxyConnect(); // signal +} + +void S5BManager::item_waitingForActivation() +{ + Item *i = (Item *)sender(); + Entry *e = findEntry(i); + + e->c->waitingForActivation(); // signal +} + +void S5BManager::item_connected() +{ + Item *i = (Item *)sender(); + Entry *e = findEntry(i); + + // grab the client + SocksClient *client = i->client; + i->client = 0; + SocksUDP *client_udp = i->client_udp; + i->client_udp = 0; + + // give it to the connection + e->c->man_clientReady(client, client_udp); +} + +void S5BManager::item_error(int x) +{ + Item *i = (Item *)sender(); + Entry *e = findEntry(i); + + e->c->man_failed(x); +} + +void S5BManager::entryContinue(Entry *e) +{ + e->i = new Item(this); + e->i->proxy = e->proxyInfo; + + connect(e->i, SIGNAL(accepted()), SLOT(item_accepted())); + connect(e->i, SIGNAL(tryingHosts(const StreamHostList &)), SLOT(item_tryingHosts(const StreamHostList &))); + connect(e->i, SIGNAL(proxyConnect()), SLOT(item_proxyConnect())); + connect(e->i, SIGNAL(waitingForActivation()), SLOT(item_waitingForActivation())); + connect(e->i, SIGNAL(connected()), SLOT(item_connected())); + connect(e->i, SIGNAL(error(int)), SLOT(item_error(int))); + + if(e->c->isRemote()) { + const S5BRequest &req = e->c->d->req; + e->i->startTarget(e->sid, d->client->jid(), e->c->d->peer, req.hosts, req.id, req.fast, req.udp); + } + else { + e->i->startInitiator(e->sid, d->client->jid(), e->c->d->peer, true, e->c->d->mode == S5BConnection::Datagram ? true: false); + e->c->requesting(); // signal + } +} + +void S5BManager::queryProxy(Entry *e) +{ + QGuardedPtr self = this; + e->c->proxyQuery(); // signal + if(!self) + return; + +#ifdef S5B_DEBUG + printf("querying proxy: [%s]\n", e->c->d->proxy.full().latin1()); +#endif + e->query = new JT_S5B(d->client->rootTask()); + connect(e->query, SIGNAL(finished()), SLOT(query_finished())); + e->query->requestProxyInfo(e->c->d->proxy); + e->query->go(true); +} + +void S5BManager::query_finished() +{ + JT_S5B *query = (JT_S5B *)sender(); + Entry *e; + bool found = false; + QPtrListIterator it(d->activeList); + for(; (e = it.current()); ++it) { + if(e->query == query) { + found = true; + break; + } + } + if(!found) + return; + e->query = 0; + +#ifdef S5B_DEBUG + printf("query finished: "); +#endif + if(query->success()) { + e->proxyInfo = query->proxyInfo(); +#ifdef S5B_DEBUG + printf("host/ip=[%s] port=[%d]\n", e->proxyInfo.host().latin1(), e->proxyInfo.port()); +#endif + } + else { +#ifdef S5B_DEBUG + printf("fail\n"); +#endif + } + + QGuardedPtr self = this; + e->c->proxyResult(query->success()); // signal + if(!self) + return; + + entryContinue(e); +} + +bool S5BManager::targetShouldOfferProxy(Entry *e) +{ + if(!e->c->d->proxy.isValid()) + return false; + + // if target, don't offer any proxy if the initiator already did + const StreamHostList &hosts = e->c->d->req.hosts; + for(StreamHostList::ConstIterator it = hosts.begin(); it != hosts.end(); ++it) { + if((*it).isProxy()) + return false; + } + + // ensure we don't offer the same proxy as the initiator + if(haveHost(hosts, e->c->d->proxy)) + return false; + + return true; +} + +//---------------------------------------------------------------------------- +// S5BManager::Item +//---------------------------------------------------------------------------- +S5BManager::Item::Item(S5BManager *manager) : QObject(0) +{ + m = manager; + task = 0; + proxy_task = 0; + conn = 0; + proxy_conn = 0; + client_udp = 0; + client = 0; + client_out_udp = 0; + client_out = 0; + reset(); +} + +S5BManager::Item::~Item() +{ + reset(); +} + +void S5BManager::Item::reset() +{ + delete task; + task = 0; + + delete proxy_task; + proxy_task = 0; + + delete conn; + conn = 0; + + delete proxy_conn; + proxy_conn = 0; + + delete client_udp; + client_udp = 0; + + delete client; + client = 0; + + delete client_out_udp; + client_out_udp = 0; + + delete client_out; + client_out = 0; + + state = Idle; + wantFast = false; + targetMode = Unknown; + fast = false; + activated = false; + lateProxy = false; + connSuccess = false; + localFailed = false; + remoteFailed = false; + allowIncoming = false; + udp = false; +} + +void S5BManager::Item::startInitiator(const QString &_sid, const Jid &_self, const Jid &_peer, bool fast, bool _udp) +{ + sid = _sid; + self = _self; + peer = _peer; + key = makeKey(sid, self, peer); + out_key = makeKey(sid, peer, self); + wantFast = fast; + udp = _udp; + +#ifdef S5B_DEBUG + printf("S5BManager::Item initiating request %s [%s]\n", peer.full().latin1(), sid.latin1()); +#endif + state = Initiator; + doOutgoing(); +} + +void S5BManager::Item::startTarget(const QString &_sid, const Jid &_self, const Jid &_peer, const StreamHostList &hosts, const QString &iq_id, bool _fast, bool _udp) +{ + sid = _sid; + peer = _peer; + self = _self; + in_hosts = hosts; + in_id = iq_id; + fast = _fast; + key = makeKey(sid, self, peer); + out_key = makeKey(sid, peer, self); + udp = _udp; + +#ifdef S5B_DEBUG + printf("S5BManager::Item incoming request %s [%s]\n", peer.full().latin1(), sid.latin1()); +#endif + state = Target; + if(fast) + doOutgoing(); + doIncoming(); +} + +void S5BManager::Item::handleFast(const StreamHostList &hosts, const QString &iq_id) +{ + targetMode = Fast; + + QGuardedPtr self = this; + accepted(); + if(!self) + return; + + // if we already have a stream, then bounce this request + if(client) { + m->doError(peer, iq_id, 406, "Not acceptable"); + } + else { + in_hosts = hosts; + in_id = iq_id; + doIncoming(); + } +} + +void S5BManager::Item::doOutgoing() +{ + StreamHostList hosts; + S5BServer *serv = m->server(); + if(serv && serv->isActive() && !haveHost(in_hosts, m->client()->jid())) { + QStringList hostList = serv->hostList(); + for(QStringList::ConstIterator it = hostList.begin(); it != hostList.end(); ++it) { + StreamHost h; + h.setJid(m->client()->jid()); + h.setHost(*it); + h.setPort(serv->port()); + hosts += h; + } + } + + // if the proxy is valid, then it's ok to add (the manager already ensured that it doesn't conflict) + if(proxy.jid().isValid()) + hosts += proxy; + + // if we're the target and we have no streamhosts of our own, then don't even bother with fast-mode + if(state == Target && hosts.isEmpty()) { + fast = false; + return; + } + + allowIncoming = true; + + task = new JT_S5B(m->client()->rootTask()); + connect(task, SIGNAL(finished()), SLOT(jt_finished())); + task->request(peer, sid, hosts, state == Initiator ? wantFast : false, udp); + out_id = task->id(); + task->go(true); +} + +void S5BManager::Item::doIncoming() +{ + if(in_hosts.isEmpty()) { + doConnectError(); + return; + } + + StreamHostList list; + if(lateProxy) { + // take just the proxy streamhosts + for(StreamHostList::ConstIterator it = in_hosts.begin(); it != in_hosts.end(); ++it) { + if((*it).isProxy()) + list += *it; + } + lateProxy = false; + } + else { + // only try doing the late proxy trick if using fast mode AND we did not offer a proxy + if((state == Initiator || (state == Target && fast)) && !proxy.jid().isValid()) { + // take just the non-proxy streamhosts + bool hasProxies = false; + for(StreamHostList::ConstIterator it = in_hosts.begin(); it != in_hosts.end(); ++it) { + if((*it).isProxy()) + hasProxies = true; + else + list += *it; + } + if(hasProxies) { + lateProxy = true; + + // no regular streamhosts? wait for remote error + if(list.isEmpty()) + return; + } + } + else + list = in_hosts; + } + + conn = new S5BConnector; + connect(conn, SIGNAL(result(bool)), SLOT(conn_result(bool))); + + QGuardedPtr self = this; + tryingHosts(list); + if(!self) + return; + + conn->start(m->client()->jid(), list, out_key, udp, lateProxy ? 10 : 30); +} + +void S5BManager::Item::setIncomingClient(SocksClient *sc) +{ +#ifdef S5B_DEBUG + printf("S5BManager::Item: %s [%s] successful incoming connection\n", peer.full().latin1(), sid.latin1()); +#endif + + connect(sc, SIGNAL(readyRead()), SLOT(sc_readyRead())); + connect(sc, SIGNAL(bytesWritten(int)), SLOT(sc_bytesWritten(int))); + connect(sc, SIGNAL(error(int)), SLOT(sc_error(int))); + + client = sc; + allowIncoming = false; +} + +void S5BManager::Item::incomingActivate(const Jid &streamHost) +{ + if(!activated) { + activatedStream = streamHost; + checkForActivation(); + } +} + +void S5BManager::Item::jt_finished() +{ + JT_S5B *j = task; + task = 0; + +#ifdef S5B_DEBUG + printf("jt_finished: state=%s, %s\n", state == Initiator ? "initiator" : "target", j->success() ? "ok" : "fail"); +#endif + + if(state == Initiator) { + if(targetMode == Unknown) { + targetMode = NotFast; + QGuardedPtr self = this; + accepted(); + if(!self) + return; + } + } + + // if we've already reported successfully connecting to them, then this response doesn't matter + if(state == Initiator && connSuccess) { + tryActivation(); + return; + } + + if(j->success()) { + // stop connecting out + if(conn || lateProxy) { + delete conn; + conn = 0; + doConnectError(); + } + + Jid streamHost = j->streamHostUsed(); + + // they connected to us? + if(streamHost.compare(self)) { + if(client) { + if(state == Initiator) { + activatedStream = streamHost; + tryActivation(); + } + else + checkForActivation(); + } + else { +#ifdef S5B_DEBUG + printf("S5BManager::Item %s claims to have connected to us, but we don't see this\n", peer.full().latin1()); +#endif + reset(); + error(ErrWrongHost); + } + } + else if(streamHost.compare(proxy.jid())) { + // toss out any direct incoming, since it won't be used + delete client; + client = 0; + allowIncoming = false; + +#ifdef S5B_DEBUG + printf("attempting to connect to proxy\n"); +#endif + // connect to the proxy + proxy_conn = new S5BConnector; + connect(proxy_conn, SIGNAL(result(bool)), SLOT(proxy_result(bool))); + StreamHostList list; + list += proxy; + + QGuardedPtr self = this; + proxyConnect(); + if(!self) + return; + + proxy_conn->start(m->client()->jid(), list, key, udp, 30); + } + else { +#ifdef S5B_DEBUG + printf("S5BManager::Item %s claims to have connected to a streamhost we never offered\n", peer.full().latin1()); +#endif + reset(); + error(ErrWrongHost); + } + } + else { +#ifdef S5B_DEBUG + printf("S5BManager::Item %s [%s] error\n", peer.full().latin1(), sid.latin1()); +#endif + remoteFailed = true; + statusCode = j->statusCode(); + + if(lateProxy) { + if(!conn) + doIncoming(); + } + else { + // if connSuccess is true at this point, then we're a Target + if(connSuccess) + checkForActivation(); + else + checkFailure(); + } + } +} + +void S5BManager::Item::conn_result(bool b) +{ + if(b) { + SocksClient *sc = conn->takeClient(); + SocksUDP *sc_udp = conn->takeUDP(); + StreamHost h = conn->streamHostUsed(); + delete conn; + conn = 0; + connSuccess = true; + +#ifdef S5B_DEBUG + printf("S5BManager::Item: %s [%s] successful outgoing connection\n", peer.full().latin1(), sid.latin1()); +#endif + + connect(sc, SIGNAL(readyRead()), SLOT(sc_readyRead())); + connect(sc, SIGNAL(bytesWritten(int)), SLOT(sc_bytesWritten(int))); + connect(sc, SIGNAL(error(int)), SLOT(sc_error(int))); + + m->doSuccess(peer, in_id, h.jid()); + + // if the first batch works, don't try proxy + lateProxy = false; + + // if initiator, run with this one + if(state == Initiator) { + // if we had an incoming one, toss it + delete client_udp; + client_udp = sc_udp; + delete client; + client = sc; + allowIncoming = false; + activatedStream = peer; + tryActivation(); + } + else { + client_out_udp = sc_udp; + client_out = sc; + checkForActivation(); + } + } + else { + delete conn; + conn = 0; + + // if we delayed the proxies for later, try now + if(lateProxy) { + if(remoteFailed) + doIncoming(); + } + else + doConnectError(); + } +} + +void S5BManager::Item::proxy_result(bool b) +{ +#ifdef S5B_DEBUG + printf("proxy_result: %s\n", b ? "ok" : "fail"); +#endif + if(b) { + SocksClient *sc = proxy_conn->takeClient(); + SocksUDP *sc_udp = proxy_conn->takeUDP(); + delete proxy_conn; + proxy_conn = 0; + + connect(sc, SIGNAL(readyRead()), SLOT(sc_readyRead())); + connect(sc, SIGNAL(bytesWritten(int)), SLOT(sc_bytesWritten(int))); + connect(sc, SIGNAL(error(int)), SLOT(sc_error(int))); + + client = sc; + client_udp = sc_udp; + + // activate +#ifdef S5B_DEBUG + printf("activating proxy stream\n"); +#endif + proxy_task = new JT_S5B(m->client()->rootTask()); + connect(proxy_task, SIGNAL(finished()), SLOT(proxy_finished())); + proxy_task->requestActivation(proxy.jid(), sid, peer); + proxy_task->go(true); + } + else { + delete proxy_conn; + proxy_conn = 0; + reset(); + error(ErrProxy); + } +} + +void S5BManager::Item::proxy_finished() +{ + JT_S5B *j = proxy_task; + proxy_task = 0; + + if(j->success()) { +#ifdef S5B_DEBUG + printf("proxy stream activated\n"); +#endif + if(state == Initiator) { + activatedStream = proxy.jid(); + tryActivation(); + } + else + checkForActivation(); + } + else { + reset(); + error(ErrProxy); + } +} + +void S5BManager::Item::sc_readyRead() +{ +#ifdef S5B_DEBUG + printf("sc_readyRead\n"); +#endif + // only targets check for activation, and only should do it if there is no pending outgoing iq-set + if(state == Target && !task && !proxy_task) + checkForActivation(); +} + +void S5BManager::Item::sc_bytesWritten(int) +{ +#ifdef S5B_DEBUG + printf("sc_bytesWritten\n"); +#endif + // this should only happen to the initiator, and should always be 1 byte (the '\r' sent earlier) + finished(); +} + +void S5BManager::Item::sc_error(int) +{ +#ifdef S5B_DEBUG + printf("sc_error\n"); +#endif + reset(); + error(ErrConnect); +} + +void S5BManager::Item::doConnectError() +{ + localFailed = true; + m->doError(peer, in_id, 404, "Could not connect to given hosts"); + checkFailure(); +} + +void S5BManager::Item::tryActivation() +{ +#ifdef S5B_DEBUG + printf("tryActivation\n"); +#endif + if(activated) { +#ifdef S5B_DEBUG + printf("already activated !?\n"); +#endif + return; + } + + if(targetMode == NotFast) { +#ifdef S5B_DEBUG + printf("tryActivation: NotFast\n"); +#endif + // nothing to activate, we're done + finished(); + } + else if(targetMode == Fast) { + // with fast mode, we don't wait for the iq reply, so delete the task (if any) + delete task; + task = 0; + + activated = true; + + // if udp, activate using special stanza + if(udp) { + m->doActivate(peer, sid, activatedStream); + } + else { +#ifdef S5B_DEBUG + printf("sending extra CR\n"); +#endif + // must send [CR] to activate target streamhost + QByteArray a(1); + a[0] = '\r'; + client->write(a); + } + } +} + +void S5BManager::Item::checkForActivation() +{ + QPtrList clientList; + if(client) + clientList.append(client); + if(client_out) + clientList.append(client_out); + QPtrListIterator it(clientList); + for(SocksClient *sc; (sc = it.current()); ++it) { +#ifdef S5B_DEBUG + printf("checking for activation\n"); +#endif + if(fast) { + bool ok = false; + if(udp) { + if((sc == client_out && activatedStream.compare(self)) || (sc == client && !activatedStream.compare(self))) { + clientList.removeRef(sc); + ok = true; + } + } + else { +#ifdef S5B_DEBUG + printf("need CR\n"); +#endif + if(sc->bytesAvailable() >= 1) { + clientList.removeRef(sc); + QByteArray a = sc->read(1); + if(a[0] != '\r') { + delete sc; + return; + } + ok = true; + } + } + + if(ok) { + SocksUDP *sc_udp = 0; + if(sc == client) { + delete client_out_udp; + client_out_udp = 0; + sc_udp = client_udp; + } + else if(sc == client_out) { + delete client_udp; + client_udp = 0; + sc_udp = client_out_udp; + } + + sc->disconnect(this); + clientList.setAutoDelete(true); + clientList.clear(); + client = sc; + client_out = 0; + client_udp = sc_udp; + activated = true; +#ifdef S5B_DEBUG + printf("activation success\n"); +#endif + break; + } + } + else { +#ifdef S5B_DEBUG + printf("not fast mode, no need to wait for anything\n"); +#endif + clientList.removeRef(sc); + sc->disconnect(this); + clientList.setAutoDelete(true); + clientList.clear(); + client = sc; + client_out = 0; + activated = true; + break; + } + } + + if(activated) { + finished(); + } + else { + // only emit waitingForActivation if there is nothing left to do + if((connSuccess || localFailed) && !proxy_task && !proxy_conn) + waitingForActivation(); + } +} + +void S5BManager::Item::checkFailure() +{ + bool failed = false; + if(state == Initiator) { + if(remoteFailed) { + if((localFailed && targetMode == Fast) || targetMode == NotFast) + failed = true; + } + } + else { + if(localFailed) { + if((remoteFailed && fast) || !fast) + failed = true; + } + } + + if(failed) { + if(state == Initiator) { + reset(); + if(statusCode == 404) + error(ErrConnect); + else + error(ErrRefused); + } + else { + reset(); + error(ErrConnect); + } + } +} + +void S5BManager::Item::finished() +{ + client->disconnect(this); + state = Active; +#ifdef S5B_DEBUG + printf("S5BManager::Item %s [%s] linked successfully\n", peer.full().latin1(), sid.latin1()); +#endif + connected(); +} + +//---------------------------------------------------------------------------- +// S5BConnector +//---------------------------------------------------------------------------- +class S5BConnector::Item : public QObject +{ + Q_OBJECT +public: + SocksClient *client; + SocksUDP *client_udp; + StreamHost host; + QString key; + bool udp; + int udp_tries; + QTimer t; + Jid jid; + + Item(const Jid &self, const StreamHost &_host, const QString &_key, bool _udp) : QObject(0) + { + jid = self; + host = _host; + key = _key; + udp = _udp; + client = new SocksClient; + client_udp = 0; + connect(client, SIGNAL(connected()), SLOT(sc_connected())); + connect(client, SIGNAL(error(int)), SLOT(sc_error(int))); + connect(&t, SIGNAL(timeout()), SLOT(trySendUDP())); + } + + ~Item() + { + cleanup(); + } + + void start() + { + client->connectToHost(host.host(), host.port(), key, 0, udp); + } + + void udpSuccess() + { + t.stop(); + client_udp->change(key, 0); // flip over to the data port + success(); + } + +signals: + void result(bool); + +private slots: + void sc_connected() + { + // if udp, need to send init packet before we are good + if(udp) { + // port 1 is init + client_udp = client->createUDP(key, 1, client->peerAddress(), client->peerPort()); + udp_tries = 0; + t.start(5000); + trySendUDP(); + return; + } + + success(); + } + + void sc_error(int) + { +#ifdef S5B_DEBUG + printf("S5BConnector[%s]: error\n", host.host().latin1()); +#endif + cleanup(); + result(false); + } + + void trySendUDP() + { + if(udp_tries == 5) { + t.stop(); + cleanup(); + result(false); + return; + } + + // send initialization with our JID + QCString cs = jid.full().utf8(); + QByteArray a(cs.length()); + memcpy(a.data(), cs.data(), a.size()); + client_udp->write(a); + ++udp_tries; + } + +private: + void cleanup() + { + delete client_udp; + client_udp = 0; + delete client; + client = 0; + } + + void success() + { +#ifdef S5B_DEBUG + printf("S5BConnector[%s]: success\n", host.host().latin1()); +#endif + client->disconnect(this); + result(true); + } +}; + +class S5BConnector::Private +{ +public: + SocksClient *active; + SocksUDP *active_udp; + QPtrList itemList; + QString key; + StreamHost activeHost; + QTimer t; +}; + +S5BConnector::S5BConnector(QObject *parent) +:QObject(parent) +{ + d = new Private; + d->active = 0; + d->active_udp = 0; + d->itemList.setAutoDelete(true); + connect(&d->t, SIGNAL(timeout()), SLOT(t_timeout())); +} + +S5BConnector::~S5BConnector() +{ + reset(); + delete d; +} + +void S5BConnector::reset() +{ + d->t.stop(); + delete d->active_udp; + d->active_udp = 0; + delete d->active; + d->active = 0; + d->itemList.clear(); +} + +void S5BConnector::start(const Jid &self, const StreamHostList &hosts, const QString &key, bool udp, int timeout) +{ + reset(); + +#ifdef S5B_DEBUG + printf("S5BConnector: starting [%p]!\n", this); +#endif + for(StreamHostList::ConstIterator it = hosts.begin(); it != hosts.end(); ++it) { + Item *i = new Item(self, *it, key, udp); + connect(i, SIGNAL(result(bool)), SLOT(item_result(bool))); + d->itemList.append(i); + i->start(); + } + d->t.start(timeout * 1000); +} + +SocksClient *S5BConnector::takeClient() +{ + SocksClient *c = d->active; + d->active = 0; + return c; +} + +SocksUDP *S5BConnector::takeUDP() +{ + SocksUDP *c = d->active_udp; + d->active_udp = 0; + return c; +} + +StreamHost S5BConnector::streamHostUsed() const +{ + return d->activeHost; +} + +void S5BConnector::item_result(bool b) +{ + Item *i = (Item *)sender(); + if(b) { + d->active = i->client; + i->client = 0; + d->active_udp = i->client_udp; + i->client_udp = 0; + d->activeHost = i->host; + d->itemList.clear(); + d->t.stop(); +#ifdef S5B_DEBUG + printf("S5BConnector: complete! [%p]\n", this); +#endif + result(true); + } + else { + d->itemList.removeRef(i); + if(d->itemList.isEmpty()) { + d->t.stop(); +#ifdef S5B_DEBUG + printf("S5BConnector: failed! [%p]\n", this); +#endif + result(false); + } + } +} + +void S5BConnector::t_timeout() +{ + reset(); +#ifdef S5B_DEBUG + printf("S5BConnector: failed! (timeout)\n"); +#endif + result(false); +} + +void S5BConnector::man_udpSuccess(const Jid &streamHost) +{ + // was anyone sending to this streamhost? + QPtrListIterator it(d->itemList); + for(Item *i; (i = it.current()); ++it) { + if(i->host.jid().compare(streamHost) && i->client_udp) { + i->udpSuccess(); + return; + } + } +} + +//---------------------------------------------------------------------------- +// S5BServer +//---------------------------------------------------------------------------- +class S5BServer::Item : public QObject +{ + Q_OBJECT +public: + SocksClient *client; + QString host; + QTimer expire; + + Item(SocksClient *c) : QObject(0) + { + client = c; + connect(client, SIGNAL(incomingMethods(int)), SLOT(sc_incomingMethods(int))); + connect(client, SIGNAL(incomingConnectRequest(const QString &, int)), SLOT(sc_incomingConnectRequest(const QString &, int))); + connect(client, SIGNAL(error(int)), SLOT(sc_error(int))); + + connect(&expire, SIGNAL(timeout()), SLOT(doError())); + resetExpiration(); + } + + ~Item() + { + delete client; + } + + void resetExpiration() + { + expire.start(30000); + } + +signals: + void result(bool); + +private slots: + void doError() + { + expire.stop(); + delete client; + client = 0; + result(false); + } + + void sc_incomingMethods(int m) + { + if(m & SocksClient::AuthNone) + client->chooseMethod(SocksClient::AuthNone); + else + doError(); + } + + void sc_incomingConnectRequest(const QString &_host, int port) + { + if(port == 0) { + host = _host; + client->disconnect(this); + result(true); + } + else + doError(); + } + + void sc_error(int) + { + doError(); + } +}; + +class S5BServer::Private +{ +public: + SocksServer serv; + QStringList hostList; + QPtrList manList; + QPtrList itemList; +}; + +S5BServer::S5BServer(QObject *parent) +:QObject(parent) +{ + d = new Private; + d->itemList.setAutoDelete(true); + connect(&d->serv, SIGNAL(incomingReady()), SLOT(ss_incomingReady())); + connect(&d->serv, SIGNAL(incomingUDP(const QString &, int, const QHostAddress &, int, const QByteArray &)), SLOT(ss_incomingUDP(const QString &, int, const QHostAddress &, int, const QByteArray &))); +} + +S5BServer::~S5BServer() +{ + unlinkAll(); + delete d; +} + +bool S5BServer::isActive() const +{ + return d->serv.isActive(); +} + +bool S5BServer::start(int port) +{ + d->serv.stop(); + return d->serv.listen(port, true); +} + +void S5BServer::stop() +{ + d->serv.stop(); +} + +void S5BServer::setHostList(const QStringList &list) +{ + d->hostList = list; +} + +QStringList S5BServer::hostList() const +{ + return d->hostList; +} + +int S5BServer::port() const +{ + return d->serv.port(); +} + +void S5BServer::ss_incomingReady() +{ + Item *i = new Item(d->serv.takeIncoming()); +#ifdef S5B_DEBUG + printf("S5BServer: incoming connection from %s:%d\n", i->client->peerAddress().toString().latin1(), i->client->peerPort()); +#endif + connect(i, SIGNAL(result(bool)), SLOT(item_result(bool))); + d->itemList.append(i); +} + +void S5BServer::ss_incomingUDP(const QString &host, int port, const QHostAddress &addr, int sourcePort, const QByteArray &data) +{ + if(port != 0 || port != 1) + return; + + QPtrListIterator it(d->manList); + for(S5BManager *m; (m = it.current()); ++it) { + if(m->srv_ownsHash(host)) { + m->srv_incomingUDP(port == 1 ? true : false, addr, sourcePort, host, data); + return; + } + } +} + +void S5BServer::item_result(bool b) +{ + Item *i = (Item *)sender(); +#ifdef S5B_DEBUG + printf("S5BServer item result: %d\n", b); +#endif + if(!b) { + d->itemList.removeRef(i); + return; + } + + SocksClient *c = i->client; + i->client = 0; + QString key = i->host; + d->itemList.removeRef(i); + + // find the appropriate manager for this incoming connection + QPtrListIterator it(d->manList); + for(S5BManager *m; (m = it.current()); ++it) { + if(m->srv_ownsHash(key)) { + m->srv_incomingReady(c, key); + return; + } + } + + // throw it away + delete c; +} + +void S5BServer::link(S5BManager *m) +{ + d->manList.append(m); +} + +void S5BServer::unlink(S5BManager *m) +{ + d->manList.removeRef(m); +} + +void S5BServer::unlinkAll() +{ + QPtrListIterator it(d->manList); + for(S5BManager *m; (m = it.current()); ++it) + m->srv_unlink(); + d->manList.clear(); +} + +const QPtrList & S5BServer::managerList() const +{ + return d->manList; +} + +void S5BServer::writeUDP(const QHostAddress &addr, int port, const QByteArray &data) +{ + d->serv.writeUDP(addr, port, data); +} + +//---------------------------------------------------------------------------- +// JT_S5B +//---------------------------------------------------------------------------- +class JT_S5B::Private +{ +public: + QDomElement iq; + Jid to; + Jid streamHost; + StreamHost proxyInfo; + int mode; + QTimer t; +}; + +JT_S5B::JT_S5B(Task *parent) +:Task(parent) +{ + d = new Private; + d->mode = -1; + connect(&d->t, SIGNAL(timeout()), SLOT(t_timeout())); +} + +JT_S5B::~JT_S5B() +{ + delete d; +} + +void JT_S5B::request(const Jid &to, const QString &sid, const StreamHostList &hosts, bool fast, bool udp) +{ + d->mode = 0; + + QDomElement iq; + d->to = to; + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/bytestreams"); + query.setAttribute("sid", sid); + query.setAttribute("mode", udp ? "udp" : "tcp" ); + iq.appendChild(query); + for(StreamHostList::ConstIterator it = hosts.begin(); it != hosts.end(); ++it) { + QDomElement shost = doc()->createElement("streamhost"); + shost.setAttribute("jid", (*it).jid().full()); + shost.setAttribute("host", (*it).host()); + shost.setAttribute("port", QString::number((*it).port())); + if((*it).isProxy()) { + QDomElement p = doc()->createElement("proxy"); + p.setAttribute("xmlns", "http://affinix.com/jabber/stream"); + shost.appendChild(p); + } + query.appendChild(shost); + } + if(fast) { + QDomElement e = doc()->createElement("fast"); + e.setAttribute("xmlns", "http://affinix.com/jabber/stream"); + query.appendChild(e); + } + d->iq = iq; +} + +void JT_S5B::requestProxyInfo(const Jid &to) +{ + d->mode = 1; + + QDomElement iq; + d->to = to; + iq = createIQ(doc(), "get", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/bytestreams"); + iq.appendChild(query); + d->iq = iq; +} + +void JT_S5B::requestActivation(const Jid &to, const QString &sid, const Jid &target) +{ + d->mode = 2; + + QDomElement iq; + d->to = to; + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/bytestreams"); + query.setAttribute("sid", sid); + iq.appendChild(query); + QDomElement act = doc()->createElement("activate"); + act.appendChild(doc()->createTextNode(target.full())); + query.appendChild(act); + d->iq = iq; +} + +void JT_S5B::onGo() +{ + if(d->mode == 1) + d->t.start(15000, true); + send(d->iq); +} + +void JT_S5B::onDisconnect() +{ + d->t.stop(); +} + +bool JT_S5B::take(const QDomElement &x) +{ + if(d->mode == -1) + return false; + + if(!iqVerify(x, d->to, id())) + return false; + + d->t.stop(); + + if(x.attribute("type") == "result") { + QDomElement q = queryTag(x); + if(d->mode == 0) { + d->streamHost = ""; + if(!q.isNull()) { + QDomElement shost = q.elementsByTagName("streamhost-used").item(0).toElement(); + if(!shost.isNull()) + d->streamHost = shost.attribute("jid"); + } + + setSuccess(); + } + else if(d->mode == 1) { + if(!q.isNull()) { + QDomElement shost = q.elementsByTagName("streamhost").item(0).toElement(); + if(!shost.isNull()) { + Jid j = shost.attribute("jid"); + if(j.isValid()) { + QString host = shost.attribute("host"); + if(!host.isEmpty()) { + int port = shost.attribute("port").toInt(); + StreamHost h; + h.setJid(j); + h.setHost(host); + h.setPort(port); + h.setIsProxy(true); + d->proxyInfo = h; + } + } + } + } + + setSuccess(); + } + else { + setSuccess(); + } + } + else { + setError(x); + } + + return true; +} + +void JT_S5B::t_timeout() +{ + d->mode = -1; + setError(500, "Timed out"); +} + +Jid JT_S5B::streamHostUsed() const +{ + return d->streamHost; +} + +StreamHost JT_S5B::proxyInfo() const +{ + return d->proxyInfo; +} + +//---------------------------------------------------------------------------- +// JT_PushS5B +//---------------------------------------------------------------------------- +JT_PushS5B::JT_PushS5B(Task *parent) +:Task(parent) +{ +} + +JT_PushS5B::~JT_PushS5B() +{ +} + +int JT_PushS5B::priority() const +{ + return 1; +} + +bool JT_PushS5B::take(const QDomElement &e) +{ + // look for udpsuccess + if(e.tagName() == "message") { + QDomElement x = e.elementsByTagName("udpsuccess").item(0).toElement(); + if(!x.isNull() && x.attribute("xmlns") == "http://jabber.org/protocol/bytestreams") { + incomingUDPSuccess(Jid(x.attribute("from")), x.attribute("dstaddr")); + return true; + } + x = e.elementsByTagName("activate").item(0).toElement(); + if(!x.isNull() && x.attribute("xmlns") == "http://affinix.com/jabber/stream") { + incomingActivate(Jid(x.attribute("from")), x.attribute("sid"), Jid(x.attribute("jid"))); + return true; + } + return false; + } + + // must be an iq-set tag + if(e.tagName() != "iq") + return false; + if(e.attribute("type") != "set") + return false; + if(queryNS(e) != "http://jabber.org/protocol/bytestreams") + return false; + + Jid from(e.attribute("from")); + QDomElement q = queryTag(e); + QString sid = q.attribute("sid"); + + StreamHostList hosts; + QDomNodeList nl = q.elementsByTagName("streamhost"); + for(uint n = 0; n < nl.count(); ++n) { + QDomElement shost = nl.item(n).toElement(); + if(hosts.count() < MAXSTREAMHOSTS) { + Jid j = shost.attribute("jid"); + if(!j.isValid()) + continue; + QString host = shost.attribute("host"); + if(host.isEmpty()) + continue; + int port = shost.attribute("port").toInt(); + QDomElement p = shost.elementsByTagName("proxy").item(0).toElement(); + bool isProxy = false; + if(!p.isNull() && p.attribute("xmlns") == "http://affinix.com/jabber/stream") + isProxy = true; + + StreamHost h; + h.setJid(j); + h.setHost(host); + h.setPort(port); + h.setIsProxy(isProxy); + hosts += h; + } + } + + bool fast = false; + QDomElement t; + t = q.elementsByTagName("fast").item(0).toElement(); + if(!t.isNull() && t.attribute("xmlns") == "http://affinix.com/jabber/stream") + fast = true; + + S5BRequest r; + r.from = from; + r.id = e.attribute("id"); + r.sid = sid; + r.hosts = hosts; + r.fast = fast; + r.udp = q.attribute("mode") == "udp" ? true: false; + + incoming(r); + return true; +} + +void JT_PushS5B::respondSuccess(const Jid &to, const QString &id, const Jid &streamHost) +{ + QDomElement iq = createIQ(doc(), "result", to.full(), id); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/bytestreams"); + iq.appendChild(query); + QDomElement shost = doc()->createElement("streamhost-used"); + shost.setAttribute("jid", streamHost.full()); + query.appendChild(shost); + send(iq); +} + +void JT_PushS5B::respondError(const Jid &to, const QString &id, int code, const QString &str) +{ + QDomElement iq = createIQ(doc(), "error", to.full(), id); + QDomElement err = textTag(doc(), "error", str); + err.setAttribute("code", QString::number(code)); + iq.appendChild(err); + send(iq); +} + +void JT_PushS5B::sendUDPSuccess(const Jid &to, const QString &dstaddr) +{ + QDomElement m = doc()->createElement("message"); + m.setAttribute("to", to.full()); + QDomElement u = doc()->createElement("udpsuccess"); + u.setAttribute("xmlns", "http://jabber.org/protocol/bytestreams"); + u.setAttribute("dstaddr", dstaddr); + m.appendChild(u); + send(m); +} + +void JT_PushS5B::sendActivate(const Jid &to, const QString &sid, const Jid &streamHost) +{ + QDomElement m = doc()->createElement("message"); + m.setAttribute("to", to.full()); + QDomElement act = doc()->createElement("activate"); + act.setAttribute("xmlns", "http://affinix.com/jabber/stream"); + act.setAttribute("sid", sid); + act.setAttribute("jid", streamHost.full()); + m.appendChild(act); + send(m); +} + +//---------------------------------------------------------------------------- +// StreamHost +//---------------------------------------------------------------------------- +StreamHost::StreamHost() +{ + v_port = -1; + proxy = false; +} + +const Jid & StreamHost::jid() const +{ + return j; +} + +const QString & StreamHost::host() const +{ + return v_host; +} + +int StreamHost::port() const +{ + return v_port; +} + +bool StreamHost::isProxy() const +{ + return proxy; +} + +void StreamHost::setJid(const Jid &_j) +{ + j = _j; +} + +void StreamHost::setHost(const QString &host) +{ + v_host = host; +} + +void StreamHost::setPort(int port) +{ + v_port = port; +} + +void StreamHost::setIsProxy(bool b) +{ + proxy = b; +} + +} + +#include"s5b.moc" diff --git a/kopete/protocols/jabber/libiris/iris/jabber/s5b.h b/kopete/protocols/jabber/libiris/iris/jabber/s5b.h new file mode 100644 index 00000000..dec06969 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/s5b.h @@ -0,0 +1,341 @@ +/* + * s5b.h - direct connection protocol via tcp + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef XMPP_S5B_H +#define XMPP_S5B_H + +#include +#include +#include +#include +#include"im.h" +#include"bytestream.h" + +class SocksClient; +class SocksUDP; + +namespace XMPP +{ + class StreamHost; + class S5BConnection; + class S5BManager; + class S5BServer; + struct S5BRequest; + typedef QValueList StreamHostList; + typedef QPtrList S5BConnectionList; + typedef QPtrListIterator S5BConnectionListIt; + + class S5BDatagram + { + public: + S5BDatagram(); + S5BDatagram(int source, int dest, const QByteArray &data); + + int sourcePort() const; + int destPort() const; + QByteArray data() const; + + private: + int _source, _dest; + QByteArray _buf; + }; + + class S5BConnection : public ByteStream + { + Q_OBJECT + public: + enum Mode { Stream, Datagram }; + enum Error { ErrRefused, ErrConnect, ErrProxy, ErrSocket }; + enum State { Idle, Requesting, Connecting, WaitingForAccept, Active }; + ~S5BConnection(); + + Jid proxy() const; + void setProxy(const Jid &proxy); + + void connectToJid(const Jid &peer, const QString &sid, Mode m = Stream); + void accept(); + void close(); + + Jid peer() const; + QString sid() const; + bool isRemote() const; + Mode mode() const; + int state() const; + + bool isOpen() const; + void write(const QByteArray &); + QByteArray read(int bytes=0); + int bytesAvailable() const; + int bytesToWrite() const; + + void writeDatagram(const S5BDatagram &); + S5BDatagram readDatagram(); + int datagramsAvailable() const; + + signals: + void proxyQuery(); // querying proxy for streamhost information + void proxyResult(bool b); // query success / fail + void requesting(); // sent actual S5B request (initiator only) + void accepted(); // target accepted (initiator only + void tryingHosts(const StreamHostList &hosts); // currently connecting to these hosts + void proxyConnect(); // connecting to proxy + void waitingForActivation(); // waiting for activation (target only) + void connected(); // connection active + void datagramReady(); + + private slots: + void doPending(); + + void sc_connectionClosed(); + void sc_delayedCloseFinished(); + void sc_readyRead(); + void sc_bytesWritten(int); + void sc_error(int); + + void su_packetReady(const QByteArray &buf); + + private: + class Private; + Private *d; + + void reset(bool clear=false); + void handleUDP(const QByteArray &buf); + void sendUDP(const QByteArray &buf); + + friend class S5BManager; + void man_waitForAccept(const S5BRequest &r); + void man_clientReady(SocksClient *, SocksUDP *); + void man_udpReady(const QByteArray &buf); + void man_failed(int); + S5BConnection(S5BManager *, QObject *parent=0); + }; + + class S5BManager : public QObject + { + Q_OBJECT + public: + S5BManager(Client *); + ~S5BManager(); + + Client *client() const; + S5BServer *server() const; + void setServer(S5BServer *s); + + bool isAcceptableSID(const Jid &peer, const QString &sid) const; + QString genUniqueSID(const Jid &peer) const; + + S5BConnection *createConnection(); + S5BConnection *takeIncoming(); + + class Item; + class Entry; + + signals: + void incomingReady(); + + private slots: + void ps_incoming(const S5BRequest &req); + void ps_incomingUDPSuccess(const Jid &from, const QString &dstaddr); + void ps_incomingActivate(const Jid &from, const QString &sid, const Jid &streamHost); + void item_accepted(); + void item_tryingHosts(const StreamHostList &list); + void item_proxyConnect(); + void item_waitingForActivation(); + void item_connected(); + void item_error(int); + void query_finished(); + + private: + class Private; + Private *d; + + S5BConnection *findIncoming(const Jid &from, const QString &sid) const; + Entry *findEntry(S5BConnection *) const; + Entry *findEntry(Item *) const; + Entry *findEntryByHash(const QString &key) const; + Entry *findEntryBySID(const Jid &peer, const QString &sid) const; + Entry *findServerEntryByHash(const QString &key) const; + + void entryContinue(Entry *e); + void queryProxy(Entry *e); + bool targetShouldOfferProxy(Entry *e); + + friend class S5BConnection; + void con_connect(S5BConnection *); + void con_accept(S5BConnection *); + void con_reject(S5BConnection *); + void con_unlink(S5BConnection *); + void con_sendUDP(S5BConnection *, const QByteArray &buf); + + friend class S5BServer; + bool srv_ownsHash(const QString &key) const; + void srv_incomingReady(SocksClient *sc, const QString &key); + void srv_incomingUDP(bool init, const QHostAddress &addr, int port, const QString &key, const QByteArray &data); + void srv_unlink(); + + friend class Item; + void doSuccess(const Jid &peer, const QString &id, const Jid &streamHost); + void doError(const Jid &peer, const QString &id, int, const QString &); + void doActivate(const Jid &peer, const QString &sid, const Jid &streamHost); + }; + + class S5BConnector : public QObject + { + Q_OBJECT + public: + S5BConnector(QObject *parent=0); + ~S5BConnector(); + + void reset(); + void start(const Jid &self, const StreamHostList &hosts, const QString &key, bool udp, int timeout); + SocksClient *takeClient(); + SocksUDP *takeUDP(); + StreamHost streamHostUsed() const; + + class Item; + + signals: + void result(bool); + + private slots: + void item_result(bool); + void t_timeout(); + + private: + class Private; + Private *d; + + friend class S5BManager; + void man_udpSuccess(const Jid &streamHost); + }; + + // listens on a port for serving + class S5BServer : public QObject + { + Q_OBJECT + public: + S5BServer(QObject *par=0); + ~S5BServer(); + + bool isActive() const; + bool start(int port); + void stop(); + int port() const; + void setHostList(const QStringList &); + QStringList hostList() const; + + class Item; + + private slots: + void ss_incomingReady(); + void ss_incomingUDP(const QString &host, int port, const QHostAddress &addr, int sourcePort, const QByteArray &data); + void item_result(bool); + + private: + class Private; + Private *d; + + friend class S5BManager; + void link(S5BManager *); + void unlink(S5BManager *); + void unlinkAll(); + const QPtrList & managerList() const; + void writeUDP(const QHostAddress &addr, int port, const QByteArray &data); + }; + + class JT_S5B : public Task + { + Q_OBJECT + public: + JT_S5B(Task *); + ~JT_S5B(); + + void request(const Jid &to, const QString &sid, const StreamHostList &hosts, bool fast, bool udp=false); + void requestProxyInfo(const Jid &to); + void requestActivation(const Jid &to, const QString &sid, const Jid &target); + + void onGo(); + void onDisconnect(); + bool take(const QDomElement &); + + Jid streamHostUsed() const; + StreamHost proxyInfo() const; + + private slots: + void t_timeout(); + + private: + class Private; + Private *d; + }; + + struct S5BRequest + { + Jid from; + QString id, sid; + StreamHostList hosts; + bool fast; + bool udp; + }; + class JT_PushS5B : public Task + { + Q_OBJECT + public: + JT_PushS5B(Task *); + ~JT_PushS5B(); + + int priority() const; + + void respondSuccess(const Jid &to, const QString &id, const Jid &streamHost); + void respondError(const Jid &to, const QString &id, int code, const QString &str); + void sendUDPSuccess(const Jid &to, const QString &dstaddr); + void sendActivate(const Jid &to, const QString &sid, const Jid &streamHost); + + bool take(const QDomElement &); + + signals: + void incoming(const S5BRequest &req); + void incomingUDPSuccess(const Jid &from, const QString &dstaddr); + void incomingActivate(const Jid &from, const QString &sid, const Jid &streamHost); + }; + + class StreamHost + { + public: + StreamHost(); + + const Jid & jid() const; + const QString & host() const; + int port() const; + bool isProxy() const; + void setJid(const Jid &); + void setHost(const QString &); + void setPort(int); + void setIsProxy(bool); + + private: + Jid j; + QString v_host; + int v_port; + bool proxy; + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/jabber/xmpp_ibb.cpp b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_ibb.cpp new file mode 100644 index 00000000..813157bf --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_ibb.cpp @@ -0,0 +1,638 @@ +/* + * ibb.cpp - Inband bytestream + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"xmpp_ibb.h" + +#include +#include"xmpp_xmlcommon.h" +#include"base64.h" + +#include + +#define IBB_PACKET_SIZE 4096 +#define IBB_PACKET_DELAY 0 + +using namespace XMPP; + +static int num_conn = 0; +static int id_conn = 0; + +//---------------------------------------------------------------------------- +// IBBConnection +//---------------------------------------------------------------------------- +class IBBConnection::Private +{ +public: + Private() {} + + int state; + Jid peer; + QString sid; + IBBManager *m; + JT_IBB *j; + QDomElement comment; + QString iq_id; + + int blockSize; + QByteArray recvbuf, sendbuf; + bool closePending, closing; + + int id; +}; + +IBBConnection::IBBConnection(IBBManager *m) +:ByteStream(m) +{ + d = new Private; + d->m = m; + d->j = 0; + reset(); + + ++num_conn; + d->id = id_conn++; + QString dstr; dstr.sprintf("IBBConnection[%d]: constructing, count=%d\n", d->id, num_conn); + d->m->client()->debug(dstr); +} + +void IBBConnection::reset(bool clear) +{ + d->m->unlink(this); + d->state = Idle; + d->closePending = false; + d->closing = false; + + delete d->j; + d->j = 0; + + d->sendbuf.resize(0); + if(clear) + d->recvbuf.resize(0); +} + +IBBConnection::~IBBConnection() +{ + reset(true); + + --num_conn; + QString dstr; dstr.sprintf("IBBConnection[%d]: destructing, count=%d\n", d->id, num_conn); + d->m->client()->debug(dstr); + + delete d; +} + +void IBBConnection::connectToJid(const Jid &peer, const QDomElement &comment) +{ + close(); + reset(true); + + d->state = Requesting; + d->peer = peer; + d->comment = comment; + + QString dstr; dstr.sprintf("IBBConnection[%d]: initiating request to %s\n", d->id, peer.full().latin1()); + d->m->client()->debug(dstr); + + d->j = new JT_IBB(d->m->client()->rootTask()); + connect(d->j, SIGNAL(finished()), SLOT(ibb_finished())); + d->j->request(d->peer, comment); + d->j->go(true); +} + +void IBBConnection::accept() +{ + if(d->state != WaitingForAccept) + return; + + QString dstr; dstr.sprintf("IBBConnection[%d]: accepting %s [%s]\n", d->id, d->peer.full().latin1(), d->sid.latin1()); + d->m->client()->debug(dstr); + + d->m->doAccept(this, d->iq_id); + d->state = Active; + d->m->link(this); +} + +void IBBConnection::close() +{ + if(d->state == Idle) + return; + + if(d->state == WaitingForAccept) { + d->m->doReject(this, d->iq_id, 403, "Rejected"); + reset(); + return; + } + + QString dstr; dstr.sprintf("IBBConnection[%d]: closing\n", d->id); + d->m->client()->debug(dstr); + + if(d->state == Active) { + // if there is data pending to be written, then pend the closing + if(bytesToWrite() > 0) { + d->closePending = true; + trySend(); + return; + } + + // send a close packet + JT_IBB *j = new JT_IBB(d->m->client()->rootTask()); + j->sendData(d->peer, d->sid, QByteArray(), true); + j->go(true); + } + + reset(); +} + +int IBBConnection::state() const +{ + return d->state; +} + +Jid IBBConnection::peer() const +{ + return d->peer; +} + +QString IBBConnection::streamid() const +{ + return d->sid; +} + +QDomElement IBBConnection::comment() const +{ + return d->comment; +} + +bool IBBConnection::isOpen() const +{ + if(d->state == Active) + return true; + else + return false; +} + +void IBBConnection::write(const QByteArray &a) +{ + if(d->state != Active || d->closePending || d->closing) + return; + + // append to the end of our send buffer + int oldsize = d->sendbuf.size(); + d->sendbuf.resize(oldsize + a.size()); + memcpy(d->sendbuf.data() + oldsize, a.data(), a.size()); + + trySend(); +} + +QByteArray IBBConnection::read(int) +{ + // TODO: obey argument + QByteArray a = d->recvbuf.copy(); + d->recvbuf.resize(0); + return a; +} + +int IBBConnection::bytesAvailable() const +{ + return d->recvbuf.size(); +} + +int IBBConnection::bytesToWrite() const +{ + return d->sendbuf.size(); +} + +void IBBConnection::waitForAccept(const Jid &peer, const QString &sid, const QDomElement &comment, const QString &iq_id) +{ + close(); + reset(true); + + d->state = WaitingForAccept; + d->peer = peer; + d->sid = sid; + d->comment = comment; + d->iq_id = iq_id; +} + +void IBBConnection::takeIncomingData(const QByteArray &a, bool close) +{ + // append to the end of our recv buffer + int oldsize = d->recvbuf.size(); + d->recvbuf.resize(oldsize + a.size()); + memcpy(d->recvbuf.data() + oldsize, a.data(), a.size()); + + readyRead(); + + if(close) { + reset(); + connectionClosed(); + } +} + +void IBBConnection::ibb_finished() +{ + JT_IBB *j = d->j; + d->j = 0; + + if(j->success()) { + if(j->mode() == JT_IBB::ModeRequest) { + d->sid = j->streamid(); + + QString dstr; dstr.sprintf("IBBConnection[%d]: %s [%s] accepted.\n", d->id, d->peer.full().latin1(), d->sid.latin1()); + d->m->client()->debug(dstr); + + d->state = Active; + d->m->link(this); + connected(); + } + else { + bytesWritten(d->blockSize); + + if(d->closing) { + reset(); + delayedCloseFinished(); + } + + if(!d->sendbuf.isEmpty() || d->closePending) + QTimer::singleShot(IBB_PACKET_DELAY, this, SLOT(trySend())); + } + } + else { + if(j->mode() == JT_IBB::ModeRequest) { + QString dstr; dstr.sprintf("IBBConnection[%d]: %s refused.\n", d->id, d->peer.full().latin1()); + d->m->client()->debug(dstr); + + reset(true); + error(ErrRequest); + } + else { + reset(true); + error(ErrData); + } + } +} + +void IBBConnection::trySend() +{ + // if we already have an active task, then don't do anything + if(d->j) + return; + + QByteArray a; + if(!d->sendbuf.isEmpty()) { + // take a chunk + if(d->sendbuf.size() < IBB_PACKET_SIZE) + a.resize(d->sendbuf.size()); + else + a.resize(IBB_PACKET_SIZE); + memcpy(a.data(), d->sendbuf.data(), a.size()); + d->sendbuf.resize(d->sendbuf.size() - a.size()); + } + + bool doClose = false; + if(d->sendbuf.isEmpty() && d->closePending) + doClose = true; + + // null operation? + if(a.isEmpty() && !doClose) + return; + + printf("IBBConnection[%d]: sending [%d] bytes ", d->id, a.size()); + if(doClose) + printf("and closing.\n"); + else + printf("(%d bytes left)\n", d->sendbuf.size()); + + if(doClose) { + d->closePending = false; + d->closing = true; + } + + d->blockSize = a.size(); + d->j = new JT_IBB(d->m->client()->rootTask()); + connect(d->j, SIGNAL(finished()), SLOT(ibb_finished())); + d->j->sendData(d->peer, d->sid, a, doClose); + d->j->go(true); +} + + +//---------------------------------------------------------------------------- +// IBBManager +//---------------------------------------------------------------------------- +class IBBManager::Private +{ +public: + Private() {} + + Client *client; + IBBConnectionList activeConns; + IBBConnectionList incomingConns; + JT_IBB *ibb; +}; + +IBBManager::IBBManager(Client *parent) +:QObject(parent) +{ + d = new Private; + d->client = parent; + + d->ibb = new JT_IBB(d->client->rootTask(), true); + connect(d->ibb, SIGNAL(incomingRequest(const Jid &, const QString &, const QDomElement &)), SLOT(ibb_incomingRequest(const Jid &, const QString &, const QDomElement &))); + connect(d->ibb, SIGNAL(incomingData(const Jid &, const QString &, const QString &, const QByteArray &, bool)), SLOT(ibb_incomingData(const Jid &, const QString &, const QString &, const QByteArray &, bool))); +} + +IBBManager::~IBBManager() +{ + d->incomingConns.setAutoDelete(true); + d->incomingConns.clear(); + delete d->ibb; + delete d; +} + +Client *IBBManager::client() const +{ + return d->client; +} + +IBBConnection *IBBManager::takeIncoming() +{ + if(d->incomingConns.isEmpty()) + return 0; + + IBBConnection *c = d->incomingConns.getFirst(); + d->incomingConns.removeRef(c); + return c; +} + +void IBBManager::ibb_incomingRequest(const Jid &from, const QString &id, const QDomElement &comment) +{ + QString sid = genUniqueKey(); + + // create a "waiting" connection + IBBConnection *c = new IBBConnection(this); + c->waitForAccept(from, sid, comment, id); + d->incomingConns.append(c); + incomingReady(); +} + +void IBBManager::ibb_incomingData(const Jid &from, const QString &streamid, const QString &id, const QByteArray &data, bool close) +{ + IBBConnection *c = findConnection(streamid, from); + if(!c) { + d->ibb->respondError(from, id, 404, "No such stream"); + } + else { + d->ibb->respondAck(from, id); + c->takeIncomingData(data, close); + } +} + +QString IBBManager::genKey() const +{ + QString key = "ibb_"; + + for(int i = 0; i < 4; ++i) { + int word = rand() & 0xffff; + for(int n = 0; n < 4; ++n) { + QString s; + s.sprintf("%x", (word >> (n * 4)) & 0xf); + key.append(s); + } + } + + return key; +} + +QString IBBManager::genUniqueKey() const +{ + // get unused key + QString key; + while(1) { + key = genKey(); + + if(!findConnection(key)) + break; + } + + return key; +} + +void IBBManager::link(IBBConnection *c) +{ + d->activeConns.append(c); +} + +void IBBManager::unlink(IBBConnection *c) +{ + d->activeConns.removeRef(c); +} + +IBBConnection *IBBManager::findConnection(const QString &sid, const Jid &peer) const +{ + IBBConnectionListIt it(d->activeConns); + for(IBBConnection *c; (c = it.current()); ++it) { + if(c->streamid() == sid && (peer.isEmpty() || c->peer().compare(peer)) ) + return c; + } + return 0; +} + +void IBBManager::doAccept(IBBConnection *c, const QString &id) +{ + d->ibb->respondSuccess(c->peer(), id, c->streamid()); +} + +void IBBManager::doReject(IBBConnection *c, const QString &id, int code, const QString &str) +{ + d->ibb->respondError(c->peer(), id, code, str); +} + + +//---------------------------------------------------------------------------- +// JT_IBB +//---------------------------------------------------------------------------- +class JT_IBB::Private +{ +public: + Private() {} + + QDomElement iq; + int mode; + bool serve; + Jid to; + QString streamid; +}; + +JT_IBB::JT_IBB(Task *parent, bool serve) +:Task(parent) +{ + d = new Private; + d->serve = serve; +} + +JT_IBB::~JT_IBB() +{ + delete d; +} + +void JT_IBB::request(const Jid &to, const QDomElement &comment) +{ + d->mode = ModeRequest; + QDomElement iq; + d->to = to; + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/ibb"); + iq.appendChild(query); + query.appendChild(comment); + d->iq = iq; +} + +void JT_IBB::sendData(const Jid &to, const QString &streamid, const QByteArray &a, bool close) +{ + d->mode = ModeSendData; + QDomElement iq; + d->to = to; + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/ibb"); + iq.appendChild(query); + query.appendChild(textTag(doc(), "streamid", streamid)); + if(!a.isEmpty()) + query.appendChild(textTag(doc(), "data", Base64::arrayToString(a))); + if(close) { + QDomElement c = doc()->createElement("close"); + query.appendChild(c); + } + d->iq = iq; +} + +void JT_IBB::respondSuccess(const Jid &to, const QString &id, const QString &streamid) +{ + QDomElement iq = createIQ(doc(), "result", to.full(), id); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/ibb"); + iq.appendChild(query); + query.appendChild(textTag(doc(), "streamid", streamid)); + send(iq); +} + +void JT_IBB::respondError(const Jid &to, const QString &id, int code, const QString &str) +{ + QDomElement iq = createIQ(doc(), "error", to.full(), id); + QDomElement err = textTag(doc(), "error", str); + err.setAttribute("code", QString::number(code)); + iq.appendChild(err); + send(iq); +} + +void JT_IBB::respondAck(const Jid &to, const QString &id) +{ + QDomElement iq = createIQ(doc(), "result", to.full(), id); + send(iq); +} + +void JT_IBB::onGo() +{ + send(d->iq); +} + +bool JT_IBB::take(const QDomElement &e) +{ + if(d->serve) { + // must be an iq-set tag + if(e.tagName() != "iq" || e.attribute("type") != "set") + return false; + + if(queryNS(e) != "http://jabber.org/protocol/ibb") + return false; + + Jid from(e.attribute("from")); + QString id = e.attribute("id"); + QDomElement q = queryTag(e); + + bool found; + QDomElement s = findSubTag(q, "streamid", &found); + if(!found) { + QDomElement comment = findSubTag(q, "comment", &found); + incomingRequest(from, id, comment); + } + else { + QString sid = tagContent(s); + QByteArray a; + bool close = false; + s = findSubTag(q, "data", &found); + if(found) + a = Base64::stringToArray(tagContent(s)); + s = findSubTag(q, "close", &found); + if(found) + close = true; + + incomingData(from, sid, id, a, close); + } + + return true; + } + else { + Jid from(e.attribute("from")); + if(e.attribute("id") != id() || !d->to.compare(from)) + return false; + + if(e.attribute("type") == "result") { + QDomElement q = queryTag(e); + + // request + if(d->mode == ModeRequest) { + bool found; + QDomElement s = findSubTag(q, "streamid", &found); + if(found) + d->streamid = tagContent(s); + else + d->streamid = ""; + setSuccess(); + } + // sendData + else { + // thank you for the ack, kind sir + setSuccess(); + } + } + else { + setError(e); + } + + return true; + } +} + +QString JT_IBB::streamid() const +{ + return d->streamid; +} + +Jid JT_IBB::jid() const +{ + return d->to; +} + +int JT_IBB::mode() const +{ + return d->mode; +} + diff --git a/kopete/protocols/jabber/libiris/iris/jabber/xmpp_ibb.h b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_ibb.h new file mode 100644 index 00000000..73de4ac4 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_ibb.h @@ -0,0 +1,145 @@ +/* + * ibb.h - Inband bytestream + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef JABBER_IBB_H +#define JABBER_IBB_H + +#include +#include +#include +#include +#include"bytestream.h" +#include"im.h" + +namespace XMPP +{ + class Client; + class IBBManager; + + // this is an IBB connection. use it much like a qsocket + class IBBConnection : public ByteStream + { + Q_OBJECT + public: + enum { ErrRequest, ErrData }; + enum { Idle, Requesting, WaitingForAccept, Active }; + IBBConnection(IBBManager *); + ~IBBConnection(); + + void connectToJid(const Jid &peer, const QDomElement &comment); + void accept(); + void close(); + + int state() const; + Jid peer() const; + QString streamid() const; + QDomElement comment() const; + + bool isOpen() const; + void write(const QByteArray &); + QByteArray read(int bytes=0); + int bytesAvailable() const; + int bytesToWrite() const; + + signals: + void connected(); + + private slots: + void ibb_finished(); + void trySend(); + + private: + class Private; + Private *d; + + void reset(bool clear=false); + + friend class IBBManager; + void waitForAccept(const Jid &peer, const QString &sid, const QDomElement &comment, const QString &iq_id); + void takeIncomingData(const QByteArray &, bool close); + }; + + typedef QPtrList IBBConnectionList; + typedef QPtrListIterator IBBConnectionListIt; + class IBBManager : public QObject + { + Q_OBJECT + public: + IBBManager(Client *); + ~IBBManager(); + + Client *client() const; + + IBBConnection *takeIncoming(); + + signals: + void incomingReady(); + + private slots: + void ibb_incomingRequest(const Jid &from, const QString &id, const QDomElement &); + void ibb_incomingData(const Jid &from, const QString &streamid, const QString &id, const QByteArray &data, bool close); + + private: + class Private; + Private *d; + + QString genKey() const; + + friend class IBBConnection; + IBBConnection *findConnection(const QString &sid, const Jid &peer="") const; + QString genUniqueKey() const; + void link(IBBConnection *); + void unlink(IBBConnection *); + void doAccept(IBBConnection *c, const QString &id); + void doReject(IBBConnection *c, const QString &id, int, const QString &); + }; + + class JT_IBB : public Task + { + Q_OBJECT + public: + enum { ModeRequest, ModeSendData }; + JT_IBB(Task *, bool serve=false); + ~JT_IBB(); + + void request(const Jid &, const QDomElement &comment); + void sendData(const Jid &, const QString &streamid, const QByteArray &data, bool close); + void respondSuccess(const Jid &, const QString &id, const QString &streamid); + void respondError(const Jid &, const QString &id, int code, const QString &str); + void respondAck(const Jid &to, const QString &id); + + void onGo(); + bool take(const QDomElement &); + + QString streamid() const; + Jid jid() const; + int mode() const; + + signals: + void incomingRequest(const Jid &from, const QString &id, const QDomElement &); + void incomingData(const Jid &from, const QString &streamid, const QString &id, const QByteArray &data, bool close); + + private: + class Private; + Private *d; + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/jabber/xmpp_jidlink.cpp b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_jidlink.cpp new file mode 100644 index 00000000..eb140880 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_jidlink.cpp @@ -0,0 +1,318 @@ +/* + * jidlink.cpp - establish a link between Jabber IDs + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"xmpp_jidlink.h" + +#include +#include +#include"im.h" +#include"s5b.h" +#include"xmpp_ibb.h" + +using namespace XMPP; + +//---------------------------------------------------------------------------- +// JidLink +//---------------------------------------------------------------------------- +class JidLink::Private +{ +public: + Client *client; + ByteStream *bs; + int type; + int state; + Jid peer; +}; + +JidLink::JidLink(Client *client) +:QObject(client->jidLinkManager()) +{ + d = new Private; + d->client = client; + d->bs = 0; + + reset(); +} + +JidLink::~JidLink() +{ + reset(true); + + delete d; +} + +void JidLink::reset(bool clear) +{ + d->type = None; + d->state = Idle; + + if(d->bs) { + unlink(); + d->bs->close(); + if(clear) { + delete d->bs; + d->bs = 0; + } + } +} + +void JidLink::connectToJid(const Jid &jid, int type, const QDomElement &comment) +{ + reset(true); + if(type == DTCP) + d->bs = d->client->s5bManager()->createConnection(); + else if(type == IBB) + d->bs = new IBBConnection(d->client->ibbManager()); + else + return; + + d->type = type; + d->peer = jid; + d->state = Connecting; + + link(); + + if(type == DTCP) { + S5BConnection *c = (S5BConnection *)d->bs; + status(StatDTCPRequesting); + c->connectToJid(jid, d->client->s5bManager()->genUniqueSID(jid)); + } + else { + IBBConnection *c = (IBBConnection *)d->bs; + status(StatIBBRequesting); + c->connectToJid(jid, comment); + } +} + +void JidLink::link() +{ + if(d->type == DTCP) { + S5BConnection *c = (S5BConnection *)d->bs; + connect(c, SIGNAL(connected()), SLOT(dtcp_connected())); + connect(c, SIGNAL(accepted()), SLOT(dtcp_accepted())); + } + else { + IBBConnection *c = (IBBConnection *)d->bs; + connect(c, SIGNAL(connected()), SLOT(ibb_connected())); + } + + connect(d->bs, SIGNAL(connectionClosed()), SLOT(bs_connectionClosed())); + connect(d->bs, SIGNAL(error(int)), SLOT(bs_error(int))); + connect(d->bs, SIGNAL(bytesWritten(int)), SLOT(bs_bytesWritten(int))); + connect(d->bs, SIGNAL(readyRead()), SLOT(bs_readyRead())); +} + +void JidLink::unlink() +{ + d->bs->disconnect(this); +} + +void JidLink::accept() +{ + if(d->state != WaitingForAccept) + return; + + QTimer::singleShot(0, this, SLOT(doRealAccept())); +} + +void JidLink::doRealAccept() +{ + if(d->type == DTCP) { + ((S5BConnection *)d->bs)->accept(); + d->state = Connecting; + dtcp_accepted(); + } + else { + ((IBBConnection *)d->bs)->accept(); + d->state = Active; + connected(); + } +} + +bool JidLink::setStream(ByteStream *bs) +{ + reset(true); + int type = None; + if(bs->inherits("XMPP::S5BConnection")) + type = DTCP; + else if(bs->inherits("XMPP::IBBConnection")) + type = IBB; + + if(type == None) + return false; + + d->type = type; + d->bs = bs; + d->state = WaitingForAccept; + + link(); + + if(d->type == DTCP) + d->peer = ((S5BConnection *)d->bs)->peer(); + else + d->peer = ((IBBConnection *)d->bs)->peer(); + + return true; +} + +int JidLink::type() const +{ + return d->type; +} + +Jid JidLink::peer() const +{ + return d->peer; +} + +int JidLink::state() const +{ + return d->state; +} + +bool JidLink::isOpen() const +{ + if(d->state == Active) + return true; + else + return false; +} + +void JidLink::close() +{ + if(d->state == Idle) + return; + reset(); +} + +void JidLink::write(const QByteArray &a) +{ + if(d->state == Active) + d->bs->write(a); +} + +QByteArray JidLink::read(int bytes) +{ + if(d->bs) + return d->bs->read(bytes); + else + return QByteArray(); +} + +int JidLink::bytesAvailable() const +{ + if(d->bs) + return d->bs->bytesAvailable(); + else + return 0; +} + +int JidLink::bytesToWrite() const +{ + if(d->state == Active) + return d->bs->bytesToWrite(); + else + return 0; +} + +void JidLink::dtcp_accepted() +{ + status(StatDTCPAccepted); +} + +void JidLink::dtcp_connected() +{ + d->state = Active; + status(StatDTCPConnected); + connected(); +} + +void JidLink::ibb_connected() +{ + d->state = Active; + status(StatIBBConnected); + connected(); +} + +void JidLink::bs_connectionClosed() +{ + reset(); + connectionClosed(); +} + +void JidLink::bs_error(int) +{ + reset(); + error(ErrConnect); +} + +void JidLink::bs_readyRead() +{ + readyRead(); +} + +void JidLink::bs_bytesWritten(int x) +{ + bytesWritten(x); +} + + +//---------------------------------------------------------------------------- +// JidLinkManager +//---------------------------------------------------------------------------- +class JidLinkManager::Private +{ +public: + Private() {} + + Client *client; + QPtrList incomingList; +}; + +JidLinkManager::JidLinkManager(Client *par) +:QObject(par) +{ + d = new Private; + d->client = par; +} + +JidLinkManager::~JidLinkManager() +{ + d->incomingList.setAutoDelete(true); + d->incomingList.clear(); + delete d; +} + +JidLink *JidLinkManager::takeIncoming() +{ + if(d->incomingList.isEmpty()) + return 0; + + JidLink *j = d->incomingList.getFirst(); + d->incomingList.removeRef(j); + return j; +} + +void JidLinkManager::insertStream(ByteStream *bs) +{ + JidLink *j = new JidLink(d->client); + if(j->setStream(bs)) + d->incomingList.append(j); +} diff --git a/kopete/protocols/jabber/libiris/iris/jabber/xmpp_jidlink.h b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_jidlink.h new file mode 100644 index 00000000..955fce50 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_jidlink.h @@ -0,0 +1,114 @@ +/* + * jidlink.h - establish a link between Jabber IDs + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + NOTE: this is not to be confused with JEP-0041 +*/ + +#ifndef JABBER_JIDLINK_H +#define JABBER_JIDLINK_H + +#include +#include +#include"xmpp.h" + +class ByteStream; + +namespace XMPP +{ + class Client; + + class JidLink : public QObject + { + Q_OBJECT + public: + enum { None, DTCP, IBB }; + enum { Idle, Connecting, WaitingForAccept, Active }; + enum { ErrConnect, ErrStream }; + enum { StatDTCPRequesting, StatDTCPAccepted, StatDTCPConnected, StatIBBRequesting, StatIBBConnected }; + JidLink(Client *client); + ~JidLink(); + + void connectToJid(const Jid &jid, int type, const QDomElement &comment); + void accept(); + + int type() const; + Jid peer() const; + int state() const; + + bool isOpen() const; + void close(); + void write(const QByteArray &); + QByteArray read(int bytes=0); + int bytesAvailable() const; + int bytesToWrite() const; + + signals: + void connected(); + void connectionClosed(); + void readyRead(); + void bytesWritten(int); + void error(int); + void status(int); + + private slots: + void dtcp_connected(); + void dtcp_accepted(); + void ibb_connected(); + + void bs_connectionClosed(); + void bs_error(int); + void bs_readyRead(); + void bs_bytesWritten(int); + + void doRealAccept(); + + private: + class Private; + Private *d; + + void reset(bool clear=false); + + void link(); + void unlink(); + + friend class JidLinkManager; + bool setStream(ByteStream *); + }; + + // the job of JidLinkManager is to keep track of streams and properly shut them down + class JidLinkManager : public QObject + { + Q_OBJECT + public: + JidLinkManager(Client *); + ~JidLinkManager(); + + JidLink *takeIncoming(); + + void insertStream(ByteStream *); + + private: + class Private; + Private *d; + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/Makefile.am b/kopete/protocols/jabber/libiris/iris/xmpp-core/Makefile.am new file mode 100644 index 00000000..f35b1c68 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/Makefile.am @@ -0,0 +1,30 @@ +# The only Q_OBJECT lines are in securestream.{h,cpp} and we deal with them below. +# Give metasources a file with no Q_OBJECT line to stop unsermake assuming we want METASOURCES = AUTO +METASOURCES = ignore_this_warning.moc + +noinst_LTLIBRARIES = libiris_xmpp_core.la +AM_CPPFLAGS = $(IDN_CFLAGS) +INCLUDES = -I$(srcdir)/../include -I$(srcdir)/../xmpp-core -I$(srcdir)/../xmpp-im -I$(srcdir)/../../cutestuff/util -I$(srcdir)/../../cutestuff/network -I$(srcdir)/../../qca/src $(all_includes) + +libiris_xmpp_core_la_CPPFLAGS = $(IDN_CFLAGS) +libiris_xmpp_core_la_LDFLAGS = $(IDN_LIBS) +libiris_xmpp_core_la_SOURCES = \ + connector.cpp \ + jid.cpp \ + securestream.cpp \ + tlshandler.cpp \ + hash.cpp \ + protocol.cpp \ + stream.cpp \ + xmlprotocol.cpp \ + parser.cpp \ + simplesasl.cpp + +libiris_xmpp_core_la_COMPILE_FIRST = securestream.moc + +CLEANFILES = securestream.moc +securestream.moc: $(srcdir)/securestream.cpp $(srcdir)/securestream.h + ${MOC} $(srcdir)/securestream.h > $@ + ${MOC} $(srcdir)/securestream.cpp >> $@ + +KDE_OPTIONS = nofinal diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/connector.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/connector.cpp new file mode 100644 index 00000000..8ebc3ee8 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/connector.cpp @@ -0,0 +1,719 @@ +/* + * connector.cpp - establish a connection to an XMPP server + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + TODO: + + - Test and analyze all possible branches + + XMPP::AdvancedConnector is "good for now." The only real issue is that + most of what it provides is just to work around the old Jabber/XMPP 0.9 + connection behavior. When XMPP 1.0 has taken over the world, we can + greatly simplify this class. - Sep 3rd, 2003. +*/ + +#include"xmpp.h" + +#include +#include +#include"safedelete.h" + +#ifdef NO_NDNS +#include +#else +#include"ndns.h" +#endif + +#include"srvresolver.h" +#include"bsocket.h" +#include"httpconnect.h" +#include"httppoll.h" +#include"socks.h" +#include"hash.h" + +//#define XMPP_DEBUG + +using namespace XMPP; + +//---------------------------------------------------------------------------- +// Connector +//---------------------------------------------------------------------------- +Connector::Connector(QObject *parent) +:QObject(parent) +{ + setUseSSL(false); + setPeerAddressNone(); +} + +Connector::~Connector() +{ +} + +bool Connector::useSSL() const +{ + return ssl; +} + +bool Connector::havePeerAddress() const +{ + return haveaddr; +} + +QHostAddress Connector::peerAddress() const +{ + return addr; +} + +Q_UINT16 Connector::peerPort() const +{ + return port; +} + +void Connector::setUseSSL(bool b) +{ + ssl = b; +} + +void Connector::setPeerAddressNone() +{ + haveaddr = false; + addr = QHostAddress(); + port = 0; +} + +void Connector::setPeerAddress(const QHostAddress &_addr, Q_UINT16 _port) +{ + haveaddr = true; + addr = _addr; + port = _port; +} + + +//---------------------------------------------------------------------------- +// AdvancedConnector::Proxy +//---------------------------------------------------------------------------- +AdvancedConnector::Proxy::Proxy() +{ + t = None; + v_poll = 30; +} + +AdvancedConnector::Proxy::~Proxy() +{ +} + +int AdvancedConnector::Proxy::type() const +{ + return t; +} + +QString AdvancedConnector::Proxy::host() const +{ + return v_host; +} + +Q_UINT16 AdvancedConnector::Proxy::port() const +{ + return v_port; +} + +QString AdvancedConnector::Proxy::url() const +{ + return v_url; +} + +QString AdvancedConnector::Proxy::user() const +{ + return v_user; +} + +QString AdvancedConnector::Proxy::pass() const +{ + return v_pass; +} + +int AdvancedConnector::Proxy::pollInterval() const +{ + return v_poll; +} + +void AdvancedConnector::Proxy::setHttpConnect(const QString &host, Q_UINT16 port) +{ + t = HttpConnect; + v_host = host; + v_port = port; +} + +void AdvancedConnector::Proxy::setHttpPoll(const QString &host, Q_UINT16 port, const QString &url) +{ + t = HttpPoll; + v_host = host; + v_port = port; + v_url = url; +} + +void AdvancedConnector::Proxy::setSocks(const QString &host, Q_UINT16 port) +{ + t = Socks; + v_host = host; + v_port = port; +} + +void AdvancedConnector::Proxy::setUserPass(const QString &user, const QString &pass) +{ + v_user = user; + v_pass = pass; +} + +void AdvancedConnector::Proxy::setPollInterval(int secs) +{ + v_poll = secs; +} + + +//---------------------------------------------------------------------------- +// AdvancedConnector +//---------------------------------------------------------------------------- +enum { Idle, Connecting, Connected }; +class AdvancedConnector::Private +{ +public: + int mode; + ByteStream *bs; +#ifdef NO_NDNS + QDns *qdns; +#else + NDns dns; +#endif + SrvResolver srv; + + QString server; + QString opt_host; + int opt_port; + bool opt_probe, opt_ssl; + Proxy proxy; + + QString host; + int port; + QValueList servers; + int errorCode; + + bool multi, using_srv; + bool will_be_ssl; + int probe_mode; + + bool aaaa; + SafeDelete sd; +}; + +AdvancedConnector::AdvancedConnector(QObject *parent) +:Connector(parent) +{ + d = new Private; + d->bs = 0; +#ifdef NO_NDNS + d->qdns = 0; +#else + connect(&d->dns, SIGNAL(resultsReady()), SLOT(dns_done())); +#endif + connect(&d->srv, SIGNAL(resultsReady()), SLOT(srv_done())); + d->opt_probe = false; + d->opt_ssl = false; + cleanup(); + d->errorCode = 0; +} + +AdvancedConnector::~AdvancedConnector() +{ + cleanup(); + delete d; +} + +void AdvancedConnector::cleanup() +{ + d->mode = Idle; + + // stop any dns +#ifdef NO_NDNS + if(d->qdns) { + d->qdns->disconnect(this); + d->qdns->deleteLater(); + //d->sd.deleteLater(d->qdns); + d->qdns = 0; + } +#else + if(d->dns.isBusy()) + d->dns.stop(); +#endif + if(d->srv.isBusy()) + d->srv.stop(); + + // destroy the bytestream, if there is one + delete d->bs; + d->bs = 0; + + d->multi = false; + d->using_srv = false; + d->will_be_ssl = false; + d->probe_mode = -1; + + setUseSSL(false); + setPeerAddressNone(); +} + +void AdvancedConnector::setProxy(const Proxy &proxy) +{ + if(d->mode != Idle) + return; + d->proxy = proxy; +} + +void AdvancedConnector::setOptHostPort(const QString &host, Q_UINT16 _port) +{ + if(d->mode != Idle) + return; + d->opt_host = host; + d->opt_port = _port; +} + +void AdvancedConnector::setOptProbe(bool b) +{ + if(d->mode != Idle) + return; + d->opt_probe = b; +} + +void AdvancedConnector::setOptSSL(bool b) +{ + if(d->mode != Idle) + return; + d->opt_ssl = b; +} + +void AdvancedConnector::connectToServer(const QString &server) +{ + if(d->mode != Idle) + return; + if(server.isEmpty()) + return; + + d->errorCode = 0; + d->server = server; + d->mode = Connecting; + d->aaaa = true; + + if(d->proxy.type() == Proxy::HttpPoll) { + // need SHA1 here + if(!QCA::isSupported(QCA::CAP_SHA1)) + QCA::insertProvider(createProviderHash()); + + HttpPoll *s = new HttpPoll; + d->bs = s; + connect(s, SIGNAL(connected()), SLOT(bs_connected())); + connect(s, SIGNAL(syncStarted()), SLOT(http_syncStarted())); + connect(s, SIGNAL(syncFinished()), SLOT(http_syncFinished())); + connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); + if(!d->proxy.user().isEmpty()) + s->setAuth(d->proxy.user(), d->proxy.pass()); + s->setPollInterval(d->proxy.pollInterval()); + + if(d->proxy.host().isEmpty()) + s->connectToUrl(d->proxy.url()); + else + s->connectToHost(d->proxy.host(), d->proxy.port(), d->proxy.url()); + } + else { + if(!d->opt_host.isEmpty()) { + d->host = d->opt_host; + d->port = d->opt_port; + do_resolve(); + } + else { + d->multi = true; + + QGuardedPtr self = this; + srvLookup(d->server); + if(!self) + return; + + d->srv.resolveSrvOnly(d->server, "xmpp-client", "tcp"); + } + } +} + +void AdvancedConnector::changePollInterval(int secs) +{ + if(d->bs && (d->bs->inherits("XMPP::HttpPoll") || d->bs->inherits("HttpPoll"))) { + HttpPoll *s = static_cast(d->bs); + s->setPollInterval(secs); + } +} + +ByteStream *AdvancedConnector::stream() const +{ + if(d->mode == Connected) + return d->bs; + else + return 0; +} + +void AdvancedConnector::done() +{ + cleanup(); +} + +int AdvancedConnector::errorCode() const +{ + return d->errorCode; +} + +void AdvancedConnector::do_resolve() +{ +#ifdef NO_NDNS + printf("resolving (aaaa=%d)\n", d->aaaa); + d->qdns = new QDns; + connect(d->qdns, SIGNAL(resultsReady()), SLOT(dns_done())); + if(d->aaaa) + d->qdns->setRecordType(QDns::Aaaa); // IPv6 + else + d->qdns->setRecordType(QDns::A); // IPv4 + d->qdns->setLabel(d->host); +#else + d->dns.resolve(d->host); +#endif +} + +void AdvancedConnector::dns_done() +{ + bool failed = false; + QHostAddress addr; + +#ifdef NO_NDNS + //if(!d->qdns) + // return; + + // apparently we sometimes get this signal even though the results aren' t ready + //if(d->qdns->isWorking()) + // return; + + //SafeDeleteLock s(&d->sd); + + // grab the address list and destroy the qdns object + QValueList list = d->qdns->addresses(); + d->qdns->disconnect(this); + d->qdns->deleteLater(); + //d->sd.deleteLater(d->qdns); + d->qdns = 0; + + if(list.isEmpty()) { + if(d->aaaa) { + d->aaaa = false; + do_resolve(); + return; + } + //do_resolve(); + //return; + failed = true; + } + else + addr = list.first(); +#else + if(d->dns.result() == 0) + failed = true; + else + addr = QHostAddress(d->dns.result()); +#endif + + if(failed) { +#ifdef XMPP_DEBUG + printf("dns1\n"); +#endif + // using proxy? then try the unresolved host through the proxy + if(d->proxy.type() != Proxy::None) { +#ifdef XMPP_DEBUG + printf("dns1.1\n"); +#endif + do_connect(); + } + else if(d->using_srv) { +#ifdef XMPP_DEBUG + printf("dns1.2\n"); +#endif + if(d->servers.isEmpty()) { +#ifdef XMPP_DEBUG + printf("dns1.2.1\n"); +#endif + cleanup(); + d->errorCode = ErrConnectionRefused; + error(); + } + else { +#ifdef XMPP_DEBUG + printf("dns1.2.2\n"); +#endif + tryNextSrv(); + return; + } + } + else { +#ifdef XMPP_DEBUG + printf("dns1.3\n"); +#endif + cleanup(); + d->errorCode = ErrHostNotFound; + error(); + } + } + else { +#ifdef XMPP_DEBUG + printf("dns2\n"); +#endif + d->host = addr.toString(); + do_connect(); + } +} + +void AdvancedConnector::do_connect() +{ +#ifdef XMPP_DEBUG + printf("trying %s:%d\n", d->host.latin1(), d->port); +#endif + int t = d->proxy.type(); + if(t == Proxy::None) { +#ifdef XMPP_DEBUG + printf("do_connect1\n"); +#endif + BSocket *s = new BSocket; + d->bs = s; + connect(s, SIGNAL(connected()), SLOT(bs_connected())); + connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); + s->connectToHost(d->host, d->port); + } + else if(t == Proxy::HttpConnect) { +#ifdef XMPP_DEBUG + printf("do_connect2\n"); +#endif + HttpConnect *s = new HttpConnect; + d->bs = s; + connect(s, SIGNAL(connected()), SLOT(bs_connected())); + connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); + if(!d->proxy.user().isEmpty()) + s->setAuth(d->proxy.user(), d->proxy.pass()); + s->connectToHost(d->proxy.host(), d->proxy.port(), d->host, d->port); + } + else if(t == Proxy::Socks) { +#ifdef XMPP_DEBUG + printf("do_connect3\n"); +#endif + SocksClient *s = new SocksClient; + d->bs = s; + connect(s, SIGNAL(connected()), SLOT(bs_connected())); + connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); + if(!d->proxy.user().isEmpty()) + s->setAuth(d->proxy.user(), d->proxy.pass()); + s->connectToHost(d->proxy.host(), d->proxy.port(), d->host, d->port); + } +} + +void AdvancedConnector::tryNextSrv() +{ +#ifdef XMPP_DEBUG + printf("trying next srv\n"); +#endif + d->host = d->servers.first().name; + d->port = d->servers.first().port; + d->servers.remove(d->servers.begin()); + do_resolve(); +} + +void AdvancedConnector::srv_done() +{ + QGuardedPtr self = this; +#ifdef XMPP_DEBUG + printf("srv_done1\n"); +#endif + d->servers = d->srv.servers(); + if(d->servers.isEmpty()) { + srvResult(false); + if(!self) + return; + +#ifdef XMPP_DEBUG + printf("srv_done1.1\n"); +#endif + // fall back to A record + d->using_srv = false; + d->host = d->server; + if(d->opt_probe) { +#ifdef XMPP_DEBUG + printf("srv_done1.1.1\n"); +#endif + d->probe_mode = 0; + d->port = 5223; + d->will_be_ssl = true; + } + else { +#ifdef XMPP_DEBUG + printf("srv_done1.1.2\n"); +#endif + d->probe_mode = 1; + d->port = 5222; + } + do_resolve(); + return; + } + + srvResult(true); + if(!self) + return; + + d->using_srv = true; + tryNextSrv(); +} + +void AdvancedConnector::bs_connected() +{ + if(d->proxy.type() == Proxy::None) { + QHostAddress h = (static_cast(d->bs))->peerAddress(); + int p = (static_cast(d->bs))->peerPort(); + setPeerAddress(h, p); + } + + // only allow ssl override if proxy==poll or host:port + if((d->proxy.type() == Proxy::HttpPoll || !d->opt_host.isEmpty()) && d->opt_ssl) + setUseSSL(true); + else if(d->will_be_ssl) + setUseSSL(true); + + d->mode = Connected; + connected(); +} + +void AdvancedConnector::bs_error(int x) +{ + if(d->mode == Connected) { + d->errorCode = ErrStream; + error(); + return; + } + + bool proxyError = false; + int err = ErrConnectionRefused; + int t = d->proxy.type(); + +#ifdef XMPP_DEBUG + printf("bse1\n"); +#endif + + // figure out the error + if(t == Proxy::None) { + if(x == BSocket::ErrHostNotFound) + err = ErrHostNotFound; + else + err = ErrConnectionRefused; + } + else if(t == Proxy::HttpConnect) { + if(x == HttpConnect::ErrConnectionRefused) + err = ErrConnectionRefused; + else if(x == HttpConnect::ErrHostNotFound) + err = ErrHostNotFound; + else { + proxyError = true; + if(x == HttpConnect::ErrProxyAuth) + err = ErrProxyAuth; + else if(x == HttpConnect::ErrProxyNeg) + err = ErrProxyNeg; + else + err = ErrProxyConnect; + } + } + else if(t == Proxy::HttpPoll) { + if(x == HttpPoll::ErrConnectionRefused) + err = ErrConnectionRefused; + else if(x == HttpPoll::ErrHostNotFound) + err = ErrHostNotFound; + else { + proxyError = true; + if(x == HttpPoll::ErrProxyAuth) + err = ErrProxyAuth; + else if(x == HttpPoll::ErrProxyNeg) + err = ErrProxyNeg; + else + err = ErrProxyConnect; + } + } + else if(t == Proxy::Socks) { + if(x == SocksClient::ErrConnectionRefused) + err = ErrConnectionRefused; + else if(x == SocksClient::ErrHostNotFound) + err = ErrHostNotFound; + else { + proxyError = true; + if(x == SocksClient::ErrProxyAuth) + err = ErrProxyAuth; + else if(x == SocksClient::ErrProxyNeg) + err = ErrProxyNeg; + else + err = ErrProxyConnect; + } + } + + // no-multi or proxy error means we quit + if(!d->multi || proxyError) { + cleanup(); + d->errorCode = err; + error(); + return; + } + + if(d->using_srv && !d->servers.isEmpty()) { +#ifdef XMPP_DEBUG + printf("bse1.1\n"); +#endif + tryNextSrv(); + } + else if(!d->using_srv && d->opt_probe && d->probe_mode == 0) { +#ifdef XMPP_DEBUG + printf("bse1.2\n"); +#endif + d->probe_mode = 1; + d->port = 5222; + d->will_be_ssl = false; + do_connect(); + } + else { +#ifdef XMPP_DEBUG + printf("bse1.3\n"); +#endif + cleanup(); + d->errorCode = ErrConnectionRefused; + error(); + } +} + +void AdvancedConnector::http_syncStarted() +{ + httpSyncStarted(); +} + +void AdvancedConnector::http_syncFinished() +{ + httpSyncFinished(); +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.cpp new file mode 100644 index 00000000..4d7f9e41 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.cpp @@ -0,0 +1,670 @@ +/* + * hash.cpp - hashing functions for SHA1 and MD5 + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"hash.h" + +namespace XMPP +{ + +static bool bigEndian; +static bool haveEndian = false; + +static void ensureEndian() +{ + if(!haveEndian) { + haveEndian = true; + int wordSize; + qSysInfo(&wordSize, &bigEndian); + } +} + +//---------------------------------------------------------------------------- +// MD5 +//---------------------------------------------------------------------------- + +/* NOTE: the following code was modified to not need BYTE_ORDER -- Justin */ + +/* + Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id$ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.c is L. Peter Deutsch + . Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order + either statically or dynamically; added missing #include + in library. + 2002-03-11 lpd Corrected argument list for main(), and added int return + type, in test program and T value program. + 2002-02-21 lpd Added missing #include in test program. + 2000-07-03 lpd Patched to eliminate warnings about "constant is + unsigned in ANSI C, signed in traditional"; made test program + self-checking. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). + 1999-05-03 lpd Original version. + */ + +/* + * This package supports both compile-time and run-time determination of CPU + * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be + * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is + * defined as non-zero, the code will be compiled to run only on big-endian + * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to + * run on either big- or little-endian CPUs, but will run slightly less + * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. + */ + +typedef Q_UINT8 md5_byte_t; /* 8-bit byte */ +typedef Q_UINT32 md5_word_t; /* 32-bit word */ + +/* Define the state of the MD5 Algorithm. */ +typedef struct md5_state_s { + md5_word_t count[2]; /* message length in bits, lsw first */ + md5_word_t abcd[4]; /* digest buffer */ + md5_byte_t buf[64]; /* accumulate block */ +} md5_state_t; + +/* Initialize the algorithm. */ +void md5_init(md5_state_t *pms); + +/* Append a string to the message. */ +void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); + +/* Finish the message and return the digest. */ +void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); + +#define T_MASK ((md5_word_t)~0) +#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) +#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) +#define T3 0x242070db +#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) +#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) +#define T6 0x4787c62a +#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) +#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) +#define T9 0x698098d8 +#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) +#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) +#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) +#define T13 0x6b901122 +#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) +#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) +#define T16 0x49b40821 +#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) +#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) +#define T19 0x265e5a51 +#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) +#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) +#define T22 0x02441453 +#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) +#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) +#define T25 0x21e1cde6 +#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) +#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) +#define T28 0x455a14ed +#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) +#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) +#define T31 0x676f02d9 +#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) +#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) +#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) +#define T35 0x6d9d6122 +#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) +#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) +#define T38 0x4bdecfa9 +#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) +#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) +#define T41 0x289b7ec6 +#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) +#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) +#define T44 0x04881d05 +#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) +#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) +#define T47 0x1fa27cf8 +#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) +#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) +#define T50 0x432aff97 +#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) +#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) +#define T53 0x655b59c3 +#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) +#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) +#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) +#define T57 0x6fa87e4f +#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) +#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) +#define T60 0x4e0811a1 +#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) +#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) +#define T63 0x2ad7d2bb +#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) + + +static void +md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) +{ + md5_word_t + a = pms->abcd[0], b = pms->abcd[1], + c = pms->abcd[2], d = pms->abcd[3]; + md5_word_t t; + + /* Define storage for little-endian or both types of CPUs. */ + md5_word_t xbuf[16]; + const md5_word_t *X; + + { + if(bigEndian) + { + /* + * On big-endian machines, we must arrange the bytes in the + * right order. + */ + const md5_byte_t *xp = data; + int i; + + X = xbuf; /* (dynamic only) */ + + for (i = 0; i < 16; ++i, xp += 4) + xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); + } + else /* dynamic big-endian */ + { + /* + * On little-endian machines, we can process properly aligned + * data without copying it. + */ + if (!((data - (const md5_byte_t *)0) & 3)) { + /* data are properly aligned */ + X = (const md5_word_t *)data; + } else { + /* not aligned */ + memcpy(xbuf, data, 64); + X = xbuf; + } + } + } + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + /* Round 1. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ +#define F(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + F(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 7, T1); + SET(d, a, b, c, 1, 12, T2); + SET(c, d, a, b, 2, 17, T3); + SET(b, c, d, a, 3, 22, T4); + SET(a, b, c, d, 4, 7, T5); + SET(d, a, b, c, 5, 12, T6); + SET(c, d, a, b, 6, 17, T7); + SET(b, c, d, a, 7, 22, T8); + SET(a, b, c, d, 8, 7, T9); + SET(d, a, b, c, 9, 12, T10); + SET(c, d, a, b, 10, 17, T11); + SET(b, c, d, a, 11, 22, T12); + SET(a, b, c, d, 12, 7, T13); + SET(d, a, b, c, 13, 12, T14); + SET(c, d, a, b, 14, 17, T15); + SET(b, c, d, a, 15, 22, T16); +#undef SET + + /* Round 2. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ +#define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + G(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 1, 5, T17); + SET(d, a, b, c, 6, 9, T18); + SET(c, d, a, b, 11, 14, T19); + SET(b, c, d, a, 0, 20, T20); + SET(a, b, c, d, 5, 5, T21); + SET(d, a, b, c, 10, 9, T22); + SET(c, d, a, b, 15, 14, T23); + SET(b, c, d, a, 4, 20, T24); + SET(a, b, c, d, 9, 5, T25); + SET(d, a, b, c, 14, 9, T26); + SET(c, d, a, b, 3, 14, T27); + SET(b, c, d, a, 8, 20, T28); + SET(a, b, c, d, 13, 5, T29); + SET(d, a, b, c, 2, 9, T30); + SET(c, d, a, b, 7, 14, T31); + SET(b, c, d, a, 12, 20, T32); +#undef SET + + /* Round 3. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + H(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 5, 4, T33); + SET(d, a, b, c, 8, 11, T34); + SET(c, d, a, b, 11, 16, T35); + SET(b, c, d, a, 14, 23, T36); + SET(a, b, c, d, 1, 4, T37); + SET(d, a, b, c, 4, 11, T38); + SET(c, d, a, b, 7, 16, T39); + SET(b, c, d, a, 10, 23, T40); + SET(a, b, c, d, 13, 4, T41); + SET(d, a, b, c, 0, 11, T42); + SET(c, d, a, b, 3, 16, T43); + SET(b, c, d, a, 6, 23, T44); + SET(a, b, c, d, 9, 4, T45); + SET(d, a, b, c, 12, 11, T46); + SET(c, d, a, b, 15, 16, T47); + SET(b, c, d, a, 2, 23, T48); +#undef SET + + /* Round 4. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ +#define I(x, y, z) ((y) ^ ((x) | ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + I(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 6, T49); + SET(d, a, b, c, 7, 10, T50); + SET(c, d, a, b, 14, 15, T51); + SET(b, c, d, a, 5, 21, T52); + SET(a, b, c, d, 12, 6, T53); + SET(d, a, b, c, 3, 10, T54); + SET(c, d, a, b, 10, 15, T55); + SET(b, c, d, a, 1, 21, T56); + SET(a, b, c, d, 8, 6, T57); + SET(d, a, b, c, 15, 10, T58); + SET(c, d, a, b, 6, 15, T59); + SET(b, c, d, a, 13, 21, T60); + SET(a, b, c, d, 4, 6, T61); + SET(d, a, b, c, 11, 10, T62); + SET(c, d, a, b, 2, 15, T63); + SET(b, c, d, a, 9, 21, T64); +#undef SET + + /* Then perform the following additions. (That is increment each + of the four registers by the value it had before this block + was started.) */ + pms->abcd[0] += a; + pms->abcd[1] += b; + pms->abcd[2] += c; + pms->abcd[3] += d; +} + +void +md5_init(md5_state_t *pms) +{ + pms->count[0] = pms->count[1] = 0; + pms->abcd[0] = 0x67452301; + pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; + pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; + pms->abcd[3] = 0x10325476; +} + +void +md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) +{ + const md5_byte_t *p = data; + int left = nbytes; + int offset = (pms->count[0] >> 3) & 63; + md5_word_t nbits = (md5_word_t)(nbytes << 3); + + if (nbytes <= 0) + return; + + /* Update the message length. */ + pms->count[1] += nbytes >> 29; + pms->count[0] += nbits; + if (pms->count[0] < nbits) + pms->count[1]++; + + /* Process an initial partial block. */ + if (offset) { + int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); + + memcpy(pms->buf + offset, p, copy); + if (offset + copy < 64) + return; + p += copy; + left -= copy; + md5_process(pms, pms->buf); + } + + /* Process full blocks. */ + for (; left >= 64; p += 64, left -= 64) + md5_process(pms, p); + + /* Process a final partial block. */ + if (left) + memcpy(pms->buf, p, left); +} + +void +md5_finish(md5_state_t *pms, md5_byte_t digest[16]) +{ + static const md5_byte_t pad[64] = { + 0x80, 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 + }; + md5_byte_t data[8]; + int i; + + /* Save the length before padding. */ + for (i = 0; i < 8; ++i) + data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); + /* Pad to 56 bytes mod 64. */ + md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); + /* Append the length. */ + md5_append(pms, data, 8); + for (i = 0; i < 16; ++i) + digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); +} + + +//---------------------------------------------------------------------------- +// SHA1 - from a public domain implementation by Steve Reid (steve@edmweb.com) +//---------------------------------------------------------------------------- + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15]^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + +struct SHA1_CONTEXT +{ + Q_UINT32 state[5]; + Q_UINT32 count[2]; + unsigned char buffer[64]; +}; + +typedef union { + unsigned char c[64]; + Q_UINT32 l[16]; +} CHAR64LONG16; + +class SHA1Context : public QCA_HashContext +{ +public: + SHA1_CONTEXT _context; + CHAR64LONG16* block; + + SHA1Context() + { + reset(); + } + + QCA_HashContext *clone() + { + return new SHA1Context(*this); + } + + void reset() + { + sha1_init(&_context); + } + + void update(const char *in, unsigned int len) + { + sha1_update(&_context, (unsigned char *)in, (unsigned int)len); + } + + void final(QByteArray *out) + { + QByteArray b(20); + sha1_final((unsigned char *)b.data(), &_context); + *out = b; + } + + unsigned long blk0(Q_UINT32 i) + { + if(bigEndian) + return block->l[i]; + else + return (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) | (rol(block->l[i],8)&0x00FF00FF)); + } + + // Hash a single 512-bit block. This is the core of the algorithm. + void transform(Q_UINT32 state[5], unsigned char buffer[64]) + { + Q_UINT32 a, b, c, d, e; + + block = (CHAR64LONG16*)buffer; + + // Copy context->state[] to working vars + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + + // 4 rounds of 20 operations each. Loop unrolled. + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + + // Add the working vars back into context.state[] + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + + // Wipe variables + a = b = c = d = e = 0; + } + + // SHA1Init - Initialize new context + void sha1_init(SHA1_CONTEXT* context) + { + // SHA1 initialization constants + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; + } + + // Run your data through this + void sha1_update(SHA1_CONTEXT* context, unsigned char* data, Q_UINT32 len) + { + Q_UINT32 i, j; + + j = (context->count[0] >> 3) & 63; + if((context->count[0] += len << 3) < (len << 3)) + context->count[1]++; + + context->count[1] += (len >> 29); + + if((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); + } + + // Add padding and return the message digest + void sha1_final(unsigned char digest[20], SHA1_CONTEXT* context) + { + Q_UINT32 i, j; + unsigned char finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); // Endian independent + } + sha1_update(context, (unsigned char *)"\200", 1); + while ((context->count[0] & 504) != 448) { + sha1_update(context, (unsigned char *)"\0", 1); + } + sha1_update(context, finalcount, 8); // Should cause a transform() + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + + // Wipe variables + i = j = 0; + memset(context->buffer, 0, 64); + memset(context->state, 0, 20); + memset(context->count, 0, 8); + memset(&finalcount, 0, 8); + } +}; + +class MD5Context : public QCA_HashContext +{ +public: + MD5Context() + { + reset(); + } + + QCA_HashContext *clone() + { + return new MD5Context(*this); + } + + void reset() + { + md5_init(&md5); + } + + void update(const char *in, unsigned int len) + { + md5_append(&md5, (const md5_byte_t *)in, len); + } + + void final(QByteArray *out) + { + QByteArray b(16); + md5_finish(&md5, (md5_byte_t *)b.data()); + *out = b; + } + + md5_state_t md5; +}; + +class HashProvider : public QCAProvider +{ +public: + HashProvider() {} + ~HashProvider() {} + + void init() + { + ensureEndian(); + } + + int qcaVersion() const + { + return QCA_PLUGIN_VERSION; + } + + int capabilities() const + { + return (QCA::CAP_SHA1 | QCA::CAP_MD5); + } + + void *context(int cap) + { + if(cap == QCA::CAP_SHA1) + return new SHA1Context; + if(cap == QCA::CAP_MD5) + return new MD5Context; + return 0; + } +}; + +QCAProvider *createProviderHash() +{ + return (new HashProvider); +} + +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.h new file mode 100644 index 00000000..a4d2eea8 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.h @@ -0,0 +1,31 @@ +/* + * hash.h - hashing functions for SHA1 and MD5 + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef HASH_H +#define HASH_H + +#include"qcaprovider.h" + +namespace XMPP +{ + QCAProvider *createProviderHash(); +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/jid.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/jid.cpp new file mode 100644 index 00000000..29932513 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/jid.cpp @@ -0,0 +1,409 @@ +/* + * jid.cpp - class for verifying and manipulating Jabber IDs + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"xmpp.h" + +#include +#include + +using namespace XMPP; + +//---------------------------------------------------------------------------- +// StringPrepCache +//---------------------------------------------------------------------------- +class StringPrepCache +{ +public: + static bool nameprep(const QString &in, int maxbytes, QString *out) + { + if(in.isEmpty()) + { + if(out) + *out = QString(); + return true; + } + + StringPrepCache *that = get_instance(); + + Result *r = that->nameprep_table.find(in); + if(r) + { + if(!r->norm) + return false; + if(out) + *out = *(r->norm); + return true; + } + + QCString cs = in.utf8(); + cs.resize(maxbytes); + if(stringprep(cs.data(), maxbytes, (Stringprep_profile_flags)0, stringprep_nameprep) != 0) + { + that->nameprep_table.insert(in, new Result); + return false; + } + + QString norm = QString::fromUtf8(cs); + that->nameprep_table.insert(in, new Result(norm)); + if(out) + *out = norm; + return true; + } + + static bool nodeprep(const QString &in, int maxbytes, QString *out) + { + if(in.isEmpty()) + { + if(out) + *out = QString(); + return true; + } + + StringPrepCache *that = get_instance(); + + Result *r = that->nodeprep_table.find(in); + if(r) + { + if(!r->norm) + return false; + if(out) + *out = *(r->norm); + return true; + } + + QCString cs = in.utf8(); + cs.resize(maxbytes); + if(stringprep(cs.data(), maxbytes, (Stringprep_profile_flags)0, stringprep_xmpp_nodeprep) != 0) + { + that->nodeprep_table.insert(in, new Result); + return false; + } + + QString norm = QString::fromUtf8(cs); + that->nodeprep_table.insert(in, new Result(norm)); + if(out) + *out = norm; + return true; + } + + static bool resourceprep(const QString &in, int maxbytes, QString *out) + { + if(in.isEmpty()) + { + if(out) + *out = QString(); + return true; + } + + StringPrepCache *that = get_instance(); + + Result *r = that->resourceprep_table.find(in); + if(r) + { + if(!r->norm) + return false; + if(out) + *out = *(r->norm); + return true; + } + + QCString cs = in.utf8(); + cs.resize(maxbytes); + if(stringprep(cs.data(), maxbytes, (Stringprep_profile_flags)0, stringprep_xmpp_resourceprep) != 0) + { + that->resourceprep_table.insert(in, new Result); + return false; + } + + QString norm = QString::fromUtf8(cs); + that->resourceprep_table.insert(in, new Result(norm)); + if(out) + *out = norm; + return true; + } + +private: + class Result + { + public: + QString *norm; + + Result() : norm(0) + { + } + + Result(const QString &s) : norm(new QString(s)) + { + } + + ~Result() + { + delete norm; + } + }; + + QDict nameprep_table; + QDict nodeprep_table; + QDict resourceprep_table; + + static StringPrepCache *instance; + + static StringPrepCache *get_instance() + { + if(!instance) + instance = new StringPrepCache; + return instance; + } + + StringPrepCache() + { + nameprep_table.setAutoDelete(true); + nodeprep_table.setAutoDelete(true); + resourceprep_table.setAutoDelete(true); + } +}; + +StringPrepCache *StringPrepCache::instance = 0; + +//---------------------------------------------------------------------------- +// Jid +//---------------------------------------------------------------------------- +Jid::Jid() +{ + valid = false; +} + +Jid::~Jid() +{ +} + +Jid::Jid(const QString &s) +{ + set(s); +} + +Jid::Jid(const char *s) +{ + set(QString(s)); +} + +Jid & Jid::operator=(const QString &s) +{ + set(s); + return *this; +} + +Jid & Jid::operator=(const char *s) +{ + set(QString(s)); + return *this; +} + +void Jid::reset() +{ + f = QString(); + b = QString(); + d = QString(); + n = QString(); + r = QString(); + valid = false; +} + +void Jid::update() +{ + // build 'bare' and 'full' jids + if(n.isEmpty()) + b = d; + else + b = n + '@' + d; + + b=b.lower(); // JID are not case sensitive + + if(r.isEmpty()) + f = b; + else + f = b + '/' + r; + if(f.isEmpty()) + valid = false; +} + +void Jid::set(const QString &s) +{ + QString rest, domain, node, resource; + QString norm_domain, norm_node, norm_resource; + int x = s.find('/'); + if(x != -1) { + rest = s.mid(0, x); + resource = s.mid(x+1); + } + else { + rest = s; + resource = QString(); + } + if(!validResource(resource, &norm_resource)) { + reset(); + return; + } + + x = rest.find('@'); + if(x != -1) { + node = rest.mid(0, x); + domain = rest.mid(x+1); + } + else { + node = QString(); + domain = rest; + } + if(!validDomain(domain, &norm_domain) || !validNode(node, &norm_node)) { + reset(); + return; + } + + valid = true; + d = norm_domain; + n = norm_node; + r = norm_resource; + update(); +} + +void Jid::set(const QString &domain, const QString &node, const QString &resource) +{ + QString norm_domain, norm_node, norm_resource; + if(!validDomain(domain, &norm_domain) || !validNode(node, &norm_node) || !validResource(resource, &norm_resource)) { + reset(); + return; + } + valid = true; + d = norm_domain; + n = norm_node; + r = norm_resource; + update(); +} + +void Jid::setDomain(const QString &s) +{ + if(!valid) + return; + QString norm; + if(!validDomain(s, &norm)) { + reset(); + return; + } + d = norm; + update(); +} + +void Jid::setNode(const QString &s) +{ + if(!valid) + return; + QString norm; + if(!validNode(s, &norm)) { + reset(); + return; + } + n = norm; + update(); +} + +void Jid::setResource(const QString &s) +{ + if(!valid) + return; + QString norm; + if(!validResource(s, &norm)) { + reset(); + return; + } + r = norm; + update(); +} + +Jid Jid::withNode(const QString &s) const +{ + Jid j = *this; + j.setNode(s); + return j; +} + +Jid Jid::withResource(const QString &s) const +{ + Jid j = *this; + j.setResource(s); + return j; +} + +bool Jid::isValid() const +{ + return valid; +} + +bool Jid::isEmpty() const +{ + return f.isEmpty(); +} + +bool Jid::compare(const Jid &a, bool compareRes) const +{ + // only compare valid jids + if(!valid || !a.valid) + return false; + + if(compareRes ? (f != a.f) : (b != a.b)) + return false; + + return true; +} + +bool Jid::validDomain(const QString &s, QString *norm) +{ + /*QCString cs = s.utf8(); + cs.resize(1024); + if(stringprep(cs.data(), 1024, (Stringprep_profile_flags)0, stringprep_nameprep) != 0) + return false; + if(norm) + *norm = QString::fromUtf8(cs); + return true;*/ + return StringPrepCache::nameprep(s, 1024, norm); +} + +bool Jid::validNode(const QString &s, QString *norm) +{ + /*QCString cs = s.utf8(); + cs.resize(1024); + if(stringprep(cs.data(), 1024, (Stringprep_profile_flags)0, stringprep_xmpp_nodeprep) != 0) + return false; + if(norm) + *norm = QString::fromUtf8(cs); + return true;*/ + return StringPrepCache::nodeprep(s, 1024, norm); +} + +bool Jid::validResource(const QString &s, QString *norm) +{ + /*QCString cs = s.utf8(); + cs.resize(1024); + if(stringprep(cs.data(), 1024, (Stringprep_profile_flags)0, stringprep_xmpp_resourceprep) != 0) + return false; + if(norm) + *norm = QString::fromUtf8(cs); + return true;*/ + return StringPrepCache::resourceprep(s, 1024, norm); +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.cpp new file mode 100644 index 00000000..e1a64532 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.cpp @@ -0,0 +1,798 @@ +/* + * parser.cpp - parse an XMPP "document" + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + TODO: + + For XMPP::Parser to be "perfect", some things must be solved/changed in the + Qt library: + + - Fix weird QDomElement::haveAttributeNS() bug (patch submitted to + Trolltech on Aug 31st, 2003). + - Fix weird behavior in QXmlSimpleReader of reporting endElement() when + the '/' character of a self-closing tag is reached, instead of when + the final '>' is reached. + - Fix incremental parsing bugs in QXmlSimpleReader. At the moment, the + only bug I've found is related to attribute parsing, but there might + be more (search for '###' in $QTDIR/src/xml/qxml.cpp). + + We have workarounds for all of the above problems in the code below. + + - Deal with the processing instruction as an event type, so that we + can feed it back to the application properly. Right now it is completely + untrackable and is simply tacked into the first event's actualString. We + can't easily do this because QXmlSimpleReader eats an extra byte beyond + the processing instruction before reporting it. + + - Make QXmlInputSource capable of accepting data incrementally, to ensure + proper text encoding detection and processing over a network. This is + technically not a bug, as we have our own subclass below to do it, but + it would be nice if Qt had this already. +*/ + +#include"parser.h" + +#include +#include +#include + +using namespace XMPP; + +static bool qt_bug_check = false; +static bool qt_bug_have; + +//---------------------------------------------------------------------------- +// StreamInput +//---------------------------------------------------------------------------- +class StreamInput : public QXmlInputSource +{ +public: + StreamInput() + { + dec = 0; + reset(); + } + + ~StreamInput() + { + delete dec; + } + + void reset() + { + delete dec; + dec = 0; + in.resize(0); + out = ""; + at = 0; + paused = false; + mightChangeEncoding = true; + checkBad = true; + last = QChar(); + v_encoding = ""; + resetLastData(); + } + + void resetLastData() + { + last_string = ""; + } + + QString lastString() const + { + return last_string; + } + + void appendData(const QByteArray &a) + { + int oldsize = in.size(); + in.resize(oldsize + a.size()); + memcpy(in.data() + oldsize, a.data(), a.size()); + processBuf(); + } + + QChar lastRead() + { + return last; + } + + QChar next() + { + if(paused) + return EndOfData; + else + return readNext(); + } + + // NOTE: setting 'peek' to true allows the same char to be read again, + // however this still advances the internal byte processing. + QChar readNext(bool peek=false) + { + QChar c; + if(mightChangeEncoding) + c = EndOfData; + else { + if(out.isEmpty()) { + QString s; + if(!tryExtractPart(&s)) + c = EndOfData; + else { + out = s; + c = out[0]; + } + } + else + c = out[0]; + if(!peek) + out.remove(0, 1); + } + if(c == EndOfData) { +#ifdef XMPP_PARSER_DEBUG + printf("next() = EOD\n"); +#endif + } + else { +#ifdef XMPP_PARSER_DEBUG + printf("next() = [%c]\n", c.latin1()); +#endif + last = c; + } + + return c; + } + + QByteArray unprocessed() const + { + QByteArray a(in.size() - at); + memcpy(a.data(), in.data() + at, a.size()); + return a; + } + + void pause(bool b) + { + paused = b; + } + + bool isPaused() + { + return paused; + } + + QString encoding() const + { + return v_encoding; + } + +private: + QTextDecoder *dec; + QByteArray in; + QString out; + int at; + bool paused; + bool mightChangeEncoding; + QChar last; + QString v_encoding; + QString last_string; + bool checkBad; + + void processBuf() + { +#ifdef XMPP_PARSER_DEBUG + printf("processing. size=%d, at=%d\n", in.size(), at); +#endif + if(!dec) { + QTextCodec *codec = 0; + uchar *p = (uchar *)in.data() + at; + int size = in.size() - at; + + // do we have enough information to determine the encoding? + if(size == 0) + return; + bool utf16 = false; + if(p[0] == 0xfe || p[0] == 0xff) { + // probably going to be a UTF-16 byte order mark + if(size < 2) + return; + if((p[0] == 0xfe && p[1] == 0xff) || (p[0] == 0xff && p[1] == 0xfe)) { + // ok it is UTF-16 + utf16 = true; + } + } + if(utf16) + codec = QTextCodec::codecForMib(1000); // UTF-16 + else + codec = QTextCodec::codecForMib(106); // UTF-8 + + v_encoding = codec->name(); + dec = codec->makeDecoder(); + + // for utf16, put in the byte order mark + if(utf16) { + out += dec->toUnicode((const char *)p, 2); + at += 2; + } + } + + if(mightChangeEncoding) { + while(1) { + int n = out.find('<'); + if(n != -1) { + // we need a closing bracket + int n2 = out.find('>', n); + if(n2 != -1) { + ++n2; + QString h = out.mid(n, n2-n); + QString enc = processXmlHeader(h); + QTextCodec *codec = 0; + if(!enc.isEmpty()) + codec = QTextCodec::codecForName(enc.latin1()); + + // changing codecs + if(codec) { + v_encoding = codec->name(); + delete dec; + dec = codec->makeDecoder(); + } + mightChangeEncoding = false; + out.truncate(0); + at = 0; + resetLastData(); + break; + } + } + QString s; + if(!tryExtractPart(&s)) + break; + if(checkBad && checkForBadChars(s)) { + // go to the parser + mightChangeEncoding = false; + out.truncate(0); + at = 0; + resetLastData(); + break; + } + out += s; + } + } + } + + QString processXmlHeader(const QString &h) + { + if(h.left(5) != ""); + int startPos = h.find("encoding"); + if(startPos < endPos && startPos != -1) { + QString encoding; + do { + startPos++; + if(startPos > endPos) { + return ""; + } + } while(h[startPos] != '"' && h[startPos] != '\''); + startPos++; + while(h[startPos] != '"' && h[startPos] != '\'') { + encoding += h[startPos]; + startPos++; + if(startPos > endPos) { + return ""; + } + } + return encoding; + } + else + return ""; + } + + bool tryExtractPart(QString *s) + { + int size = in.size() - at; + if(size == 0) + return false; + uchar *p = (uchar *)in.data() + at; + QString nextChars; + while(1) { + nextChars = dec->toUnicode((const char *)p, 1); + ++p; + ++at; + if(!nextChars.isEmpty()) + break; + if(at == (int)in.size()) + return false; + } + last_string += nextChars; + *s = nextChars; + + // free processed data? + if(at >= 1024) { + char *p = in.data(); + int size = in.size() - at; + memmove(p, p + at, size); + in.resize(size); + at = 0; + } + + return true; + } + + bool checkForBadChars(const QString &s) + { + int len = s.find('<'); + if(len == -1) + len = s.length(); + else + checkBad = false; + for(int n = 0; n < len; ++n) { + if(!s.at(n).isSpace()) + return true; + } + return false; + } +}; + + +//---------------------------------------------------------------------------- +// ParserHandler +//---------------------------------------------------------------------------- +namespace XMPP +{ + class ParserHandler : public QXmlDefaultHandler + { + public: + ParserHandler(StreamInput *_in, QDomDocument *_doc) + { + in = _in; + doc = _doc; + needMore = false; + } + + ~ParserHandler() + { + eventList.setAutoDelete(true); + eventList.clear(); + } + + bool startDocument() + { + depth = 0; + return true; + } + + bool endDocument() + { + return true; + } + + bool startPrefixMapping(const QString &prefix, const QString &uri) + { + if(depth == 0) { + nsnames += prefix; + nsvalues += uri; + } + return true; + } + + bool startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts) + { + if(depth == 0) { + Parser::Event *e = new Parser::Event; + QXmlAttributes a; + for(int n = 0; n < atts.length(); ++n) { + QString uri = atts.uri(n); + QString ln = atts.localName(n); + if(a.index(uri, ln) == -1) + a.append(atts.qName(n), uri, ln, atts.value(n)); + } + e->setDocumentOpen(namespaceURI, localName, qName, a, nsnames, nsvalues); + nsnames.clear(); + nsvalues.clear(); + e->setActualString(in->lastString()); + + in->resetLastData(); + eventList.append(e); + in->pause(true); + } + else { + QDomElement e = doc->createElementNS(namespaceURI, qName); + for(int n = 0; n < atts.length(); ++n) { + QString uri = atts.uri(n); + QString ln = atts.localName(n); + bool have; + if(!uri.isEmpty()) { + have = e.hasAttributeNS(uri, ln); + if(qt_bug_have) + have = !have; + } + else + have = e.hasAttribute(ln); + if(!have) + e.setAttributeNS(uri, atts.qName(n), atts.value(n)); + } + + if(depth == 1) { + elem = e; + current = e; + } + else { + current.appendChild(e); + current = e; + } + } + ++depth; + return true; + } + + bool endElement(const QString &namespaceURI, const QString &localName, const QString &qName) + { + --depth; + if(depth == 0) { + Parser::Event *e = new Parser::Event; + e->setDocumentClose(namespaceURI, localName, qName); + e->setActualString(in->lastString()); + in->resetLastData(); + eventList.append(e); + in->pause(true); + } + else { + // done with a depth 1 element? + if(depth == 1) { + Parser::Event *e = new Parser::Event; + e->setElement(elem); + e->setActualString(in->lastString()); + in->resetLastData(); + eventList.append(e); + in->pause(true); + + elem = QDomElement(); + current = QDomElement(); + } + else + current = current.parentNode().toElement(); + } + + if(in->lastRead() == '/') + checkNeedMore(); + + return true; + } + + bool characters(const QString &str) + { + if(depth >= 1) { + QString content = str; + if(content.isEmpty()) + return true; + + if(!current.isNull()) { + QDomText text = doc->createTextNode(content); + current.appendChild(text); + } + } + return true; + } + + /*bool processingInstruction(const QString &target, const QString &data) + { + printf("Processing: [%s], [%s]\n", target.latin1(), data.latin1()); + in->resetLastData(); + return true; + }*/ + + void checkNeedMore() + { + // Here we will work around QXmlSimpleReader strangeness and self-closing tags. + // The problem is that endElement() is called when the '/' is read, not when + // the final '>' is read. This is a potential problem when obtaining unprocessed + // bytes from StreamInput after this event, as the '>' character will end up + // in the unprocessed chunk. To work around this, we need to advance StreamInput's + // internal byte processing, but not the xml character data. This way, the '>' + // will get processed and will no longer be in the unprocessed return, but + // QXmlSimpleReader can still read it. To do this, we call StreamInput::readNext + // with 'peek' mode. + QChar c = in->readNext(true); // peek + if(c == QXmlInputSource::EndOfData) { + needMore = true; + } + else { + // We'll assume the next char is a '>'. If it isn't, then + // QXmlSimpleReader will deal with that problem on the next + // parse. We don't need to take any action here. + needMore = false; + + // there should have been a pending event + Parser::Event *e = eventList.getFirst(); + if(e) { + e->setActualString(e->actualString() + '>'); + in->resetLastData(); + } + } + } + + Parser::Event *takeEvent() + { + if(needMore) + return 0; + if(eventList.isEmpty()) + return 0; + + Parser::Event *e = eventList.getFirst(); + eventList.removeRef(e); + in->pause(false); + return e; + } + + StreamInput *in; + QDomDocument *doc; + int depth; + QStringList nsnames, nsvalues; + QDomElement elem, current; + QPtrList eventList; + bool needMore; + }; +} + + +//---------------------------------------------------------------------------- +// Event +//---------------------------------------------------------------------------- +class Parser::Event::Private +{ +public: + int type; + QString ns, ln, qn; + QXmlAttributes a; + QDomElement e; + QString str; + QStringList nsnames, nsvalues; +}; + +Parser::Event::Event() +{ + d = 0; +} + +Parser::Event::Event(const Event &from) +{ + d = 0; + *this = from; +} + +Parser::Event & Parser::Event::operator=(const Event &from) +{ + delete d; + d = 0; + if(from.d) + d = new Private(*from.d); + return *this; +} + +Parser::Event::~Event() +{ + delete d; +} + +bool Parser::Event::isNull() const +{ + return (d ? false: true); +} + +int Parser::Event::type() const +{ + if(isNull()) + return -1; + return d->type; +} + +QString Parser::Event::nsprefix(const QString &s) const +{ + QStringList::ConstIterator it = d->nsnames.begin(); + QStringList::ConstIterator it2 = d->nsvalues.begin(); + for(; it != d->nsnames.end(); ++it) { + if((*it) == s) + return (*it2); + ++it2; + } + return QString::null; +} + +QString Parser::Event::namespaceURI() const +{ + return d->ns; +} + +QString Parser::Event::localName() const +{ + return d->ln; +} + +QString Parser::Event::qName() const +{ + return d->qn; +} + +QXmlAttributes Parser::Event::atts() const +{ + return d->a; +} + +QString Parser::Event::actualString() const +{ + return d->str; +} + +QDomElement Parser::Event::element() const +{ + return d->e; +} + +void Parser::Event::setDocumentOpen(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts, const QStringList &nsnames, const QStringList &nsvalues) +{ + if(!d) + d = new Private; + d->type = DocumentOpen; + d->ns = namespaceURI; + d->ln = localName; + d->qn = qName; + d->a = atts; + d->nsnames = nsnames; + d->nsvalues = nsvalues; +} + +void Parser::Event::setDocumentClose(const QString &namespaceURI, const QString &localName, const QString &qName) +{ + if(!d) + d = new Private; + d->type = DocumentClose; + d->ns = namespaceURI; + d->ln = localName; + d->qn = qName; +} + +void Parser::Event::setElement(const QDomElement &elem) +{ + if(!d) + d = new Private; + d->type = Element; + d->e = elem; +} + +void Parser::Event::setError() +{ + if(!d) + d = new Private; + d->type = Error; +} + +void Parser::Event::setActualString(const QString &str) +{ + d->str = str; +} + +//---------------------------------------------------------------------------- +// Parser +//---------------------------------------------------------------------------- +class Parser::Private +{ +public: + Private() + { + doc = 0; + in = 0; + handler = 0; + reader = 0; + reset(); + } + + ~Private() + { + reset(false); + } + + void reset(bool create=true) + { + delete reader; + delete handler; + delete in; + delete doc; + + if(create) { + doc = new QDomDocument; + in = new StreamInput; + handler = new ParserHandler(in, doc); + reader = new QXmlSimpleReader; + reader->setContentHandler(handler); + + // initialize the reader + in->pause(true); + reader->parse(in, true); + in->pause(false); + } + } + + QDomDocument *doc; + StreamInput *in; + ParserHandler *handler; + QXmlSimpleReader *reader; +}; + +Parser::Parser() +{ + d = new Private; + + // check for evil bug in Qt <= 3.2.1 + if(!qt_bug_check) { + qt_bug_check = true; + QDomElement e = d->doc->createElementNS("someuri", "somename"); + if(e.hasAttributeNS("someuri", "somename")) + qt_bug_have = true; + else + qt_bug_have = false; + } +} + +Parser::~Parser() +{ + delete d; +} + +void Parser::reset() +{ + d->reset(); +} + +void Parser::appendData(const QByteArray &a) +{ + d->in->appendData(a); + + // if handler was waiting for more, give it a kick + if(d->handler->needMore) + d->handler->checkNeedMore(); +} + +Parser::Event Parser::readNext() +{ + Event e; + if(d->handler->needMore) + return e; + Event *ep = d->handler->takeEvent(); + if(!ep) { + if(!d->reader->parseContinue()) { + e.setError(); + return e; + } + ep = d->handler->takeEvent(); + if(!ep) + return e; + } + e = *ep; + delete ep; + return e; +} + +QByteArray Parser::unprocessed() const +{ + return d->in->unprocessed(); +} + +QString Parser::encoding() const +{ + return d->in->encoding(); +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.h new file mode 100644 index 00000000..808b6c3d --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.h @@ -0,0 +1,86 @@ +/* + * parser.h - parse an XMPP "document" + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef PARSER_H +#define PARSER_H + +#include +#include + +namespace XMPP +{ + class Parser + { + public: + Parser(); + ~Parser(); + + class Event + { + public: + enum Type { DocumentOpen, DocumentClose, Element, Error }; + Event(); + Event(const Event &); + Event & operator=(const Event &); + ~Event(); + + bool isNull() const; + int type() const; + + // for document open + QString nsprefix(const QString &s=QString::null) const; + + // for document open / close + QString namespaceURI() const; + QString localName() const; + QString qName() const; + QXmlAttributes atts() const; + + // for element + QDomElement element() const; + + // for any + QString actualString() const; + + // setup + void setDocumentOpen(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts, const QStringList &nsnames, const QStringList &nsvalues); + void setDocumentClose(const QString &namespaceURI, const QString &localName, const QString &qName); + void setElement(const QDomElement &elem); + void setError(); + void setActualString(const QString &); + + private: + class Private; + Private *d; + }; + + void reset(); + void appendData(const QByteArray &a); + Event readNext(); + QByteArray unprocessed() const; + QString encoding() const; + + private: + class Private; + Private *d; + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.cpp new file mode 100644 index 00000000..dfd3253c --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.cpp @@ -0,0 +1,1595 @@ +/* + * protocol.cpp - XMPP-Core protocol state machine + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +// TODO: let the app know if tls is required +// require mutual auth for server out/in +// report ErrProtocol if server uses wrong NS +// use send() instead of writeElement() in CoreProtocol + +#include"protocol.h" + +#include +#include"base64.h" +#include"hash.h" + +#ifdef XMPP_TEST +#include"td.h" +#endif + +using namespace XMPP; + +// printArray +// +// This function prints out an array of bytes as latin characters, converting +// non-printable bytes into hex values as necessary. Useful for displaying +// QByteArrays for debugging purposes. +static QString printArray(const QByteArray &a) +{ + QString s; + for(uint n = 0; n < a.size(); ++n) { + unsigned char c = (unsigned char)a[(int)n]; + if(c < 32 || c >= 127) { + QString str; + str.sprintf("[%02x]", c); + s += str; + } + else + s += c; + } + return s; +} + +// firstChildElement +// +// Get an element's first child element +static QDomElement firstChildElement(const QDomElement &e) +{ + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + if(n.isElement()) + return n.toElement(); + } + return QDomElement(); +} + +//---------------------------------------------------------------------------- +// Version +//---------------------------------------------------------------------------- +Version::Version(int maj, int min) +{ + major = maj; + minor = min; +} + +//---------------------------------------------------------------------------- +// StreamFeatures +//---------------------------------------------------------------------------- +StreamFeatures::StreamFeatures() +{ + tls_supported = false; + sasl_supported = false; + bind_supported = false; + tls_required = false; +} + +//---------------------------------------------------------------------------- +// BasicProtocol +//---------------------------------------------------------------------------- +BasicProtocol::SASLCondEntry BasicProtocol::saslCondTable[] = +{ + { "aborted", Aborted }, + { "incorrect-encoding", IncorrectEncoding }, + { "invalid-authzid", InvalidAuthzid }, + { "invalid-mechanism", InvalidMech }, + { "mechanism-too-weak", MechTooWeak }, + { "not-authorized", NotAuthorized }, + { "temporary-auth-failure", TemporaryAuthFailure }, + { 0, 0 }, +}; + +BasicProtocol::StreamCondEntry BasicProtocol::streamCondTable[] = +{ + { "bad-format", BadFormat }, + { "bad-namespace-prefix", BadNamespacePrefix }, + { "conflict", Conflict }, + { "connection-timeout", ConnectionTimeout }, + { "host-gone", HostGone }, + { "host-unknown", HostUnknown }, + { "improper-addressing", ImproperAddressing }, + { "internal-server-error", InternalServerError }, + { "invalid-from", InvalidFrom }, + { "invalid-id", InvalidId }, + { "invalid-namespace", InvalidNamespace }, + { "invalid-xml", InvalidXml }, + { "not-authorized", StreamNotAuthorized }, + { "policy-violation", PolicyViolation }, + { "remote-connection-failed", RemoteConnectionFailed }, + { "resource-constraint", ResourceConstraint }, + { "restricted-xml", RestrictedXml }, + { "see-other-host", SeeOtherHost }, + { "system-shutdown", SystemShutdown }, + { "undefined-condition", UndefinedCondition }, + { "unsupported-encoding", UnsupportedEncoding }, + { "unsupported-stanza-type", UnsupportedStanzaType }, + { "unsupported-version", UnsupportedVersion }, + { "xml-not-well-formed", XmlNotWellFormed }, + { 0, 0 }, +}; + +BasicProtocol::BasicProtocol() +:XmlProtocol() +{ + init(); +} + +BasicProtocol::~BasicProtocol() +{ +} + +void BasicProtocol::init() +{ + errCond = -1; + sasl_authed = false; + doShutdown = false; + delayedError = false; + closeError = false; + ready = false; + stanzasPending = 0; + stanzasWritten = 0; +} + +void BasicProtocol::reset() +{ + XmlProtocol::reset(); + init(); + + to = QString(); + from = QString(); + id = QString(); + lang = QString(); + version = Version(1,0); + errText = QString(); + errAppSpec = QDomElement(); + otherHost = QString(); + spare.resize(0); + sasl_mech = QString(); + sasl_mechlist.clear(); + sasl_step.resize(0); + stanzaToRecv = QDomElement(); + sendList.clear(); +} + +void BasicProtocol::sendStanza(const QDomElement &e) +{ + SendItem i; + i.stanzaToSend = e; + sendList += i; +} + +void BasicProtocol::sendDirect(const QString &s) +{ + SendItem i; + i.stringToSend = s; + sendList += i; +} + +void BasicProtocol::sendWhitespace() +{ + SendItem i; + i.doWhitespace = true; + sendList += i; +} + +QDomElement BasicProtocol::recvStanza() +{ + QDomElement e = stanzaToRecv; + stanzaToRecv = QDomElement(); + return e; +} + +void BasicProtocol::shutdown() +{ + doShutdown = true; +} + +void BasicProtocol::shutdownWithError(int cond, const QString &str) +{ + otherHost = str; + delayErrorAndClose(cond); +} + +bool BasicProtocol::isReady() const +{ + return ready; +} + +void BasicProtocol::setReady(bool b) +{ + ready = b; +} + +QString BasicProtocol::saslMech() const +{ + return sasl_mech; +} + +QByteArray BasicProtocol::saslStep() const +{ + return sasl_step; +} + +void BasicProtocol::setSASLMechList(const QStringList &list) +{ + sasl_mechlist = list; +} + +void BasicProtocol::setSASLFirst(const QString &mech, const QByteArray &step) +{ + sasl_mech = mech; + sasl_step = step; +} + +void BasicProtocol::setSASLNext(const QByteArray &step) +{ + sasl_step = step; +} + +void BasicProtocol::setSASLAuthed() +{ + sasl_authed = true; +} + +int BasicProtocol::stringToSASLCond(const QString &s) +{ + for(int n = 0; saslCondTable[n].str; ++n) { + if(s == saslCondTable[n].str) + return saslCondTable[n].cond; + } + return -1; +} + +int BasicProtocol::stringToStreamCond(const QString &s) +{ + for(int n = 0; streamCondTable[n].str; ++n) { + if(s == streamCondTable[n].str) + return streamCondTable[n].cond; + } + return -1; +} + +QString BasicProtocol::saslCondToString(int x) +{ + for(int n = 0; saslCondTable[n].str; ++n) { + if(x == saslCondTable[n].cond) + return saslCondTable[n].str; + } + return QString(); +} + +QString BasicProtocol::streamCondToString(int x) +{ + for(int n = 0; streamCondTable[n].str; ++n) { + if(x == streamCondTable[n].cond) + return streamCondTable[n].str; + } + return QString(); +} + +void BasicProtocol::extractStreamError(const QDomElement &e) +{ + QString text; + QDomElement appSpec; + + QDomElement t = firstChildElement(e); + if(t.isNull() || t.namespaceURI() != NS_STREAMS) { + // probably old-style error + errCond = -1; + errText = e.text(); + } + else + errCond = stringToStreamCond(t.tagName()); + + if(errCond != -1) { + if(errCond == SeeOtherHost) + otherHost = t.text(); + + t = e.elementsByTagNameNS(NS_STREAMS, "text").item(0).toElement(); + if(!t.isNull()) + text = t.text(); + + // find first non-standard namespaced element + QDomNodeList nl = e.childNodes(); + for(uint n = 0; n < nl.count(); ++n) { + QDomNode i = nl.item(n); + if(i.isElement() && i.namespaceURI() != NS_STREAMS) { + appSpec = i.toElement(); + break; + } + } + + errText = text; + errAppSpec = appSpec; + } +} + +void BasicProtocol::send(const QDomElement &e, bool clip) +{ + writeElement(e, TypeElement, false, clip); +} + +void BasicProtocol::sendStreamError(int cond, const QString &text, const QDomElement &appSpec) +{ + QDomElement se = doc.createElementNS(NS_ETHERX, "stream:error"); + QDomElement err = doc.createElementNS(NS_STREAMS, streamCondToString(cond)); + if(!otherHost.isEmpty()) + err.appendChild(doc.createTextNode(otherHost)); + se.appendChild(err); + if(!text.isEmpty()) { + QDomElement te = doc.createElementNS(NS_STREAMS, "text"); + te.setAttributeNS(NS_XML, "xml:lang", "en"); + te.appendChild(doc.createTextNode(text)); + se.appendChild(te); + } + se.appendChild(appSpec); + + writeElement(se, 100, false); +} + +void BasicProtocol::sendStreamError(const QString &text) +{ + QDomElement se = doc.createElementNS(NS_ETHERX, "stream:error"); + se.appendChild(doc.createTextNode(text)); + + writeElement(se, 100, false); +} + +bool BasicProtocol::errorAndClose(int cond, const QString &text, const QDomElement &appSpec) +{ + closeError = true; + errCond = cond; + errText = text; + errAppSpec = appSpec; + sendStreamError(cond, text, appSpec); + return close(); +} + +bool BasicProtocol::error(int code) +{ + event = EError; + errorCode = code; + return true; +} + +void BasicProtocol::delayErrorAndClose(int cond, const QString &text, const QDomElement &appSpec) +{ + errorCode = ErrStream; + errCond = cond; + errText = text; + errAppSpec = appSpec; + delayedError = true; +} + +void BasicProtocol::delayError(int code) +{ + errorCode = code; + delayedError = true; +} + +QDomElement BasicProtocol::docElement() +{ + // create the root element + QDomElement e = doc.createElementNS(NS_ETHERX, "stream:stream"); + + QString defns = defaultNamespace(); + QStringList list = extraNamespaces(); + + // HACK: using attributes seems to be the only way to get additional namespaces in here + if(!defns.isEmpty()) + e.setAttribute("xmlns", defns); + for(QStringList::ConstIterator it = list.begin(); it != list.end();) { + QString prefix = *(it++); + QString uri = *(it++); + e.setAttribute(QString("xmlns:") + prefix, uri); + } + + // additional attributes + if(!isIncoming() && !to.isEmpty()) + e.setAttribute("to", to); + if(isIncoming() && !from.isEmpty()) + e.setAttribute("from", from); + if(!id.isEmpty()) + e.setAttribute("id", id); + if(!lang.isEmpty()) + e.setAttributeNS(NS_XML, "xml:lang", lang); + if(version.major > 0 || version.minor > 0) + e.setAttribute("version", QString::number(version.major) + '.' + QString::number(version.minor)); + + return e; +} + +void BasicProtocol::handleDocOpen(const Parser::Event &pe) +{ + if(isIncoming()) { + if(xmlEncoding() != "UTF-8") { + delayErrorAndClose(UnsupportedEncoding); + return; + } + } + + if(pe.namespaceURI() == NS_ETHERX && pe.localName() == "stream") { + QXmlAttributes atts = pe.atts(); + + // grab the version + int major = 0; + int minor = 0; + QString verstr = atts.value("version"); + if(!verstr.isEmpty()) { + int n = verstr.find('.'); + if(n != -1) { + major = verstr.mid(0, n).toInt(); + minor = verstr.mid(n+1).toInt(); + } + else { + major = verstr.toInt(); + minor = 0; + } + } + version = Version(major, minor); + + if(isIncoming()) { + to = atts.value("to"); + QString peerLang = atts.value(NS_XML, "lang"); + if(!peerLang.isEmpty()) + lang = peerLang; + } + // outgoing + else { + from = atts.value("from"); + lang = atts.value(NS_XML, "lang"); + id = atts.value("id"); + } + + handleStreamOpen(pe); + } + else { + if(isIncoming()) + delayErrorAndClose(BadFormat); + else + delayError(ErrProtocol); + } +} + +bool BasicProtocol::handleError() +{ + if(isIncoming()) + return errorAndClose(XmlNotWellFormed); + else + return error(ErrParse); +} + +bool BasicProtocol::handleCloseFinished() +{ + if(closeError) { + event = EError; + errorCode = ErrStream; + // note: errCond and friends are already set at this point + } + else + event = EClosed; + return true; +} + +bool BasicProtocol::doStep(const QDomElement &e) +{ + // handle pending error + if(delayedError) { + if(isIncoming()) + return errorAndClose(errCond, errText, errAppSpec); + else + return error(errorCode); + } + + // shutdown? + if(doShutdown) { + doShutdown = false; + return close(); + } + + if(!e.isNull()) { + // check for error + if(e.namespaceURI() == NS_ETHERX && e.tagName() == "error") { + extractStreamError(e); + return error(ErrStream); + } + } + + if(ready) { + // stanzas written? + if(stanzasWritten > 0) { + --stanzasWritten; + event = EStanzaSent; + return true; + } + // send items? + if(!sendList.isEmpty()) { + SendItem i; + { + QValueList::Iterator it = sendList.begin(); + i = (*it); + sendList.remove(it); + } + + // outgoing stanza? + if(!i.stanzaToSend.isNull()) { + ++stanzasPending; + writeElement(i.stanzaToSend, TypeStanza, true); + event = ESend; + } + // direct send? + else if(!i.stringToSend.isEmpty()) { + writeString(i.stringToSend, TypeDirect, true); + event = ESend; + } + // whitespace keepalive? + else if(i.doWhitespace) { + writeString("\n", TypePing, false); + event = ESend; + } + return true; + } + else { + // if we have pending outgoing stanzas, ask for write notification + if(stanzasPending) + notify |= NSend; + } + } + + return doStep2(e); +} + +void BasicProtocol::itemWritten(int id, int) +{ + if(id == TypeStanza) { + --stanzasPending; + ++stanzasWritten; + } +} + +QString BasicProtocol::defaultNamespace() +{ + // default none + return QString(); +} + +QStringList BasicProtocol::extraNamespaces() +{ + // default none + return QStringList(); +} + +void BasicProtocol::handleStreamOpen(const Parser::Event &) +{ + // default does nothing +} + +//---------------------------------------------------------------------------- +// CoreProtocol +//---------------------------------------------------------------------------- +CoreProtocol::CoreProtocol() +:BasicProtocol() +{ + init(); +} + +CoreProtocol::~CoreProtocol() +{ +} + +void CoreProtocol::init() +{ + step = Start; + + // ?? + server = false; + dialback = false; + dialback_verify = false; + + // settings + jid = Jid(); + password = QString(); + oldOnly = false; + allowPlain = false; + doTLS = true; + doAuth = true; + doBinding = true; + + // input + user = QString(); + host = QString(); + + // status + old = false; + digest = false; + tls_started = false; + sasl_started = false; +} + +void CoreProtocol::reset() +{ + BasicProtocol::reset(); + init(); +} + +void CoreProtocol::startClientOut(const Jid &_jid, bool _oldOnly, bool tlsActive, bool _doAuth) +{ + jid = _jid; + to = _jid.domain(); + oldOnly = _oldOnly; + doAuth = _doAuth; + tls_started = tlsActive; + + if(oldOnly) + version = Version(0,0); + startConnect(); +} + +void CoreProtocol::startServerOut(const QString &_to) +{ + server = true; + to = _to; + startConnect(); +} + +void CoreProtocol::startDialbackOut(const QString &_to, const QString &_from) +{ + server = true; + dialback = true; + to = _to; + self_from = _from; + startConnect(); +} + +void CoreProtocol::startDialbackVerifyOut(const QString &_to, const QString &_from, const QString &id, const QString &key) +{ + server = true; + dialback = true; + dialback_verify = true; + to = _to; + self_from = _from; + dialback_id = id; + dialback_key = key; + startConnect(); +} + +void CoreProtocol::startClientIn(const QString &_id) +{ + id = _id; + startAccept(); +} + +void CoreProtocol::startServerIn(const QString &_id) +{ + server = true; + id = _id; + startAccept(); +} + +void CoreProtocol::setLang(const QString &s) +{ + lang = s; +} + +void CoreProtocol::setAllowTLS(bool b) +{ + doTLS = b; +} + +void CoreProtocol::setAllowBind(bool b) +{ + doBinding = b; +} + +void CoreProtocol::setAllowPlain(bool b) +{ + allowPlain = b; +} + +void CoreProtocol::setPassword(const QString &s) +{ + password = s; +} + +void CoreProtocol::setFrom(const QString &s) +{ + from = s; +} + +void CoreProtocol::setDialbackKey(const QString &s) +{ + dialback_key = s; +} + +bool CoreProtocol::loginComplete() +{ + setReady(true); + + event = EReady; + step = Done; + return true; +} + +int CoreProtocol::getOldErrorCode(const QDomElement &e) +{ + QDomElement err = e.elementsByTagNameNS(NS_CLIENT, "error").item(0).toElement(); + if(err.isNull() || !err.hasAttribute("code")) + return -1; + return err.attribute("code").toInt(); +} + +/*QString CoreProtocol::xmlToString(const QDomElement &e, bool clip) +{ + // determine an appropriate 'fakeNS' to use + QString ns; + if(e.prefix() == "stream") + ns = NS_ETHERX; + else if(e.prefix() == "db") + ns = NS_DIALBACK; + else + ns = NS_CLIENT; + return ::xmlToString(e, ns, "stream:stream", clip); +}*/ + +bool CoreProtocol::stepAdvancesParser() const +{ + if(stepRequiresElement()) + return true; + else if(isReady()) + return true; + return false; +} + +// all element-needing steps need to be registered here +bool CoreProtocol::stepRequiresElement() const +{ + switch(step) { + case GetFeatures: + case GetTLSProceed: + case GetSASLChallenge: + case GetBindResponse: + case GetAuthGetResponse: + case GetAuthSetResponse: + case GetRequest: + case GetSASLResponse: + return true; + } + return false; +} + +void CoreProtocol::stringSend(const QString &s) +{ +#ifdef XMPP_TEST + TD::outgoingTag(s); +#endif +} + +void CoreProtocol::stringRecv(const QString &s) +{ +#ifdef XMPP_TEST + TD::incomingTag(s); +#endif +} + +QString CoreProtocol::defaultNamespace() +{ + if(server) + return NS_SERVER; + else + return NS_CLIENT; +} + +QStringList CoreProtocol::extraNamespaces() +{ + QStringList list; + if(dialback) { + list += "db"; + list += NS_DIALBACK; + } + return list; +} + +void CoreProtocol::handleStreamOpen(const Parser::Event &pe) +{ + if(isIncoming()) { + QString ns = pe.nsprefix(); + QString db; + if(server) { + db = pe.nsprefix("db"); + if(!db.isEmpty()) + dialback = true; + } + + // verify namespace + if((!server && ns != NS_CLIENT) || (server && ns != NS_SERVER) || (dialback && db != NS_DIALBACK)) { + delayErrorAndClose(InvalidNamespace); + return; + } + + // verify version + if(version.major < 1 && !dialback) { + delayErrorAndClose(UnsupportedVersion); + return; + } + } + else { + if(!dialback) { + if(version.major >= 1 && !oldOnly) + old = false; + else + old = true; + } + } +} + +void CoreProtocol::elementSend(const QDomElement &e) +{ +#ifdef XMPP_TEST + TD::outgoingXml(e); +#endif +} + +void CoreProtocol::elementRecv(const QDomElement &e) +{ +#ifdef XMPP_TEST + TD::incomingXml(e); +#endif +} + +bool CoreProtocol::doStep2(const QDomElement &e) +{ + if(dialback) + return dialbackStep(e); + else + return normalStep(e); +} + +bool CoreProtocol::isValidStanza(const QDomElement &e) const +{ + QString s = e.tagName(); + if(e.namespaceURI() == (server ? NS_SERVER : NS_CLIENT) && (s == "message" || s == "presence" || s == "iq")) + return true; + else + return false; +} + +bool CoreProtocol::grabPendingItem(const Jid &to, const Jid &from, int type, DBItem *item) +{ + for(QValueList::Iterator it = dbpending.begin(); it != dbpending.end(); ++it) { + const DBItem &i = *it; + if(i.type == type && i.to.compare(to) && i.from.compare(from)) { + const DBItem &i = (*it); + *item = i; + dbpending.remove(it); + return true; + } + } + return false; +} + +bool CoreProtocol::dialbackStep(const QDomElement &e) +{ + if(step == Start) { + setReady(true); + step = Done; + event = EReady; + return true; + } + + if(!dbrequests.isEmpty()) { + // process a request + DBItem i; + { + QValueList::Iterator it = dbrequests.begin(); + i = (*it); + dbrequests.remove(it); + } + + QDomElement r; + if(i.type == DBItem::ResultRequest) { + r = doc.createElementNS(NS_DIALBACK, "db:result"); + r.setAttribute("to", i.to.full()); + r.setAttribute("from", i.from.full()); + r.appendChild(doc.createTextNode(i.key)); + dbpending += i; + } + else if(i.type == DBItem::ResultGrant) { + r = doc.createElementNS(NS_DIALBACK, "db:result"); + r.setAttribute("to", i.to.full()); + r.setAttribute("from", i.from.full()); + r.setAttribute("type", i.ok ? "valid" : "invalid"); + if(i.ok) { + i.type = DBItem::Validated; + dbvalidated += i; + } + else { + // TODO: disconnect after writing element + } + } + else if(i.type == DBItem::VerifyRequest) { + r = doc.createElementNS(NS_DIALBACK, "db:verify"); + r.setAttribute("to", i.to.full()); + r.setAttribute("from", i.from.full()); + r.setAttribute("id", i.id); + r.appendChild(doc.createTextNode(i.key)); + dbpending += i; + } + // VerifyGrant + else { + r = doc.createElementNS(NS_DIALBACK, "db:verify"); + r.setAttribute("to", i.to.full()); + r.setAttribute("from", i.from.full()); + r.setAttribute("id", i.id); + r.setAttribute("type", i.ok ? "valid" : "invalid"); + } + + writeElement(r, TypeElement, false); + event = ESend; + return true; + } + + if(!e.isNull()) { + if(e.namespaceURI() == NS_DIALBACK) { + if(e.tagName() == "result") { + Jid to, from; + to.set(e.attribute("to"), ""); + from.set(e.attribute("from"), ""); + if(isIncoming()) { + QString key = e.text(); + // TODO: report event + } + else { + bool ok = (e.attribute("type") == "valid") ? true: false; + DBItem i; + if(grabPendingItem(from, to, DBItem::ResultRequest, &i)) { + if(ok) { + i.type = DBItem::Validated; + i.ok = true; + dbvalidated += i; + // TODO: report event + } + else { + // TODO: report event + } + } + } + } + else if(e.tagName() == "verify") { + Jid to, from; + to.set(e.attribute("to"), ""); + from.set(e.attribute("from"), ""); + QString id = e.attribute("id"); + if(isIncoming()) { + QString key = e.text(); + // TODO: report event + } + else { + bool ok = (e.attribute("type") == "valid") ? true: false; + DBItem i; + if(grabPendingItem(from, to, DBItem::VerifyRequest, &i)) { + if(ok) { + // TODO: report event + } + else { + // TODO: report event + } + } + } + } + } + else { + if(isReady()) { + if(isValidStanza(e)) { + // TODO: disconnect if stanza is from unverified sender + // TODO: ignore packets from receiving servers + stanzaToRecv = e; + event = EStanzaReady; + return true; + } + } + } + } + + need = NNotify; + notify |= NRecv; + return false; +} + +bool CoreProtocol::normalStep(const QDomElement &e) +{ + if(step == Start) { + if(isIncoming()) { + need = NSASLMechs; + step = SendFeatures; + return false; + } + else { + if(old) { + if(doAuth) + step = HandleAuthGet; + else + return loginComplete(); + } + else + step = GetFeatures; + + return processStep(); + } + } + else if(step == HandleFeatures) { + // deal with TLS? + if(doTLS && !tls_started && !sasl_authed && features.tls_supported) { + QDomElement e = doc.createElementNS(NS_TLS, "starttls"); + + send(e, true); + event = ESend; + step = GetTLSProceed; + return true; + } + + // deal with SASL? + if(!sasl_authed) { + if(!features.sasl_supported) { + // SASL MUST be supported + event = EError; + errorCode = ErrProtocol; + return true; + } + +#ifdef XMPP_TEST + TD::msg("starting SASL authentication..."); +#endif + need = NSASLFirst; + step = GetSASLFirst; + return false; + } + + if(server) { + return loginComplete(); + } + else { + if(!doBinding) + return loginComplete(); + } + + // deal with bind + if(!features.bind_supported) { + // bind MUST be supported + event = EError; + errorCode = ErrProtocol; + return true; + } + + QDomElement e = doc.createElement("iq"); + e.setAttribute("type", "set"); + e.setAttribute("id", "bind_1"); + QDomElement b = doc.createElementNS(NS_BIND, "bind"); + + // request specific resource? + QString resource = jid.resource(); + if(!resource.isEmpty()) { + QDomElement r = doc.createElement("resource"); + r.appendChild(doc.createTextNode(jid.resource())); + b.appendChild(r); + } + + e.appendChild(b); + + send(e); + event = ESend; + step = GetBindResponse; + return true; + } + else if(step == GetSASLFirst) { + QDomElement e = doc.createElementNS(NS_SASL, "auth"); + e.setAttribute("mechanism", sasl_mech); + if(!sasl_step.isEmpty()) { +#ifdef XMPP_TEST + TD::msg(QString("SASL OUT: [%1]").arg(printArray(sasl_step))); +#endif + e.appendChild(doc.createTextNode(Base64::arrayToString(sasl_step))); + } + + send(e, true); + event = ESend; + step = GetSASLChallenge; + return true; + } + else if(step == GetSASLNext) { + if(isIncoming()) { + if(sasl_authed) { + QDomElement e = doc.createElementNS(NS_SASL, "success"); + writeElement(e, TypeElement, false, true); + event = ESend; + step = IncHandleSASLSuccess; + return true; + } + else { + QByteArray stepData = sasl_step; + QDomElement e = doc.createElementNS(NS_SASL, "challenge"); + if(!stepData.isEmpty()) + e.appendChild(doc.createTextNode(Base64::arrayToString(stepData))); + + writeElement(e, TypeElement, false, true); + event = ESend; + step = GetSASLResponse; + return true; + } + } + else { + QByteArray stepData = sasl_step; +#ifdef XMPP_TEST + TD::msg(QString("SASL OUT: [%1]").arg(printArray(sasl_step))); +#endif + QDomElement e = doc.createElementNS(NS_SASL, "response"); + if(!stepData.isEmpty()) + e.appendChild(doc.createTextNode(Base64::arrayToString(stepData))); + + send(e, true); + event = ESend; + step = GetSASLChallenge; + return true; + } + } + else if(step == HandleSASLSuccess) { + need = NSASLLayer; + spare = resetStream(); + step = Start; + return false; + } + else if(step == HandleAuthGet) { + QDomElement e = doc.createElement("iq"); + e.setAttribute("to", to); + e.setAttribute("type", "get"); + e.setAttribute("id", "auth_1"); + QDomElement q = doc.createElementNS("jabber:iq:auth", "query"); + QDomElement u = doc.createElement("username"); + u.appendChild(doc.createTextNode(jid.node())); + q.appendChild(u); + e.appendChild(q); + + send(e); + event = ESend; + step = GetAuthGetResponse; + return true; + } + else if(step == HandleAuthSet) { + QDomElement e = doc.createElement("iq"); + e.setAttribute("to", to); + e.setAttribute("type", "set"); + e.setAttribute("id", "auth_2"); + QDomElement q = doc.createElementNS("jabber:iq:auth", "query"); + QDomElement u = doc.createElement("username"); + u.appendChild(doc.createTextNode(jid.node())); + q.appendChild(u); + QDomElement p; + if(digest) { + // need SHA1 here + if(!QCA::isSupported(QCA::CAP_SHA1)) + QCA::insertProvider(createProviderHash()); + + p = doc.createElement("digest"); + QCString cs = id.utf8() + password.utf8(); + p.appendChild(doc.createTextNode(QCA::SHA1::hashToString(cs))); + } + else { + p = doc.createElement("password"); + p.appendChild(doc.createTextNode(password)); + } + q.appendChild(p); + QDomElement r = doc.createElement("resource"); + r.appendChild(doc.createTextNode(jid.resource())); + q.appendChild(r); + e.appendChild(q); + + send(e, true); + event = ESend; + step = GetAuthSetResponse; + return true; + } + // server + else if(step == SendFeatures) { + QDomElement f = doc.createElementNS(NS_ETHERX, "stream:features"); + if(!tls_started && !sasl_authed) { // don't offer tls if we are already sasl'd + QDomElement tls = doc.createElementNS(NS_TLS, "starttls"); + f.appendChild(tls); + } + + if(sasl_authed) { + if(!server) { + QDomElement bind = doc.createElementNS(NS_BIND, "bind"); + f.appendChild(bind); + } + } + else { + QDomElement mechs = doc.createElementNS(NS_SASL, "mechanisms"); + for(QStringList::ConstIterator it = sasl_mechlist.begin(); it != sasl_mechlist.end(); ++it) { + QDomElement m = doc.createElement("mechanism"); + m.appendChild(doc.createTextNode(*it)); + mechs.appendChild(m); + } + f.appendChild(mechs); + } + + writeElement(f, TypeElement, false); + event = ESend; + step = GetRequest; + return true; + } + // server + else if(step == HandleTLS) { + tls_started = true; + need = NStartTLS; + spare = resetStream(); + step = Start; + return false; + } + // server + else if(step == IncHandleSASLSuccess) { + event = ESASLSuccess; + spare = resetStream(); + step = Start; + printf("sasl success\n"); + return true; + } + else if(step == GetFeatures) { + // we are waiting for stream features + if(e.namespaceURI() == NS_ETHERX && e.tagName() == "features") { + // extract features + StreamFeatures f; + QDomElement s = e.elementsByTagNameNS(NS_TLS, "starttls").item(0).toElement(); + if(!s.isNull()) { + f.tls_supported = true; + f.tls_required = s.elementsByTagNameNS(NS_TLS, "required").count() > 0; + } + QDomElement m = e.elementsByTagNameNS(NS_SASL, "mechanisms").item(0).toElement(); + if(!m.isNull()) { + f.sasl_supported = true; + QDomNodeList l = m.elementsByTagNameNS(NS_SASL, "mechanism"); + for(uint n = 0; n < l.count(); ++n) + f.sasl_mechs += l.item(n).toElement().text(); + } + QDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement(); + if(!b.isNull()) + f.bind_supported = true; + + if(f.tls_supported) { +#ifdef XMPP_TEST + QString s = "STARTTLS is available"; + if(f.tls_required) + s += " (required)"; + TD::msg(s); +#endif + } + if(f.sasl_supported) { +#ifdef XMPP_TEST + QString s = "SASL mechs:"; + for(QStringList::ConstIterator it = f.sasl_mechs.begin(); it != f.sasl_mechs.end(); ++it) + s += QString(" [%1]").arg((*it)); + TD::msg(s); +#endif + } + + if(doAuth) { + event = EFeatures; + features = f; + step = HandleFeatures; + return true; + } + else + return loginComplete(); + } + else { + // ignore + } + } + else if(step == GetTLSProceed) { + // waiting for proceed to starttls + if(e.namespaceURI() == NS_TLS) { + if(e.tagName() == "proceed") { +#ifdef XMPP_TEST + TD::msg("Server wants us to proceed with ssl handshake"); +#endif + tls_started = true; + need = NStartTLS; + spare = resetStream(); + step = Start; + return false; + } + else if(e.tagName() == "failure") { + event = EError; + errorCode = ErrStartTLS; + return true; + } + else { + event = EError; + errorCode = ErrProtocol; + return true; + } + } + else { + // ignore + } + } + else if(step == GetSASLChallenge) { + // waiting for sasl challenge/success/fail + if(e.namespaceURI() == NS_SASL) { + if(e.tagName() == "challenge") { + QByteArray a = Base64::stringToArray(e.text()); +#ifdef XMPP_TEST + TD::msg(QString("SASL IN: [%1]").arg(printArray(a))); +#endif + sasl_step = a; + need = NSASLNext; + step = GetSASLNext; + return false; + } + else if(e.tagName() == "success") { + sasl_authed = true; + event = ESASLSuccess; + step = HandleSASLSuccess; + return true; + } + else if(e.tagName() == "failure") { + QDomElement t = firstChildElement(e); + if(t.isNull() || t.namespaceURI() != NS_SASL) + errCond = -1; + else + errCond = stringToSASLCond(t.tagName()); + + event = EError; + errorCode = ErrAuth; + return true; + } + else { + event = EError; + errorCode = ErrProtocol; + return true; + } + } + } + else if(step == GetBindResponse) { + if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { + QString type(e.attribute("type")); + QString id(e.attribute("id")); + + if(id == "bind_1" && (type == "result" || type == "error")) { + if(type == "result") { + QDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement(); + Jid j; + if(!b.isNull()) { + QDomElement je = e.elementsByTagName("jid").item(0).toElement(); + j = je.text(); + } + if(!j.isValid()) { + event = EError; + errorCode = ErrProtocol; + return true; + } + jid = j; + return loginComplete(); + } + else { + errCond = -1; + + QDomElement err = e.elementsByTagNameNS(NS_CLIENT, "error").item(0).toElement(); + if(!err.isNull()) { + // get error condition + QDomNodeList nl = err.childNodes(); + QDomElement t; + for(uint n = 0; n < nl.count(); ++n) { + QDomNode i = nl.item(n); + if(i.isElement()) { + t = i.toElement(); + break; + } + } + if(!t.isNull() && t.namespaceURI() == NS_STANZAS) { + QString cond = t.tagName(); + if(cond == "not-allowed") + errCond = BindNotAllowed; + else if(cond == "conflict") + errCond = BindConflict; + } + } + + event = EError; + errorCode = ErrBind; + return true; + } + } + else { + // ignore + } + } + else { + // ignore + } + } + else if(step == GetAuthGetResponse) { + // waiting for an iq + if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { + Jid from(e.attribute("from")); + QString type(e.attribute("type")); + QString id(e.attribute("id")); + + bool okfrom = (from.isEmpty() || from.compare(Jid(to))); + if(okfrom && id == "auth_1" && (type == "result" || type == "error")) { + if(type == "result") { + QDomElement q = e.elementsByTagNameNS("jabber:iq:auth", "query").item(0).toElement(); + if(q.isNull() || q.elementsByTagName("username").item(0).isNull() || q.elementsByTagName("resource").item(0).isNull()) { + event = EError; + errorCode = ErrProtocol; + return true; + } + bool plain_supported = !q.elementsByTagName("password").item(0).isNull(); + bool digest_supported = !q.elementsByTagName("digest").item(0).isNull(); + + if(!digest_supported && !plain_supported) { + event = EError; + errorCode = ErrProtocol; + return true; + } + + // plain text not allowed? + if(!digest_supported && !allowPlain) { + event = EError; + errorCode = ErrPlain; + return true; + } + + digest = digest_supported; + need = NPassword; + step = HandleAuthSet; + return false; + } + else { + errCond = getOldErrorCode(e); + + event = EError; + errorCode = ErrAuth; + return true; + } + } + else { + // ignore + } + } + else { + // ignore + } + } + else if(step == GetAuthSetResponse) { + // waiting for an iq + if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { + Jid from(e.attribute("from")); + QString type(e.attribute("type")); + QString id(e.attribute("id")); + + bool okfrom = (from.isEmpty() || from.compare(Jid(to))); + if(okfrom && id == "auth_2" && (type == "result" || type == "error")) { + if(type == "result") { + return loginComplete(); + } + else { + errCond = getOldErrorCode(e); + + event = EError; + errorCode = ErrAuth; + return true; + } + } + else { + // ignore + } + } + else { + // ignore + } + } + // server + else if(step == GetRequest) { + printf("get request: [%s], %s\n", e.namespaceURI().latin1(), e.tagName().latin1()); + if(e.namespaceURI() == NS_TLS && e.localName() == "starttls") { + // TODO: don't let this be done twice + + QDomElement e = doc.createElementNS(NS_TLS, "proceed"); + writeElement(e, TypeElement, false, true); + event = ESend; + step = HandleTLS; + return true; + } + if(e.namespaceURI() == NS_SASL) { + if(e.localName() == "auth") { + if(sasl_started) { + // TODO + printf("error\n"); + return false; + } + + sasl_started = true; + sasl_mech = e.attribute("mechanism"); + // TODO: if child text missing, don't pass it + sasl_step = Base64::stringToArray(e.text()); + need = NSASLFirst; + step = GetSASLNext; + return false; + } + else { + // TODO + printf("unknown sasl tag\n"); + return false; + } + } + if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { + QDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement(); + if(!b.isNull()) { + QDomElement res = b.elementsByTagName("resource").item(0).toElement(); + QString resource = res.text(); + + QDomElement r = doc.createElement("iq"); + r.setAttribute("type", "result"); + r.setAttribute("id", e.attribute("id")); + QDomElement bind = doc.createElementNS(NS_BIND, "bind"); + QDomElement jid = doc.createElement("jid"); + Jid j = user + '@' + host + '/' + resource; + jid.appendChild(doc.createTextNode(j.full())); + bind.appendChild(jid); + r.appendChild(bind); + + writeElement(r, TypeElement, false); + event = ESend; + // TODO + return true; + } + else { + // TODO + } + } + } + else if(step == GetSASLResponse) { + if(e.namespaceURI() == NS_SASL && e.localName() == "response") { + sasl_step = Base64::stringToArray(e.text()); + need = NSASLNext; + step = GetSASLNext; + return false; + } + } + + if(isReady()) { + if(!e.isNull() && isValidStanza(e)) { + stanzaToRecv = e; + event = EStanzaReady; + setIncomingAsExternal(); + return true; + } + } + + need = NNotify; + notify |= NRecv; + return false; +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.h new file mode 100644 index 00000000..8511ce32 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.h @@ -0,0 +1,355 @@ +/* + * protocol.h - XMPP-Core protocol state machine + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; 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 +#include"xmlprotocol.h" +#include"xmpp.h" + +#define NS_ETHERX "http://etherx.jabber.org/streams" +#define NS_CLIENT "jabber:client" +#define NS_SERVER "jabber:server" +#define NS_DIALBACK "jabber:server:dialback" +#define NS_STREAMS "urn:ietf:params:xml:ns:xmpp-streams" +#define NS_TLS "urn:ietf:params:xml:ns:xmpp-tls" +#define NS_SASL "urn:ietf:params:xml:ns:xmpp-sasl" +#define NS_SESSION "urn:ietf:params:xml:ns:xmpp-session" +#define NS_STANZAS "urn:ietf:params:xml:ns:xmpp-stanzas" +#define NS_BIND "urn:ietf:params:xml:ns:xmpp-bind" +#define NS_XHTML_IM "http://jabber.org/protocol/xhtml-im" +#define NS_XHTML "http://www.w3.org/1999/xhtml" +#define NS_CHATSTATES "http://jabber.org/protocol/chatstates" + +namespace XMPP +{ + class Version + { + public: + Version(int maj=0, int min=0); + + int major, minor; + }; + + class StreamFeatures + { + public: + StreamFeatures(); + + bool tls_supported, sasl_supported, bind_supported; + bool tls_required; + QStringList sasl_mechs; + }; + + class BasicProtocol : public XmlProtocol + { + public: + // xmpp 1.0 error conditions + enum SASLCond { + Aborted, + IncorrectEncoding, + InvalidAuthzid, + InvalidMech, + MechTooWeak, + NotAuthorized, + TemporaryAuthFailure + }; + enum StreamCond { + BadFormat, + BadNamespacePrefix, + Conflict, + ConnectionTimeout, + HostGone, + HostUnknown, + ImproperAddressing, + InternalServerError, + InvalidFrom, + InvalidId, + InvalidNamespace, + InvalidXml, + StreamNotAuthorized, + PolicyViolation, + RemoteConnectionFailed, + ResourceConstraint, + RestrictedXml, + SeeOtherHost, + SystemShutdown, + UndefinedCondition, + UnsupportedEncoding, + UnsupportedStanzaType, + UnsupportedVersion, + XmlNotWellFormed + }; + enum BindCond { + BindBadRequest, + BindNotAllowed, + BindConflict + }; + + // extend the XmlProtocol enums + enum Need { + NSASLMechs = XmlProtocol::NCustom, // need SASL mechlist + NStartTLS, // need to switch on TLS layer + NSASLFirst, // need SASL first step + NSASLNext, // need SASL next step + NSASLLayer, // need to switch on SASL layer + NCustom = XmlProtocol::NCustom+10 + }; + enum Event { + EFeatures = XmlProtocol::ECustom, // breakpoint after features packet is received + ESASLSuccess, // breakpoint after successful sasl auth + EStanzaReady, // a stanza was received + EStanzaSent, // a stanza was sent + EReady, // stream is ready for stanza use + ECustom = XmlProtocol::ECustom+10 + }; + enum Error { + ErrProtocol = XmlProtocol::ErrCustom, // there was an error in the xmpp-core protocol exchange + ErrStream, // , see errCond, errText, and errAppSpec for details + ErrStartTLS, // server refused starttls + ErrAuth, // authorization error. errCond holds sasl condition (or numeric code for old-protocol) + ErrBind, // server refused resource bind + ErrCustom = XmlProtocol::ErrCustom+10 + }; + + BasicProtocol(); + ~BasicProtocol(); + + void reset(); + + // for outgoing xml + QDomDocument doc; + + // sasl-related + QString saslMech() const; + QByteArray saslStep() const; + void setSASLMechList(const QStringList &list); + void setSASLFirst(const QString &mech, const QByteArray &step); + void setSASLNext(const QByteArray &step); + void setSASLAuthed(); + + // send / recv + void sendStanza(const QDomElement &e); + void sendDirect(const QString &s); + void sendWhitespace(); + QDomElement recvStanza(); + + // shutdown + void shutdown(); + void shutdownWithError(int cond, const QString &otherHost=""); + + // information + QString to, from, id, lang; + Version version; + + // error output + int errCond; + QString errText; + QDomElement errAppSpec; + QString otherHost; + + QByteArray spare; // filled with unprocessed data on NStartTLS and NSASLLayer + + bool isReady() const; + + enum { TypeElement, TypeStanza, TypeDirect, TypePing }; + + protected: + static int stringToSASLCond(const QString &s); + static int stringToStreamCond(const QString &s); + static QString saslCondToString(int); + static QString streamCondToString(int); + + void send(const QDomElement &e, bool clip=false); + void sendStreamError(int cond, const QString &text="", const QDomElement &appSpec=QDomElement()); + void sendStreamError(const QString &text); // old-style + + bool errorAndClose(int cond, const QString &text="", const QDomElement &appSpec=QDomElement()); + bool error(int code); + void delayErrorAndClose(int cond, const QString &text="", const QDomElement &appSpec=QDomElement()); + void delayError(int code); + + // reimplemented + QDomElement docElement(); + void handleDocOpen(const Parser::Event &pe); + bool handleError(); + bool handleCloseFinished(); + bool doStep(const QDomElement &e); + void itemWritten(int id, int size); + + virtual QString defaultNamespace(); + virtual QStringList extraNamespaces(); // stringlist: prefix,uri,prefix,uri, [...] + virtual void handleStreamOpen(const Parser::Event &pe); + virtual bool doStep2(const QDomElement &e)=0; + + void setReady(bool b); + + QString sasl_mech; + QStringList sasl_mechlist; + QByteArray sasl_step; + bool sasl_authed; + + QDomElement stanzaToRecv; + + private: + struct SASLCondEntry + { + const char *str; + int cond; + }; + static SASLCondEntry saslCondTable[]; + + struct StreamCondEntry + { + const char *str; + int cond; + }; + static StreamCondEntry streamCondTable[]; + + struct SendItem + { + QDomElement stanzaToSend; + QString stringToSend; + bool doWhitespace; + }; + QValueList sendList; + + bool doShutdown, delayedError, closeError, ready; + int stanzasPending, stanzasWritten; + + void init(); + void extractStreamError(const QDomElement &e); + }; + + class CoreProtocol : public BasicProtocol + { + public: + enum { + NPassword = NCustom, // need password for old-mode + EDBVerify = ECustom, // breakpoint after db:verify request + ErrPlain = ErrCustom // server only supports plain, but allowPlain is false locally + }; + + CoreProtocol(); + ~CoreProtocol(); + + void reset(); + + void startClientOut(const Jid &jid, bool oldOnly, bool tlsActive, bool doAuth); + void startServerOut(const QString &to); + void startDialbackOut(const QString &to, const QString &from); + void startDialbackVerifyOut(const QString &to, const QString &from, const QString &id, const QString &key); + void startClientIn(const QString &id); + void startServerIn(const QString &id); + + void setLang(const QString &s); + void setAllowTLS(bool b); + void setAllowBind(bool b); + void setAllowPlain(bool b); // old-mode + + void setPassword(const QString &s); + void setFrom(const QString &s); + void setDialbackKey(const QString &s); + + // input + QString user, host; + + // status + bool old; + + StreamFeatures features; + + //static QString xmlToString(const QDomElement &e, bool clip=false); + + class DBItem + { + public: + enum { ResultRequest, ResultGrant, VerifyRequest, VerifyGrant, Validated }; + int type; + Jid to, from; + QString key, id; + bool ok; + }; + + private: + enum Step { + Start, + Done, + SendFeatures, + GetRequest, + HandleTLS, + GetSASLResponse, + IncHandleSASLSuccess, + GetFeatures, // read features packet + HandleFeatures, // act on features, by initiating tls, sasl, or bind + GetTLSProceed, // read tls response + GetSASLFirst, // perform sasl first step using provided data + GetSASLChallenge, // read server sasl challenge + GetSASLNext, // perform sasl next step using provided data + HandleSASLSuccess, // handle what must be done after reporting sasl success + GetBindResponse, // read bind response + HandleAuthGet, // send old-protocol auth-get + GetAuthGetResponse, // read auth-get response + HandleAuthSet, // send old-protocol auth-set + GetAuthSetResponse // read auth-set response + }; + + QValueList dbrequests, dbpending, dbvalidated; + + bool server, dialback, dialback_verify; + int step; + + bool digest; + bool tls_started, sasl_started; + + Jid jid; + bool oldOnly; + bool allowPlain; + bool doTLS, doAuth, doBinding; + QString password; + + QString dialback_id, dialback_key; + QString self_from; + + void init(); + static int getOldErrorCode(const QDomElement &e); + bool loginComplete(); + + bool isValidStanza(const QDomElement &e) const; + bool grabPendingItem(const Jid &to, const Jid &from, int type, DBItem *item); + bool normalStep(const QDomElement &e); + bool dialbackStep(const QDomElement &e); + + // reimplemented + bool stepAdvancesParser() const; + bool stepRequiresElement() const; + void stringSend(const QString &s); + void stringRecv(const QString &s); + QString defaultNamespace(); + QStringList extraNamespaces(); + void handleStreamOpen(const Parser::Event &pe); + bool doStep2(const QDomElement &e); + void elementSend(const QDomElement &e); + void elementRecv(const QDomElement &e); + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/qcaprovider.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/qcaprovider.h new file mode 100644 index 00000000..a7f1805b --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/qcaprovider.h @@ -0,0 +1,191 @@ +/* + * qcaprovider.h - QCA Plugin API + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef QCAPROVIDER_H +#define QCAPROVIDER_H + +#include +#include +#include +#include +#include +#include"qca.h" + +#define QCA_PLUGIN_VERSION 1 + +class QCAProvider +{ +public: + QCAProvider() {} + virtual ~QCAProvider() {} + + virtual void init()=0; + virtual int qcaVersion() const=0; + virtual int capabilities() const=0; + virtual void *context(int cap)=0; +}; + +class QCA_HashContext +{ +public: + virtual ~QCA_HashContext() {} + + virtual QCA_HashContext *clone()=0; + virtual void reset()=0; + virtual void update(const char *in, unsigned int len)=0; + virtual void final(QByteArray *out)=0; +}; + +class QCA_CipherContext +{ +public: + virtual ~QCA_CipherContext() {} + + virtual QCA_CipherContext *clone()=0; + virtual int keySize()=0; + virtual int blockSize()=0; + virtual bool generateKey(char *out, int keysize=-1)=0; + virtual bool generateIV(char *out)=0; + + virtual bool setup(int dir, int mode, const char *key, int keysize, const char *iv, bool pad)=0; + virtual bool update(const char *in, unsigned int len)=0; + virtual bool final(QByteArray *out)=0; +}; + +class QCA_RSAKeyContext +{ +public: + virtual ~QCA_RSAKeyContext() {} + + virtual QCA_RSAKeyContext *clone() const=0; + virtual bool isNull() const=0; + virtual bool havePublic() const=0; + virtual bool havePrivate() const=0; + virtual bool createFromDER(const char *in, unsigned int len)=0; + virtual bool createFromPEM(const char *in, unsigned int len)=0; + virtual bool createFromNative(void *in)=0; + virtual bool generate(unsigned int bits)=0; + virtual bool toDER(QByteArray *out, bool publicOnly)=0; + virtual bool toPEM(QByteArray *out, bool publicOnly)=0; + + virtual bool encrypt(const QByteArray &in, QByteArray *out, bool oaep)=0; + virtual bool decrypt(const QByteArray &in, QByteArray *out, bool oaep)=0; +}; + +struct QCA_CertProperty +{ + QString var; + QString val; +}; + +class QCA_CertContext +{ +public: + virtual ~QCA_CertContext() {} + + virtual QCA_CertContext *clone() const=0; + virtual bool isNull() const=0; + virtual bool createFromDER(const char *in, unsigned int len)=0; + virtual bool createFromPEM(const char *in, unsigned int len)=0; + virtual bool toDER(QByteArray *out)=0; + virtual bool toPEM(QByteArray *out)=0; + + virtual QString serialNumber() const=0; + virtual QString subjectString() const=0; + virtual QString issuerString() const=0; + virtual QValueList subject() const=0; + virtual QValueList issuer() const=0; + virtual QDateTime notBefore() const=0; + virtual QDateTime notAfter() const=0; + virtual bool matchesAddress(const QString &realHost) const=0; +}; + +class QCA_TLSContext +{ +public: + enum Result { Success, Error, Continue }; + virtual ~QCA_TLSContext() {} + + virtual void reset()=0; + virtual bool startClient(const QPtrList &store, const QCA_CertContext &cert, const QCA_RSAKeyContext &key)=0; + virtual bool startServer(const QPtrList &store, const QCA_CertContext &cert, const QCA_RSAKeyContext &key)=0; + + virtual int handshake(const QByteArray &in, QByteArray *out)=0; + virtual int shutdown(const QByteArray &in, QByteArray *out)=0; + virtual bool encode(const QByteArray &plain, QByteArray *to_net, int *encoded)=0; + virtual bool decode(const QByteArray &from_net, QByteArray *plain, QByteArray *to_net)=0; + virtual bool eof() const=0; + virtual QByteArray unprocessed()=0; + + virtual QCA_CertContext *peerCertificate() const=0; + virtual int validityResult() const=0; +}; + +struct QCA_SASLHostPort +{ + QHostAddress addr; + Q_UINT16 port; +}; + +struct QCA_SASLNeedParams +{ + bool user, authzid, pass, realm; +}; + +class QCA_SASLContext +{ +public: + enum Result { Success, Error, NeedParams, AuthCheck, Continue }; + virtual ~QCA_SASLContext() {} + + // common + virtual void reset()=0; + virtual void setCoreProps(const QString &service, const QString &host, QCA_SASLHostPort *local, QCA_SASLHostPort *remote)=0; + virtual void setSecurityProps(bool noPlain, bool noActive, bool noDict, bool noAnon, bool reqForward, bool reqCreds, bool reqMutual, int ssfMin, int ssfMax, const QString &_ext_authid, int _ext_ssf)=0; + virtual int security() const=0; + virtual int errorCond() const=0; + + // init / first step + virtual bool clientStart(const QStringList &mechlist)=0; + virtual int clientFirstStep(bool allowClientSendFirst)=0; + virtual bool serverStart(const QString &realm, QStringList *mechlist, const QString &name)=0; + virtual int serverFirstStep(const QString &mech, const QByteArray *in)=0; + + // get / set params + virtual QCA_SASLNeedParams clientParamsNeeded() const=0; + virtual void setClientParams(const QString *user, const QString *authzid, const QString *pass, const QString *realm)=0; + virtual QString username() const=0; + virtual QString authzid() const=0; + + // continue steps + virtual int nextStep(const QByteArray &in)=0; + virtual int tryAgain()=0; + + // results + virtual QString mech() const=0; + virtual const QByteArray *clientInit() const=0; + virtual QByteArray result() const=0; + + // security layer + virtual bool encode(const QByteArray &in, QByteArray *out)=0; + virtual bool decode(const QByteArray &in, QByteArray *out)=0; +}; + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.cpp new file mode 100644 index 00000000..6bd902d9 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.cpp @@ -0,0 +1,589 @@ +/* + * securestream.cpp - combines a ByteStream with TLS and SASL + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + Note: SecureStream depends on the underlying security layers to signal + plain-to-encrypted results immediately (as opposed to waiting for the + event loop) so that the user cannot add/remove security layers during + this conversion moment. QCA::TLS and QCA::SASL behave as expected, + but future layers might not. +*/ + +#include"securestream.h" + +#include +#include +#include + +#ifdef USE_TLSHANDLER +#include"xmpp.h" +#endif + +//---------------------------------------------------------------------------- +// LayerTracker +//---------------------------------------------------------------------------- +class LayerTracker +{ +public: + struct Item + { + int plain; + int encoded; + }; + + LayerTracker(); + + void reset(); + void addPlain(int plain); + void specifyEncoded(int encoded, int plain); + int finished(int encoded); + + int p; + QValueList list; +}; + +LayerTracker::LayerTracker() +{ + p = 0; +} + +void LayerTracker::reset() +{ + p = 0; + list.clear(); +} + +void LayerTracker::addPlain(int plain) +{ + p += plain; +} + +void LayerTracker::specifyEncoded(int encoded, int plain) +{ + // can't specify more bytes than we have + if(plain > p) + plain = p; + p -= plain; + Item i; + i.plain = plain; + i.encoded = encoded; + list += i; +} + +int LayerTracker::finished(int encoded) +{ + int plain = 0; + for(QValueList::Iterator it = list.begin(); it != list.end();) { + Item &i = *it; + + // not enough? + if(encoded < i.encoded) { + i.encoded -= encoded; + break; + } + + encoded -= i.encoded; + plain += i.plain; + it = list.remove(it); + } + return plain; +} + +//---------------------------------------------------------------------------- +// SecureStream +//---------------------------------------------------------------------------- +class SecureLayer : public QObject +{ + Q_OBJECT +public: + enum { TLS, SASL, TLSH }; + int type; + union { + QCA::TLS *tls; + QCA::SASL *sasl; +#ifdef USE_TLSHANDLER + XMPP::TLSHandler *tlsHandler; +#endif + } p; + LayerTracker layer; + bool tls_done; + int prebytes; + + SecureLayer(QCA::TLS *t) + { + type = TLS; + p.tls = t; + init(); + connect(p.tls, SIGNAL(handshaken()), SLOT(tls_handshaken())); + connect(p.tls, SIGNAL(readyRead()), SLOT(tls_readyRead())); + connect(p.tls, SIGNAL(readyReadOutgoing(int)), SLOT(tls_readyReadOutgoing(int))); + connect(p.tls, SIGNAL(closed()), SLOT(tls_closed())); + connect(p.tls, SIGNAL(error(int)), SLOT(tls_error(int))); + } + + SecureLayer(QCA::SASL *s) + { + type = SASL; + p.sasl = s; + init(); + connect(p.sasl, SIGNAL(readyRead()), SLOT(sasl_readyRead())); + connect(p.sasl, SIGNAL(readyReadOutgoing(int)), SLOT(sasl_readyReadOutgoing(int))); + connect(p.sasl, SIGNAL(error(int)), SLOT(sasl_error(int))); + } + +#ifdef USE_TLSHANDLER + SecureLayer(XMPP::TLSHandler *t) + { + type = TLSH; + p.tlsHandler = t; + init(); + connect(p.tlsHandler, SIGNAL(success()), SLOT(tlsHandler_success())); + connect(p.tlsHandler, SIGNAL(fail()), SLOT(tlsHandler_fail())); + connect(p.tlsHandler, SIGNAL(closed()), SLOT(tlsHandler_closed())); + connect(p.tlsHandler, SIGNAL(readyRead(const QByteArray &)), SLOT(tlsHandler_readyRead(const QByteArray &))); + connect(p.tlsHandler, SIGNAL(readyReadOutgoing(const QByteArray &, int)), SLOT(tlsHandler_readyReadOutgoing(const QByteArray &, int))); + } +#endif + + void init() + { + tls_done = false; + prebytes = 0; + } + + void write(const QByteArray &a) + { + layer.addPlain(a.size()); + switch(type) { + case TLS: { p.tls->write(a); break; } + case SASL: { p.sasl->write(a); break; } +#ifdef USE_TLSHANDLER + case TLSH: { p.tlsHandler->write(a); break; } +#endif + } + } + + void writeIncoming(const QByteArray &a) + { + switch(type) { + case TLS: { p.tls->writeIncoming(a); break; } + case SASL: { p.sasl->writeIncoming(a); break; } +#ifdef USE_TLSHANDLER + case TLSH: { p.tlsHandler->writeIncoming(a); break; } +#endif + } + } + + int finished(int plain) + { + int written = 0; + + // deal with prebytes (bytes sent prior to this security layer) + if(prebytes > 0) { + if(prebytes >= plain) { + written += plain; + prebytes -= plain; + plain = 0; + } + else { + written += prebytes; + plain -= prebytes; + prebytes = 0; + } + } + + // put remainder into the layer tracker + if(type == SASL || tls_done) + written += layer.finished(plain); + + return written; + } + +signals: + void tlsHandshaken(); + void tlsClosed(const QByteArray &); + void readyRead(const QByteArray &); + void needWrite(const QByteArray &); + void error(int); + +private slots: + void tls_handshaken() + { + tls_done = true; + tlsHandshaken(); + } + + void tls_readyRead() + { + QByteArray a = p.tls->read(); + readyRead(a); + } + + void tls_readyReadOutgoing(int plainBytes) + { + QByteArray a = p.tls->readOutgoing(); + if(tls_done) + layer.specifyEncoded(a.size(), plainBytes); + needWrite(a); + } + + void tls_closed() + { + QByteArray a = p.tls->readUnprocessed(); + tlsClosed(a); + } + + void tls_error(int x) + { + error(x); + } + + void sasl_readyRead() + { + QByteArray a = p.sasl->read(); + readyRead(a); + } + + void sasl_readyReadOutgoing(int plainBytes) + { + QByteArray a = p.sasl->readOutgoing(); + layer.specifyEncoded(a.size(), plainBytes); + needWrite(a); + } + + void sasl_error(int x) + { + error(x); + } + +#ifdef USE_TLSHANDLER + void tlsHandler_success() + { + tls_done = true; + tlsHandshaken(); + } + + void tlsHandler_fail() + { + error(0); + } + + void tlsHandler_closed() + { + tlsClosed(QByteArray()); + } + + void tlsHandler_readyRead(const QByteArray &a) + { + readyRead(a); + } + + void tlsHandler_readyReadOutgoing(const QByteArray &a, int plainBytes) + { + if(tls_done) + layer.specifyEncoded(a.size(), plainBytes); + needWrite(a); + } +#endif +}; + +#include"securestream.moc" + +class SecureStream::Private +{ +public: + ByteStream *bs; + QPtrList layers; + int pending; + int errorCode; + bool active; + bool topInProgress; + + bool haveTLS() const + { + QPtrListIterator it(layers); + for(SecureLayer *s; (s = it.current()); ++it) { + if(s->type == SecureLayer::TLS +#ifdef USE_TLSHANDLER + || s->type == SecureLayer::TLSH +#endif + ) { + return true; + } + } + return false; + } + + bool haveSASL() const + { + QPtrListIterator it(layers); + for(SecureLayer *s; (s = it.current()); ++it) { + if(s->type == SecureLayer::SASL) + return true; + } + return false; + } +}; + +SecureStream::SecureStream(ByteStream *s) +:ByteStream(0) +{ + d = new Private; + + d->bs = s; + connect(d->bs, SIGNAL(readyRead()), SLOT(bs_readyRead())); + connect(d->bs, SIGNAL(bytesWritten(int)), SLOT(bs_bytesWritten(int))); + + d->layers.setAutoDelete(true); + d->pending = 0; + d->active = true; + d->topInProgress = false; +} + +SecureStream::~SecureStream() +{ + delete d; +} + +void SecureStream::linkLayer(QObject *s) +{ + connect(s, SIGNAL(tlsHandshaken()), SLOT(layer_tlsHandshaken())); + connect(s, SIGNAL(tlsClosed(const QByteArray &)), SLOT(layer_tlsClosed(const QByteArray &))); + connect(s, SIGNAL(readyRead(const QByteArray &)), SLOT(layer_readyRead(const QByteArray &))); + connect(s, SIGNAL(needWrite(const QByteArray &)), SLOT(layer_needWrite(const QByteArray &))); + connect(s, SIGNAL(error(int)), SLOT(layer_error(int))); +} + +int SecureStream::calcPrebytes() const +{ + int x = 0; + QPtrListIterator it(d->layers); + for(SecureLayer *s; (s = it.current()); ++it) + x += s->prebytes; + return (d->pending - x); +} + +void SecureStream::startTLSClient(QCA::TLS *t, const QByteArray &spare) +{ + if(!d->active || d->topInProgress || d->haveTLS()) + return; + + SecureLayer *s = new SecureLayer(t); + s->prebytes = calcPrebytes(); + linkLayer(s); + d->layers.append(s); + d->topInProgress = true; + + insertData(spare); +} + +void SecureStream::startTLSServer(QCA::TLS *t, const QByteArray &spare) +{ + if(!d->active || d->topInProgress || d->haveTLS()) + return; + + SecureLayer *s = new SecureLayer(t); + s->prebytes = calcPrebytes(); + linkLayer(s); + d->layers.append(s); + d->topInProgress = true; + + insertData(spare); +} + +void SecureStream::setLayerSASL(QCA::SASL *sasl, const QByteArray &spare) +{ + if(!d->active || d->topInProgress || d->haveSASL()) + return; + + SecureLayer *s = new SecureLayer(sasl); + s->prebytes = calcPrebytes(); + linkLayer(s); + d->layers.append(s); + + insertData(spare); +} + +#ifdef USE_TLSHANDLER +void SecureStream::startTLSClient(XMPP::TLSHandler *t, const QString &server, const QByteArray &spare) +{ + if(!d->active || d->topInProgress || d->haveTLS()) + return; + + SecureLayer *s = new SecureLayer(t); + s->prebytes = calcPrebytes(); + linkLayer(s); + d->layers.append(s); + d->topInProgress = true; + + // unlike QCA::TLS, XMPP::TLSHandler has no return value + s->p.tlsHandler->startClient(server); + + insertData(spare); +} +#endif + +void SecureStream::closeTLS() +{ + SecureLayer *s = d->layers.getLast(); + if(s) { + if(s->type == SecureLayer::TLS) + s->p.tls->close(); + } +} + +int SecureStream::errorCode() const +{ + return d->errorCode; +} + +bool SecureStream::isOpen() const +{ + return d->active; +} + +void SecureStream::write(const QByteArray &a) +{ + if(!isOpen()) + return; + + d->pending += a.size(); + + // send to the last layer + SecureLayer *s = d->layers.getLast(); + if(s) + s->write(a); + else + writeRawData(a); +} + +int SecureStream::bytesToWrite() const +{ + return d->pending; +} + +void SecureStream::bs_readyRead() +{ + QByteArray a = d->bs->read(); + + // send to the first layer + SecureLayer *s = d->layers.getFirst(); + if(s) + s->writeIncoming(a); + else + incomingData(a); +} + +void SecureStream::bs_bytesWritten(int bytes) +{ + QPtrListIterator it(d->layers); + for(SecureLayer *s; (s = it.current()); ++it) + bytes = s->finished(bytes); + + if(bytes > 0) { + d->pending -= bytes; + bytesWritten(bytes); + } +} + +void SecureStream::layer_tlsHandshaken() +{ + d->topInProgress = false; + tlsHandshaken(); +} + +void SecureStream::layer_tlsClosed(const QByteArray &) +{ + d->active = false; + d->layers.clear(); + tlsClosed(); +} + +void SecureStream::layer_readyRead(const QByteArray &a) +{ + SecureLayer *s = (SecureLayer *)sender(); + QPtrListIterator it(d->layers); + while(it.current() != s) + ++it; + + // pass upwards + ++it; + s = it.current(); + if(s) + s->writeIncoming(a); + else + incomingData(a); +} + +void SecureStream::layer_needWrite(const QByteArray &a) +{ + SecureLayer *s = (SecureLayer *)sender(); + QPtrListIterator it(d->layers); + while(it.current() != s) + ++it; + + // pass downwards + --it; + s = it.current(); + if(s) + s->write(a); + else + writeRawData(a); +} + +void SecureStream::layer_error(int x) +{ + SecureLayer *s = (SecureLayer *)sender(); + int type = s->type; + d->errorCode = x; + d->active = false; + d->layers.clear(); + if(type == SecureLayer::TLS) + error(ErrTLS); + else if(type == SecureLayer::SASL) + error(ErrSASL); +#ifdef USE_TLSHANDLER + else if(type == SecureLayer::TLSH) + error(ErrTLS); +#endif +} + +void SecureStream::insertData(const QByteArray &a) +{ + if(!a.isEmpty()) { + SecureLayer *s = d->layers.getLast(); + if(s) + s->writeIncoming(a); + else + incomingData(a); + } +} + +void SecureStream::writeRawData(const QByteArray &a) +{ + d->bs->write(a); +} + +void SecureStream::incomingData(const QByteArray &a) +{ + appendRead(a); + if(bytesAvailable()) + readyRead(); +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.h new file mode 100644 index 00000000..c5787a2b --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.h @@ -0,0 +1,84 @@ +/* + * securestream.h - combines a ByteStream with TLS and SASL + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef SECURESTREAM_H +#define SECURESTREAM_H + +#include +#include"bytestream.h" + +#define USE_TLSHANDLER + +#ifdef USE_TLSHANDLER +namespace XMPP +{ + class TLSHandler; +} +#endif + +class SecureStream : public ByteStream +{ + Q_OBJECT +public: + enum Error { ErrTLS = ErrCustom, ErrSASL }; + SecureStream(ByteStream *s); + ~SecureStream(); + + void startTLSClient(QCA::TLS *t, const QByteArray &spare=QByteArray()); + void startTLSServer(QCA::TLS *t, const QByteArray &spare=QByteArray()); + void setLayerSASL(QCA::SASL *s, const QByteArray &spare=QByteArray()); +#ifdef USE_TLSHANDLER + void startTLSClient(XMPP::TLSHandler *t, const QString &server, const QByteArray &spare=QByteArray()); +#endif + + void closeTLS(); + int errorCode() const; + + // reimplemented + bool isOpen() const; + void write(const QByteArray &); + int bytesToWrite() const; + +signals: + void tlsHandshaken(); + void tlsClosed(); + +private slots: + void bs_readyRead(); + void bs_bytesWritten(int); + + void layer_tlsHandshaken(); + void layer_tlsClosed(const QByteArray &); + void layer_readyRead(const QByteArray &); + void layer_needWrite(const QByteArray &); + void layer_error(int); + +private: + void linkLayer(QObject *); + int calcPrebytes() const; + void insertData(const QByteArray &a); + void writeRawData(const QByteArray &a); + void incomingData(const QByteArray &a); + + class Private; + Private *d; +}; + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.cpp new file mode 100644 index 00000000..54c4f405 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.cpp @@ -0,0 +1,459 @@ +/* + * simplesasl.cpp - Simple SASL implementation + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"simplesasl.h" + +#include +#include +#include +#include +#include +#include +#include"base64.h" + +namespace XMPP +{ + +struct Prop +{ + QCString var, val; +}; + +class PropList : public QValueList +{ +public: + PropList() : QValueList() + { + } + + void set(const QCString &var, const QCString &val) + { + Prop p; + p.var = var; + p.val = val; + append(p); + } + + QCString get(const QCString &var) + { + for(ConstIterator it = begin(); it != end(); ++it) { + if((*it).var == var) + return (*it).val; + } + return QCString(); + } + + QCString toString() const + { + QCString str; + bool first = true; + for(ConstIterator it = begin(); it != end(); ++it) { + if(!first) + str += ','; + str += (*it).var + "=\"" + (*it).val + '\"'; + first = false; + } + return str; + } + + bool fromString(const QCString &str) + { + PropList list; + int at = 0; + while(1) { + int n = str.find('=', at); + if(n == -1) + break; + QCString var, val; + var = str.mid(at, n-at); + at = n + 1; + if(str[at] == '\"') { + ++at; + n = str.find('\"', at); + if(n == -1) + break; + val = str.mid(at, n-at); + at = n + 1; + } + else { + n = str.find(',', at); + if(n != -1) { + val = str.mid(at, n-at); + at = n; + } + else { + val = str.mid(at); + at = str.length()-1; + } + } + Prop prop; + prop.var = var; + prop.val = val; + list.append(prop); + + if(str[at] != ',') + break; + ++at; + } + + // integrity check + if(list.varCount("nonce") != 1) + return false; + if(list.varCount("algorithm") != 1) + return false; + *this = list; + return true; + } + + int varCount(const QCString &var) + { + int n = 0; + for(ConstIterator it = begin(); it != end(); ++it) { + if((*it).var == var) + ++n; + } + return n; + } + + QStringList getValues(const QCString &var) + { + QStringList list; + for(ConstIterator it = begin(); it != end(); ++it) { + if((*it).var == var) + list += (*it).val; + } + return list; + } +}; + +class SimpleSASLContext : public QCA_SASLContext +{ +public: + // core props + QString service, host; + + // state + int step; + QByteArray in_buf; + QString out_mech; + QByteArray out_buf; + bool capable; + int err; + + QCA_SASLNeedParams need; + QCA_SASLNeedParams have; + QString user, authz, pass, realm; + + SimpleSASLContext() + { + reset(); + } + + ~SimpleSASLContext() + { + reset(); + } + + void reset() + { + resetState(); + resetParams(); + } + + void resetState() + { + out_mech = QString(); + out_buf.resize(0); + err = -1; + } + + void resetParams() + { + capable = true; + need.user = false; + need.authzid = false; + need.pass = false; + need.realm = false; + have.user = false; + have.authzid = false; + have.pass = false; + have.realm = false; + user = QString(); + authz = QString(); + pass = QString(); + realm = QString(); + } + + void setCoreProps(const QString &_service, const QString &_host, QCA_SASLHostPort *, QCA_SASLHostPort *) + { + service = _service; + host = _host; + } + + void setSecurityProps(bool, bool, bool, bool, bool reqForward, bool reqCreds, bool reqMutual, int ssfMin, int, const QString &, int) + { + if(reqForward || reqCreds || reqMutual || ssfMin > 0) + capable = false; + else + capable = true; + } + + int security() const + { + return 0; + } + + int errorCond() const + { + return err; + } + + bool clientStart(const QStringList &mechlist) + { + bool haveMech = false; + for(QStringList::ConstIterator it = mechlist.begin(); it != mechlist.end(); ++it) { + if((*it) == "DIGEST-MD5") { + haveMech = true; + break; + } + } + if(!capable || !haveMech) { + err = QCA::SASL::NoMech; + return false; + } + + resetState(); + step = 0; + return true; + } + + int clientFirstStep(bool) + { + return clientTryAgain(); + } + + bool serverStart(const QString &, QStringList *, const QString &) + { + return false; + } + + int serverFirstStep(const QString &, const QByteArray *) + { + return Error; + } + + QCA_SASLNeedParams clientParamsNeeded() const + { + return need; + } + + void setClientParams(const QString *_user, const QString *_authzid, const QString *_pass, const QString *_realm) + { + if(_user) { + user = *_user; + need.user = false; + have.user = true; + } + if(_authzid) { + authz = *_authzid; + need.authzid = false; + have.authzid = true; + } + if(_pass) { + pass = *_pass; + need.pass = false; + have.pass = true; + } + if(_realm) { + realm = *_realm; + need.realm = false; + have.realm = true; + } + } + + QString username() const + { + return QString(); + } + + QString authzid() const + { + return QString(); + } + + int nextStep(const QByteArray &in) + { + in_buf = in.copy(); + return tryAgain(); + } + + int tryAgain() + { + return clientTryAgain(); + } + + QString mech() const + { + return out_mech; + } + + const QByteArray *clientInit() const + { + return 0; + } + + QByteArray result() const + { + return out_buf; + } + + int clientTryAgain() + { + if(step == 0) { + out_mech = "DIGEST-MD5"; + ++step; + return Continue; + } + else if(step == 1) { + // if we still need params, then the app has failed us! + if(need.user || need.authzid || need.pass || need.realm) { + err = -1; + return Error; + } + + // see if some params are needed + if(!have.user) + need.user = true; + if(!have.authzid) + need.authzid = true; + if(!have.pass) + need.pass = true; + if(need.user || need.authzid || need.pass) + return NeedParams; + + // get props + QCString cs(in_buf.data(), in_buf.size()+1); + PropList in; + if(!in.fromString(cs)) { + err = QCA::SASL::BadProto; + return Error; + } + + // make a cnonce + QByteArray a(32); + for(int n = 0; n < (int)a.size(); ++n) + a[n] = (char)(256.0*rand()/(RAND_MAX+1.0)); + QCString cnonce = Base64::arrayToString(a).latin1(); + + // make other variables + realm = host; + QCString nonce = in.get("nonce"); + QCString nc = "00000001"; + QCString uri = service.utf8() + '/' + host.utf8(); + QCString qop = "auth"; + + // build 'response' + QCString X = user.utf8() + ':' + realm.utf8() + ':' + pass.utf8(); + QByteArray Y = QCA::MD5::hash(X); + QCString tmp = QCString(":") + nonce + ':' + cnonce + ':' + authz.utf8(); + QByteArray A1(Y.size() + tmp.length()); + memcpy(A1.data(), Y.data(), Y.size()); + memcpy(A1.data() + Y.size(), tmp.data(), tmp.length()); + QCString A2 = "AUTHENTICATE:" + uri; + QCString HA1 = QCA::MD5::hashToString(A1).latin1(); + QCString HA2 = QCA::MD5::hashToString(A2).latin1(); + QCString KD = HA1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + HA2; + QCString Z = QCA::MD5::hashToString(KD).latin1(); + + // build output + PropList out; + out.set("username", user.utf8()); + out.set("realm", host.utf8()); + out.set("nonce", nonce); + out.set("cnonce", cnonce); + out.set("nc", nc); + out.set("serv-type", service.utf8()); + out.set("host", host.utf8()); + out.set("digest-uri", uri); + out.set("qop", qop); + out.set("response", Z); + out.set("charset", "utf-8"); + out.set("authzid", authz.utf8()); + QCString s = out.toString(); + + // done + out_buf.resize(s.length()); + memcpy(out_buf.data(), s.data(), out_buf.size()); + ++step; + return Continue; + } + else { + out_buf.resize(0); + return Success; + } + } + + bool encode(const QByteArray &a, QByteArray *b) + { + *b = a.copy(); + return true; + } + + bool decode(const QByteArray &a, QByteArray *b) + { + *b = a.copy(); + return true; + } +}; + +class QCASimpleSASL : public QCAProvider +{ +public: + QCASimpleSASL() {} + ~QCASimpleSASL() {} + + void init() + { + } + + int qcaVersion() const + { + return QCA_PLUGIN_VERSION; + } + + int capabilities() const + { + return QCA::CAP_SASL; + } + + void *context(int cap) + { + if(cap == QCA::CAP_SASL) + return new SimpleSASLContext; + return 0; + } +}; + +QCAProvider *createProviderSimpleSASL() +{ + return (new QCASimpleSASL); +} + +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.h new file mode 100644 index 00000000..12a08c0e --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.h @@ -0,0 +1,31 @@ +/* + * simplesasl.h - Simple SASL implementation + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef SIMPLESASL_H +#define SIMPLESASL_H + +#include"qcaprovider.h" + +namespace XMPP +{ + QCAProvider *createProviderSimpleSASL(); +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/stream.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/stream.cpp new file mode 100644 index 00000000..bfcc218c --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/stream.cpp @@ -0,0 +1,1762 @@ +/* + * stream.cpp - handles a client stream + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + Notes: + - For Non-SASL auth (JEP-0078), username and resource fields are required. + + TODO: + - sasl needParams is totally jacked? PLAIN requires authzid, etc + - server error handling + - reply with protocol errors if the client send something wrong + - don't necessarily disconnect on protocol error. prepare for more. + - server function + - deal with stream 'to' attribute dynamically + - flag tls/sasl/binding support dynamically (have the ability to specify extra stream:features) + - inform the caller about the user authentication information + - sasl security settings + - resource-binding interaction + - timeouts + - allow exchanges of non-standard stanzas + - send even if we close prematurely? + - ensure ClientStream and child classes are fully deletable after signals + - xml:lang in root () element + - sasl external + - sasl anonymous +*/ + +#include"xmpp.h" + +#include +#include +#include +#include +#include +#include"bytestream.h" +#include"base64.h" +#include"hash.h" +#include"simplesasl.h" +#include"securestream.h" +#include"protocol.h" + +#ifdef XMPP_TEST +#include"td.h" +#endif + +//#define XMPP_DEBUG + +using namespace XMPP; + +static Debug *debug_ptr = 0; +void XMPP::setDebug(Debug *p) +{ + debug_ptr = p; +} + +static QByteArray randomArray(int size) +{ + QByteArray a(size); + for(int n = 0; n < size; ++n) + a[n] = (char)(256.0*rand()/(RAND_MAX+1.0)); + return a; +} + +static QString genId() +{ + // need SHA1 here + if(!QCA::isSupported(QCA::CAP_SHA1)) + QCA::insertProvider(createProviderHash()); + + return QCA::SHA1::hashToString(randomArray(128)); +} + +//---------------------------------------------------------------------------- +// Stanza +//---------------------------------------------------------------------------- +Stanza::Error::Error(int _type, int _condition, const QString &_text, const QDomElement &_appSpec) +{ + type = _type; + condition = _condition; + text = _text; + appSpec = _appSpec; +} + +class Stanza::Private +{ +public: + struct ErrorTypeEntry + { + const char *str; + int type; + }; + static ErrorTypeEntry errorTypeTable[]; + + struct ErrorCondEntry + { + const char *str; + int cond; + }; + static ErrorCondEntry errorCondTable[]; + + static int stringToKind(const QString &s) + { + if(s == "message") + return Message; + else if(s == "presence") + return Presence; + else if(s == "iq") + return IQ; + else + return -1; + } + + static QString kindToString(Kind k) + { + if(k == Message) + return "message"; + else if(k == Presence) + return "presence"; + else + return "iq"; + } + + static int stringToErrorType(const QString &s) + { + for(int n = 0; errorTypeTable[n].str; ++n) { + if(s == errorTypeTable[n].str) + return errorTypeTable[n].type; + } + return -1; + } + + static QString errorTypeToString(int x) + { + for(int n = 0; errorTypeTable[n].str; ++n) { + if(x == errorTypeTable[n].type) + return errorTypeTable[n].str; + } + return QString(); + } + + static int stringToErrorCond(const QString &s) + { + for(int n = 0; errorCondTable[n].str; ++n) { + if(s == errorCondTable[n].str) + return errorCondTable[n].cond; + } + return -1; + } + + static QString errorCondToString(int x) + { + for(int n = 0; errorCondTable[n].str; ++n) { + if(x == errorCondTable[n].cond) + return errorCondTable[n].str; + } + return QString(); + } + + Stream *s; + QDomElement e; +}; + +Stanza::Private::ErrorTypeEntry Stanza::Private::errorTypeTable[] = +{ + { "cancel", Cancel }, + { "continue", Continue }, + { "modify", Modify }, + { "auth", Auth }, + { "wait", Wait }, + { 0, 0 }, +}; + +Stanza::Private::ErrorCondEntry Stanza::Private::errorCondTable[] = +{ + { "bad-request", BadRequest }, + { "conflict", Conflict }, + { "feature-not-implemented", FeatureNotImplemented }, + { "forbidden", Forbidden }, + { "internal-server-error", InternalServerError }, + { "item-not-found", ItemNotFound }, + { "jid-malformed", JidMalformed }, + { "not-allowed", NotAllowed }, + { "payment-required", PaymentRequired }, + { "recipient-unavailable", RecipientUnavailable }, + { "registration-required", RegistrationRequired }, + { "remote-server-not-found", ServerNotFound }, + { "remote-server-timeout", ServerTimeout }, + { "resource-constraint", ResourceConstraint }, + { "service-unavailable", ServiceUnavailable }, + { "subscription-required", SubscriptionRequired }, + { "undefined-condition", UndefinedCondition }, + { "unexpected-request", UnexpectedRequest }, + { 0, 0 }, +}; + +Stanza::Stanza() +{ + d = 0; +} + +Stanza::Stanza(Stream *s, Kind k, const Jid &to, const QString &type, const QString &id) +{ + d = new Private; + + Kind kind; + if(k == Message || k == Presence || k == IQ) + kind = k; + else + kind = Message; + + d->s = s; + d->e = d->s->doc().createElementNS(s->baseNS(), Private::kindToString(kind)); + if(to.isValid()) + setTo(to); + if(!type.isEmpty()) + setType(type); + if(!id.isEmpty()) + setId(id); +} + +Stanza::Stanza(Stream *s, const QDomElement &e) +{ + d = 0; + if(e.namespaceURI() != s->baseNS()) + return; + int x = Private::stringToKind(e.tagName()); + if(x == -1) + return; + d = new Private; + d->s = s; + d->e = e; +} + +Stanza::Stanza(const Stanza &from) +{ + d = 0; + *this = from; +} + +Stanza & Stanza::operator=(const Stanza &from) +{ + delete d; + d = 0; + if(from.d) + d = new Private(*from.d); + return *this; +} + +Stanza::~Stanza() +{ + delete d; +} + +bool Stanza::isNull() const +{ + return (d ? false: true); +} + +QDomElement Stanza::element() const +{ + return d->e; +} + +QString Stanza::toString() const +{ + return Stream::xmlToString(d->e); +} + +QDomDocument & Stanza::doc() const +{ + return d->s->doc(); +} + +QString Stanza::baseNS() const +{ + return d->s->baseNS(); +} + +QString Stanza::xhtmlImNS() const +{ + return d->s->xhtmlImNS(); +} + +QString Stanza::xhtmlNS() const +{ + return d->s->xhtmlNS(); +} + +QDomElement Stanza::createElement(const QString &ns, const QString &tagName) +{ + return d->s->doc().createElementNS(ns, tagName); +} + +QDomElement Stanza::createTextElement(const QString &ns, const QString &tagName, const QString &text) +{ + QDomElement e = d->s->doc().createElementNS(ns, tagName); + e.appendChild(d->s->doc().createTextNode(text)); + return e; +} + +QDomElement Stanza::createXHTMLElement(const QString &xHTML) +{ + QDomDocument doc; + + doc.setContent(xHTML, true); + QDomElement root = doc.documentElement(); + //QDomElement e; + return (root); +} + +void Stanza::appendChild(const QDomElement &e) +{ + d->e.appendChild(e); +} + +Stanza::Kind Stanza::kind() const +{ + return (Kind)Private::stringToKind(d->e.tagName()); +} + +void Stanza::setKind(Kind k) +{ + d->e.setTagName(Private::kindToString(k)); +} + +Jid Stanza::to() const +{ + return Jid(d->e.attribute("to")); +} + +Jid Stanza::from() const +{ + return Jid(d->e.attribute("from")); +} + +QString Stanza::id() const +{ + return d->e.attribute("id"); +} + +QString Stanza::type() const +{ + return d->e.attribute("type"); +} + +QString Stanza::lang() const +{ + return d->e.attributeNS(NS_XML, "lang", QString()); +} + +void Stanza::setTo(const Jid &j) +{ + d->e.setAttribute("to", j.full()); +} + +void Stanza::setFrom(const Jid &j) +{ + d->e.setAttribute("from", j.full()); +} + +void Stanza::setId(const QString &id) +{ + d->e.setAttribute("id", id); +} + +void Stanza::setType(const QString &type) +{ + d->e.setAttribute("type", type); +} + +void Stanza::setLang(const QString &lang) +{ + d->e.setAttribute("xml:lang", lang); +} + +Stanza::Error Stanza::error() const +{ + Error err; + QDomElement e = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); + if(e.isNull()) + return err; + + // type + int x = Private::stringToErrorType(e.attribute("type")); + if(x != -1) + err.type = x; + + // condition: find first element + QDomNodeList nl = e.childNodes(); + QDomElement t; + uint n; + for(n = 0; n < nl.count(); ++n) { + QDomNode i = nl.item(n); + if(i.isElement()) { + t = i.toElement(); + break; + } + } + if(!t.isNull() && t.namespaceURI() == NS_STANZAS) { + x = Private::stringToErrorCond(t.tagName()); + if(x != -1) + err.condition = x; + } + + // text + t = e.elementsByTagNameNS(NS_STANZAS, "text").item(0).toElement(); + if(!t.isNull()) + err.text = t.text(); + else + err.text = e.text(); + + // appspec: find first non-standard namespaced element + nl = e.childNodes(); + for(n = 0; n < nl.count(); ++n) { + QDomNode i = nl.item(n); + if(i.isElement() && i.namespaceURI() != NS_STANZAS) { + err.appSpec = i.toElement(); + break; + } + } + return err; +} + +void Stanza::setError(const Error &err) +{ + // create the element if necessary + QDomElement errElem = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); + if(errElem.isNull()) { + errElem = d->e.ownerDocument().createElementNS(d->s->baseNS(), "error"); + d->e.appendChild(errElem); + } + + // error type/condition + if(d->s->old()) { + errElem.setAttribute("code", QString::number(err.condition)); + } + else { + QString stype = Private::errorTypeToString(err.type); + if(stype.isEmpty()) + return; + QString scond = Private::errorCondToString(err.condition); + if(scond.isEmpty()) + return; + + errElem.setAttribute("type", stype); + errElem.appendChild(d->e.ownerDocument().createElementNS(d->s->baseNS(), scond)); + } + + // text + if(d->s->old()) { + errElem.appendChild(d->e.ownerDocument().createTextNode(err.text)); + } + else { + QDomElement te = d->e.ownerDocument().createElementNS(d->s->baseNS(), "text"); + te.appendChild(d->e.ownerDocument().createTextNode(err.text)); + errElem.appendChild(te); + } + + // application specific + errElem.appendChild(err.appSpec); +} + +void Stanza::clearError() +{ + QDomElement errElem = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); + if(!errElem.isNull()) + d->e.removeChild(errElem); +} + +//---------------------------------------------------------------------------- +// Stream +//---------------------------------------------------------------------------- +static XmlProtocol *foo = 0; +Stream::Stream(QObject *parent) +:QObject(parent) +{ +} + +Stream::~Stream() +{ +} + +Stanza Stream::createStanza(Stanza::Kind k, const Jid &to, const QString &type, const QString &id) +{ + return Stanza(this, k, to, type, id); +} + +Stanza Stream::createStanza(const QDomElement &e) +{ + return Stanza(this, e); +} + +QString Stream::xmlToString(const QDomElement &e, bool clip) +{ + if(!foo) + foo = new CoreProtocol; + return foo->elementToString(e, clip); +} + +//---------------------------------------------------------------------------- +// ClientStream +//---------------------------------------------------------------------------- +enum { + Idle, + Connecting, + WaitVersion, + WaitTLS, + NeedParams, + Active, + Closing +}; + +enum { + Client, + Server +}; + +class ClientStream::Private +{ +public: + Private() + { + conn = 0; + bs = 0; + ss = 0; + tlsHandler = 0; + tls = 0; + sasl = 0; + in.setAutoDelete(true); + + oldOnly = false; + allowPlain = false; + mutualAuth = false; + haveLocalAddr = false; + minimumSSF = 0; + maximumSSF = 0; + doBinding = true; + + in_rrsig = false; + + reset(); + } + + void reset() + { + state = Idle; + notify = 0; + newStanzas = false; + sasl_ssf = 0; + tls_warned = false; + using_tls = false; + } + + Jid jid; + QString server; + bool oldOnly; + bool allowPlain, mutualAuth; + bool haveLocalAddr; + QHostAddress localAddr; + Q_UINT16 localPort; + int minimumSSF, maximumSSF; + QString sasl_mech; + bool doBinding; + + bool in_rrsig; + + Connector *conn; + ByteStream *bs; + TLSHandler *tlsHandler; + QCA::TLS *tls; + QCA::SASL *sasl; + SecureStream *ss; + CoreProtocol client; + CoreProtocol srv; + + QString defRealm; + + int mode; + int state; + int notify; + bool newStanzas; + int sasl_ssf; + bool tls_warned, using_tls; + bool doAuth; + + QStringList sasl_mechlist; + + int errCond; + QString errText; + QDomElement errAppSpec; + + QPtrList in; + + QTimer noopTimer; + int noop_time; +}; + +ClientStream::ClientStream(Connector *conn, TLSHandler *tlsHandler, QObject *parent) +:Stream(parent) +{ + d = new Private; + d->mode = Client; + d->conn = conn; + connect(d->conn, SIGNAL(connected()), SLOT(cr_connected())); + connect(d->conn, SIGNAL(error()), SLOT(cr_error())); + + d->noop_time = 0; + connect(&d->noopTimer, SIGNAL(timeout()), SLOT(doNoop())); + + d->tlsHandler = tlsHandler; +} + +ClientStream::ClientStream(const QString &host, const QString &defRealm, ByteStream *bs, QCA::TLS *tls, QObject *parent) +:Stream(parent) +{ + d = new Private; + d->mode = Server; + d->bs = bs; + connect(d->bs, SIGNAL(connectionClosed()), SLOT(bs_connectionClosed())); + connect(d->bs, SIGNAL(delayedCloseFinished()), SLOT(bs_delayedCloseFinished())); + connect(d->bs, SIGNAL(error(int)), SLOT(bs_error(int))); + + QByteArray spare = d->bs->read(); + + d->ss = new SecureStream(d->bs); + connect(d->ss, SIGNAL(readyRead()), SLOT(ss_readyRead())); + connect(d->ss, SIGNAL(bytesWritten(int)), SLOT(ss_bytesWritten(int))); + connect(d->ss, SIGNAL(tlsHandshaken()), SLOT(ss_tlsHandshaken())); + connect(d->ss, SIGNAL(tlsClosed()), SLOT(ss_tlsClosed())); + connect(d->ss, SIGNAL(error(int)), SLOT(ss_error(int))); + + d->server = host; + d->defRealm = defRealm; + + d->tls = tls; + + d->srv.startClientIn(genId()); + //d->srv.startServerIn(genId()); + //d->state = Connecting; + //d->jid = Jid(); + //d->server = QString(); +} + +ClientStream::~ClientStream() +{ + reset(); + delete d; +} + +void ClientStream::reset(bool all) +{ + d->reset(); + d->noopTimer.stop(); + + // delete securestream + delete d->ss; + d->ss = 0; + + // reset sasl + delete d->sasl; + d->sasl = 0; + + // client + if(d->mode == Client) { + // reset tls + if(d->tlsHandler) + d->tlsHandler->reset(); + + // reset connector + if(d->bs) { + d->bs->close(); + d->bs = 0; + } + d->conn->done(); + + // reset state machine + d->client.reset(); + } + // server + else { + if(d->tls) + d->tls->reset(); + + if(d->bs) { + d->bs->close(); + d->bs = 0; + } + + d->srv.reset(); + } + + if(all) + d->in.clear(); +} + +Jid ClientStream::jid() const +{ + return d->jid; +} + +void ClientStream::connectToServer(const Jid &jid, bool auth) +{ + reset(true); + d->state = Connecting; + d->jid = jid; + d->doAuth = auth; + d->server = d->jid.domain(); + + d->conn->connectToServer(d->server); +} + +void ClientStream::continueAfterWarning() +{ + if(d->state == WaitVersion) { + // if we don't have TLS yet, then we're never going to get it + if(!d->tls_warned && !d->using_tls) { + d->tls_warned = true; + d->state = WaitTLS; + warning(WarnNoTLS); + return; + } + d->state = Connecting; + processNext(); + } + else if(d->state == WaitTLS) { + d->state = Connecting; + processNext(); + } +} + +void ClientStream::accept() +{ + d->srv.host = d->server; + processNext(); +} + +bool ClientStream::isActive() const +{ + return (d->state != Idle) ? true: false; +} + +bool ClientStream::isAuthenticated() const +{ + return (d->state == Active) ? true: false; +} + +void ClientStream::setUsername(const QString &s) +{ + if(d->sasl) + d->sasl->setUsername(s); +} + +void ClientStream::setPassword(const QString &s) +{ + if(d->client.old) { + d->client.setPassword(s); + } + else { + if(d->sasl) + d->sasl->setPassword(s); + } +} + +void ClientStream::setRealm(const QString &s) +{ + if(d->sasl) + d->sasl->setRealm(s); +} + +void ClientStream::continueAfterParams() +{ + if(d->state == NeedParams) { + d->state = Connecting; + if(d->client.old) { + processNext(); + } + else { + if(d->sasl) + d->sasl->continueAfterParams(); + } + } +} + +void ClientStream::setResourceBinding(bool b) +{ + d->doBinding = b; +} + +void ClientStream::setNoopTime(int mills) +{ + d->noop_time = mills; + + if(d->state != Active) + return; + + if(d->noop_time == 0) { + d->noopTimer.stop(); + return; + } + d->noopTimer.start(d->noop_time); +} + +QString ClientStream::saslMechanism() const +{ + return d->client.saslMech(); +} + +int ClientStream::saslSSF() const +{ + return d->sasl_ssf; +} + +void ClientStream::setSASLMechanism(const QString &s) +{ + d->sasl_mech = s; +} + +void ClientStream::setLocalAddr(const QHostAddress &addr, Q_UINT16 port) +{ + d->haveLocalAddr = true; + d->localAddr = addr; + d->localPort = port; +} + +int ClientStream::errorCondition() const +{ + return d->errCond; +} + +QString ClientStream::errorText() const +{ + return d->errText; +} + +QDomElement ClientStream::errorAppSpec() const +{ + return d->errAppSpec; +} + +bool ClientStream::old() const +{ + return d->client.old; +} + +void ClientStream::close() +{ + if(d->state == Active) { + d->state = Closing; + d->client.shutdown(); + processNext(); + } + else if(d->state != Idle && d->state != Closing) { + reset(); + } +} + +QDomDocument & ClientStream::doc() const +{ + return d->client.doc; +} + +QString ClientStream::baseNS() const +{ + return NS_CLIENT; +} + +QString ClientStream::xhtmlImNS() const +{ + return NS_XHTML_IM; +} + +QString ClientStream::xhtmlNS() const +{ + return NS_XHTML; +} + +void ClientStream::setAllowPlain(bool b) +{ + d->allowPlain = b; +} + +void ClientStream::setRequireMutualAuth(bool b) +{ + d->mutualAuth = b; +} + +void ClientStream::setSSFRange(int low, int high) +{ + d->minimumSSF = low; + d->maximumSSF = high; +} + +void ClientStream::setOldOnly(bool b) +{ + d->oldOnly = b; +} + +bool ClientStream::stanzaAvailable() const +{ + return (!d->in.isEmpty()); +} + +Stanza ClientStream::read() +{ + if(d->in.isEmpty()) + return Stanza(); + else { + Stanza *sp = d->in.getFirst(); + Stanza s = *sp; + d->in.removeRef(sp); + return s; + } +} + +void ClientStream::write(const Stanza &s) +{ + if(d->state == Active) { + d->client.sendStanza(s.element()); + processNext(); + } +} + +void ClientStream::cr_connected() +{ + d->bs = d->conn->stream(); + connect(d->bs, SIGNAL(connectionClosed()), SLOT(bs_connectionClosed())); + connect(d->bs, SIGNAL(delayedCloseFinished()), SLOT(bs_delayedCloseFinished())); + + QByteArray spare = d->bs->read(); + + d->ss = new SecureStream(d->bs); + connect(d->ss, SIGNAL(readyRead()), SLOT(ss_readyRead())); + connect(d->ss, SIGNAL(bytesWritten(int)), SLOT(ss_bytesWritten(int))); + connect(d->ss, SIGNAL(tlsHandshaken()), SLOT(ss_tlsHandshaken())); + connect(d->ss, SIGNAL(tlsClosed()), SLOT(ss_tlsClosed())); + connect(d->ss, SIGNAL(error(int)), SLOT(ss_error(int))); + + //d->client.startDialbackOut("andbit.net", "im.pyxa.org"); + //d->client.startServerOut(d->server); + + d->client.startClientOut(d->jid, d->oldOnly, d->conn->useSSL(), d->doAuth); + d->client.setAllowTLS(d->tlsHandler ? true: false); + d->client.setAllowBind(d->doBinding); + d->client.setAllowPlain(d->allowPlain); + + /*d->client.jid = d->jid; + d->client.server = d->server; + d->client.allowPlain = d->allowPlain; + d->client.oldOnly = d->oldOnly; + d->client.sasl_mech = d->sasl_mech; + d->client.doTLS = d->tlsHandler ? true: false; + d->client.doBinding = d->doBinding;*/ + + QGuardedPtr self = this; + connected(); + if(!self) + return; + + // immediate SSL? + if(d->conn->useSSL()) { + d->using_tls = true; + d->ss->startTLSClient(d->tlsHandler, d->server, spare); + } + else { + d->client.addIncomingData(spare); + processNext(); + } +} + +void ClientStream::cr_error() +{ + reset(); + error(ErrConnection); +} + +void ClientStream::bs_connectionClosed() +{ + reset(); + connectionClosed(); +} + +void ClientStream::bs_delayedCloseFinished() +{ + // we don't care about this (we track all important data ourself) +} + +void ClientStream::bs_error(int) +{ + // TODO +} + +void ClientStream::ss_readyRead() +{ + QByteArray a = d->ss->read(); + +#ifdef XMPP_DEBUG + QCString cs(a.data(), a.size()+1); + fprintf(stderr, "ClientStream: recv: %d [%s]\n", a.size(), cs.data()); +#endif + + if(d->mode == Client) + d->client.addIncomingData(a); + else + d->srv.addIncomingData(a); + if(d->notify & CoreProtocol::NRecv) { +#ifdef XMPP_DEBUG + printf("We needed data, so let's process it\n"); +#endif + processNext(); + } +} + +void ClientStream::ss_bytesWritten(int bytes) +{ + if(d->mode == Client) + d->client.outgoingDataWritten(bytes); + else + d->srv.outgoingDataWritten(bytes); + + if(d->notify & CoreProtocol::NSend) { +#ifdef XMPP_DEBUG + printf("We were waiting for data to be written, so let's process\n"); +#endif + processNext(); + } +} + +void ClientStream::ss_tlsHandshaken() +{ + QGuardedPtr self = this; + securityLayerActivated(LayerTLS); + if(!self) + return; + processNext(); +} + +void ClientStream::ss_tlsClosed() +{ + reset(); + connectionClosed(); +} + +void ClientStream::ss_error(int x) +{ + if(x == SecureStream::ErrTLS) { + reset(); + d->errCond = TLSFail; + error(ErrTLS); + } + else { + reset(); + error(ErrSecurityLayer); + } +} + +void ClientStream::sasl_clientFirstStep(const QString &mech, const QByteArray *stepData) +{ + d->client.setSASLFirst(mech, stepData ? *stepData : QByteArray()); + //d->client.sasl_mech = mech; + //d->client.sasl_firstStep = stepData ? true : false; + //d->client.sasl_step = stepData ? *stepData : QByteArray(); + + processNext(); +} + +void ClientStream::sasl_nextStep(const QByteArray &stepData) +{ + if(d->mode == Client) + d->client.setSASLNext(stepData); + //d->client.sasl_step = stepData; + else + d->srv.setSASLNext(stepData); + //d->srv.sasl_step = stepData; + + processNext(); +} + +void ClientStream::sasl_needParams(bool user, bool authzid, bool pass, bool realm) +{ +#ifdef XMPP_DEBUG + printf("need params: %d,%d,%d,%d\n", user, authzid, pass, realm); +#endif + if(authzid && !user) { + d->sasl->setAuthzid(d->jid.bare()); + //d->sasl->setAuthzid("infiniti.homelesshackers.org"); + } + if(user || pass || realm) { + d->state = NeedParams; + needAuthParams(user, pass, realm); + } + else + d->sasl->continueAfterParams(); +} + +void ClientStream::sasl_authCheck(const QString &user, const QString &) +{ +//#ifdef XMPP_DEBUG +// printf("authcheck: [%s], [%s]\n", user.latin1(), authzid.latin1()); +//#endif + QString u = user; + int n = u.find('@'); + if(n != -1) + u.truncate(n); + d->srv.user = u; + d->sasl->continueAfterAuthCheck(); +} + +void ClientStream::sasl_authenticated() +{ +#ifdef XMPP_DEBUG + printf("sasl authed!!\n"); +#endif + d->sasl_ssf = d->sasl->ssf(); + + if(d->mode == Server) { + d->srv.setSASLAuthed(); + processNext(); + } +} + +void ClientStream::sasl_error(int) +{ +//#ifdef XMPP_DEBUG +// printf("sasl error: %d\n", c); +//#endif + // has to be auth error + int x = convertedSASLCond(); + reset(); + d->errCond = x; + error(ErrAuth); +} + +void ClientStream::srvProcessNext() +{ + while(1) { + printf("Processing step...\n"); + if(!d->srv.processStep()) { + int need = d->srv.need; + if(need == CoreProtocol::NNotify) { + d->notify = d->srv.notify; + if(d->notify & CoreProtocol::NSend) + printf("More data needs to be written to process next step\n"); + if(d->notify & CoreProtocol::NRecv) + printf("More data is needed to process next step\n"); + } + else if(need == CoreProtocol::NSASLMechs) { + if(!d->sasl) { + d->sasl = new QCA::SASL; + connect(d->sasl, SIGNAL(authCheck(const QString &, const QString &)), SLOT(sasl_authCheck(const QString &, const QString &))); + connect(d->sasl, SIGNAL(nextStep(const QByteArray &)), SLOT(sasl_nextStep(const QByteArray &))); + connect(d->sasl, SIGNAL(authenticated()), SLOT(sasl_authenticated())); + connect(d->sasl, SIGNAL(error(int)), SLOT(sasl_error(int))); + + //d->sasl->setAllowAnonymous(false); + //d->sasl->setRequirePassCredentials(true); + //d->sasl->setExternalAuthID("localhost"); + + d->sasl->setMinimumSSF(0); + d->sasl->setMaximumSSF(256); + + QStringList list; + // TODO: d->server is probably wrong here + if(!d->sasl->startServer("xmpp", d->server, d->defRealm, &list)) { + printf("Error initializing SASL\n"); + return; + } + d->sasl_mechlist = list; + } + d->srv.setSASLMechList(d->sasl_mechlist); + continue; + } + else if(need == CoreProtocol::NStartTLS) { + printf("Need StartTLS\n"); + if(!d->tls->startServer()) { + printf("unable to start server!\n"); + // TODO + return; + } + QByteArray a = d->srv.spare; + d->ss->startTLSServer(d->tls, a); + } + else if(need == CoreProtocol::NSASLFirst) { + printf("Need SASL First Step\n"); + QByteArray a = d->srv.saslStep(); + d->sasl->putServerFirstStep(d->srv.saslMech(), a); + } + else if(need == CoreProtocol::NSASLNext) { + printf("Need SASL Next Step\n"); + QByteArray a = d->srv.saslStep(); + QCString cs(a.data(), a.size()+1); + printf("[%s]\n", cs.data()); + d->sasl->putStep(a); + } + else if(need == CoreProtocol::NSASLLayer) { + } + + // now we can announce stanzas + //if(!d->in.isEmpty()) + // readyRead(); + return; + } + + d->notify = 0; + + int event = d->srv.event; + printf("event: %d\n", event); + switch(event) { + case CoreProtocol::EError: { + printf("Error! Code=%d\n", d->srv.errorCode); + reset(); + error(ErrProtocol); + //handleError(); + return; + } + case CoreProtocol::ESend: { + QByteArray a = d->srv.takeOutgoingData(); + QCString cs(a.size()+1); + memcpy(cs.data(), a.data(), a.size()); + printf("Need Send: {%s}\n", cs.data()); + d->ss->write(a); + break; + } + case CoreProtocol::ERecvOpen: { + printf("Break (RecvOpen)\n"); + + // calculate key + QCString str = QCA::SHA1::hashToString("secret").utf8(); + str = QCA::SHA1::hashToString(str + "im.pyxa.org").utf8(); + str = QCA::SHA1::hashToString(str + d->srv.id.utf8()).utf8(); + d->srv.setDialbackKey(str); + + //d->srv.setDialbackKey("3c5d721ea2fcc45b163a11420e4e358f87e3142a"); + + if(d->srv.to != d->server) { + // host-gone, host-unknown, see-other-host + d->srv.shutdownWithError(CoreProtocol::HostUnknown); + } + else + d->srv.setFrom(d->server); + break; + } + case CoreProtocol::ESASLSuccess: { + printf("Break SASL Success\n"); + disconnect(d->sasl, SIGNAL(error(int)), this, SLOT(sasl_error(int))); + QByteArray a = d->srv.spare; + d->ss->setLayerSASL(d->sasl, a); + break; + } + case CoreProtocol::EPeerClosed: { + // TODO: this isn' an error + printf("peer closed\n"); + reset(); + error(ErrProtocol); + return; + } + } + } +} + +void ClientStream::doReadyRead() +{ + //QGuardedPtr self = this; + readyRead(); + //if(!self) + // return; + //d->in_rrsig = false; +} + +void ClientStream::processNext() +{ + if(d->mode == Server) { + srvProcessNext(); + return; + } + + QGuardedPtr self = this; + + while(1) { +#ifdef XMPP_DEBUG + printf("Processing step...\n"); +#endif + bool ok = d->client.processStep(); + // deal with send/received items + for(QValueList::ConstIterator it = d->client.transferItemList.begin(); it != d->client.transferItemList.end(); ++it) { + const XmlProtocol::TransferItem &i = *it; + if(i.isExternal) + continue; + QString str; + if(i.isString) { + // skip whitespace pings + if(i.str.stripWhiteSpace().isEmpty()) + continue; + str = i.str; + } + else + str = d->client.elementToString(i.elem); + if(i.isSent) + outgoingXml(str); + else + incomingXml(str); + } + + if(!ok) { + bool cont = handleNeed(); + + // now we can announce stanzas + //if(!d->in_rrsig && !d->in.isEmpty()) { + if(!d->in.isEmpty()) { + //d->in_rrsig = true; + QTimer::singleShot(0, this, SLOT(doReadyRead())); + } + + if(cont) + continue; + return; + } + + int event = d->client.event; + d->notify = 0; + switch(event) { + case CoreProtocol::EError: { +#ifdef XMPP_DEBUG + printf("Error! Code=%d\n", d->client.errorCode); +#endif + handleError(); + return; + } + case CoreProtocol::ESend: { + QByteArray a = d->client.takeOutgoingData(); +#ifdef XMPP_DEBUG + QCString cs(a.size()+1); + memcpy(cs.data(), a.data(), a.size()); + printf("Need Send: {%s}\n", cs.data()); +#endif + d->ss->write(a); + break; + } + case CoreProtocol::ERecvOpen: { +#ifdef XMPP_DEBUG + printf("Break (RecvOpen)\n"); +#endif + +#ifdef XMPP_TEST + QString s = QString("handshake success (lang=[%1]").arg(d->client.lang); + if(!d->client.from.isEmpty()) + s += QString(", from=[%1]").arg(d->client.from); + s += ')'; + TD::msg(s); +#endif + + if(d->client.old) { + d->state = WaitVersion; + warning(WarnOldVersion); + return; + } + break; + } + case CoreProtocol::EFeatures: { +#ifdef XMPP_DEBUG + printf("Break (Features)\n"); +#endif + if(!d->tls_warned && !d->using_tls && !d->client.features.tls_supported) { + d->tls_warned = true; + d->state = WaitTLS; + warning(WarnNoTLS); + return; + } + break; + } + case CoreProtocol::ESASLSuccess: { +#ifdef XMPP_DEBUG + printf("Break SASL Success\n"); +#endif + break; + } + case CoreProtocol::EReady: { +#ifdef XMPP_DEBUG + printf("Done!\n"); +#endif + // grab the JID, in case it changed + // TODO: d->jid = d->client.jid; + d->state = Active; + setNoopTime(d->noop_time); + authenticated(); + if(!self) + return; + break; + } + case CoreProtocol::EPeerClosed: { +#ifdef XMPP_DEBUG + printf("DocumentClosed\n"); +#endif + reset(); + connectionClosed(); + return; + } + case CoreProtocol::EStanzaReady: { +#ifdef XMPP_DEBUG + printf("StanzaReady\n"); +#endif + // store the stanza for now, announce after processing all events + Stanza s = createStanza(d->client.recvStanza()); + if(s.isNull()) + break; + d->in.append(new Stanza(s)); + break; + } + case CoreProtocol::EStanzaSent: { +#ifdef XMPP_DEBUG + printf("StanzasSent\n"); +#endif + stanzaWritten(); + if(!self) + return; + break; + } + case CoreProtocol::EClosed: { +#ifdef XMPP_DEBUG + printf("Closed\n"); +#endif + reset(); + delayedCloseFinished(); + return; + } + } + } +} + +bool ClientStream::handleNeed() +{ + int need = d->client.need; + if(need == CoreProtocol::NNotify) { + d->notify = d->client.notify; +#ifdef XMPP_DEBUG + if(d->notify & CoreProtocol::NSend) + printf("More data needs to be written to process next step\n"); + if(d->notify & CoreProtocol::NRecv) + printf("More data is needed to process next step\n"); +#endif + return false; + } + + d->notify = 0; + switch(need) { + case CoreProtocol::NStartTLS: { +#ifdef XMPP_DEBUG + printf("Need StartTLS\n"); +#endif + d->using_tls = true; + d->ss->startTLSClient(d->tlsHandler, d->server, d->client.spare); + return false; + } + case CoreProtocol::NSASLFirst: { +#ifdef XMPP_DEBUG + printf("Need SASL First Step\n"); +#endif + // no SASL plugin? fall back to Simple SASL + if(!QCA::isSupported(QCA::CAP_SASL)) { + // Simple SASL needs MD5. do we have that either? + if(!QCA::isSupported(QCA::CAP_MD5)) + QCA::insertProvider(createProviderHash()); + QCA::insertProvider(createProviderSimpleSASL()); + } + + d->sasl = new QCA::SASL; + connect(d->sasl, SIGNAL(clientFirstStep(const QString &, const QByteArray *)), SLOT(sasl_clientFirstStep(const QString &, const QByteArray *))); + connect(d->sasl, SIGNAL(nextStep(const QByteArray &)), SLOT(sasl_nextStep(const QByteArray &))); + connect(d->sasl, SIGNAL(needParams(bool, bool, bool, bool)), SLOT(sasl_needParams(bool, bool, bool, bool))); + connect(d->sasl, SIGNAL(authenticated()), SLOT(sasl_authenticated())); + connect(d->sasl, SIGNAL(error(int)), SLOT(sasl_error(int))); + + if(d->haveLocalAddr) + d->sasl->setLocalAddr(d->localAddr, d->localPort); + if(d->conn->havePeerAddress()) + d->sasl->setRemoteAddr(d->conn->peerAddress(), d->conn->peerPort()); + + d->sasl->setAllowAnonymous(false); + + //d->sasl_mech = "ANONYMOUS"; + //d->sasl->setRequirePassCredentials(true); + + //d->sasl->setExternalAuthID("localhost"); + //d->sasl->setExternalSSF(64); + //d->sasl_mech = "EXTERNAL"; + + d->sasl->setAllowPlain(d->allowPlain); + d->sasl->setRequireMutualAuth(d->mutualAuth); + + d->sasl->setMinimumSSF(d->minimumSSF); + d->sasl->setMaximumSSF(d->maximumSSF); + + QStringList ml; + if(!d->sasl_mech.isEmpty()) + ml += d->sasl_mech; + else + ml = d->client.features.sasl_mechs; + + if(!d->sasl->startClient("xmpp", d->server, ml, true)) { + int x = convertedSASLCond(); + reset(); + d->errCond = x; + error(ErrAuth); + return false; + } + return false; + } + case CoreProtocol::NSASLNext: { +#ifdef XMPP_DEBUG + printf("Need SASL Next Step\n"); +#endif + QByteArray a = d->client.saslStep(); + d->sasl->putStep(a); + return false; + } + case CoreProtocol::NSASLLayer: { + // SecureStream will handle the errors from this point + disconnect(d->sasl, SIGNAL(error(int)), this, SLOT(sasl_error(int))); + d->ss->setLayerSASL(d->sasl, d->client.spare); + if(d->sasl_ssf > 0) { + QGuardedPtr self = this; + securityLayerActivated(LayerSASL); + if(!self) + return false; + } + break; + } + case CoreProtocol::NPassword: { +#ifdef XMPP_DEBUG + printf("Need Password\n"); +#endif + d->state = NeedParams; + needAuthParams(false, true, false); + return false; + } + } + + return true; +} + +int ClientStream::convertedSASLCond() const +{ + int x = d->sasl->errorCondition(); + if(x == QCA::SASL::NoMech) + return NoMech; + else if(x == QCA::SASL::BadProto) + return BadProto; + else if(x == QCA::SASL::BadServ) + return BadServ; + else if(x == QCA::SASL::TooWeak) + return MechTooWeak; + else + return GenericAuthError; +} + +void ClientStream::doNoop() +{ + if(d->state == Active) { +#ifdef XMPP_DEBUG + printf("doPing\n"); +#endif + d->client.sendWhitespace(); + processNext(); + } +} + +void ClientStream::writeDirect(const QString &s) +{ + if(d->state == Active) { +#ifdef XMPP_DEBUG + printf("writeDirect\n"); +#endif + d->client.sendDirect(s); + processNext(); + } +} + +void ClientStream::handleError() +{ + int c = d->client.errorCode; + if(c == CoreProtocol::ErrParse) { + reset(); + error(ErrParse); + } + else if(c == CoreProtocol::ErrProtocol) { + reset(); + error(ErrProtocol); + } + else if(c == CoreProtocol::ErrStream) { + int x = d->client.errCond; + QString text = d->client.errText; + QDomElement appSpec = d->client.errAppSpec; + + int connErr = -1; + int strErr = -1; + + switch(x) { + case CoreProtocol::BadFormat: { break; } // should NOT happen (we send the right format) + case CoreProtocol::BadNamespacePrefix: { break; } // should NOT happen (we send prefixes) + case CoreProtocol::Conflict: { strErr = Conflict; break; } + case CoreProtocol::ConnectionTimeout: { strErr = ConnectionTimeout; break; } + case CoreProtocol::HostGone: { connErr = HostGone; break; } + case CoreProtocol::HostUnknown: { connErr = HostUnknown; break; } + case CoreProtocol::ImproperAddressing: { break; } // should NOT happen (we aren't a server) + case CoreProtocol::InternalServerError: { strErr = InternalServerError; break; } + case CoreProtocol::InvalidFrom: { strErr = InvalidFrom; break; } + case CoreProtocol::InvalidId: { break; } // should NOT happen (clients don't specify id) + case CoreProtocol::InvalidNamespace: { break; } // should NOT happen (we set the right ns) + case CoreProtocol::InvalidXml: { strErr = InvalidXml; break; } // shouldn't happen either, but just in case ... + case CoreProtocol::StreamNotAuthorized: { break; } // should NOT happen (we're not stupid) + case CoreProtocol::PolicyViolation: { strErr = PolicyViolation; break; } + case CoreProtocol::RemoteConnectionFailed: { connErr = RemoteConnectionFailed; break; } + case CoreProtocol::ResourceConstraint: { strErr = ResourceConstraint; break; } + case CoreProtocol::RestrictedXml: { strErr = InvalidXml; break; } // group with this one + case CoreProtocol::SeeOtherHost: { connErr = SeeOtherHost; break; } + case CoreProtocol::SystemShutdown: { strErr = SystemShutdown; break; } + case CoreProtocol::UndefinedCondition: { break; } // leave as null error + case CoreProtocol::UnsupportedEncoding: { break; } // should NOT happen (we send good encoding) + case CoreProtocol::UnsupportedStanzaType: { break; } // should NOT happen (we're not stupid) + case CoreProtocol::UnsupportedVersion: { connErr = UnsupportedVersion; break; } + case CoreProtocol::XmlNotWellFormed: { strErr = InvalidXml; break; } // group with this one + default: { break; } + } + + reset(); + + d->errText = text; + d->errAppSpec = appSpec; + if(connErr != -1) { + d->errCond = connErr; + error(ErrNeg); + } + else { + if(strErr != -1) + d->errCond = strErr; + else + d->errCond = GenericStreamError; + error(ErrStream); + } + } + else if(c == CoreProtocol::ErrStartTLS) { + reset(); + d->errCond = TLSStart; + error(ErrTLS); + } + else if(c == CoreProtocol::ErrAuth) { + int x = d->client.errCond; + int r = GenericAuthError; + if(d->client.old) { + if(x == 401) // not authorized + r = NotAuthorized; + else if(x == 409) // conflict + r = GenericAuthError; + else if(x == 406) // not acceptable (this should NOT happen) + r = GenericAuthError; + } + else { + switch(x) { + case CoreProtocol::Aborted: { r = GenericAuthError; break; } // should NOT happen (we never send ) + case CoreProtocol::IncorrectEncoding: { r = GenericAuthError; break; } // should NOT happen + case CoreProtocol::InvalidAuthzid: { r = InvalidAuthzid; break; } + case CoreProtocol::InvalidMech: { r = InvalidMech; break; } + case CoreProtocol::MechTooWeak: { r = MechTooWeak; break; } + case CoreProtocol::NotAuthorized: { r = NotAuthorized; break; } + case CoreProtocol::TemporaryAuthFailure: { r = TemporaryAuthFailure; break; } + } + } + reset(); + d->errCond = r; + error(ErrAuth); + } + else if(c == CoreProtocol::ErrPlain) { + reset(); + d->errCond = NoMech; + error(ErrAuth); + } + else if(c == CoreProtocol::ErrBind) { + int r = -1; + if(d->client.errCond == CoreProtocol::BindBadRequest) { + // should NOT happen + } + else if(d->client.errCond == CoreProtocol::BindNotAllowed) { + r = BindNotAllowed; + } + else if(d->client.errCond == CoreProtocol::BindConflict) { + r = BindConflict; + } + + if(r != -1) { + reset(); + d->errCond = r; + error(ErrBind); + } + else { + reset(); + error(ErrProtocol); + } + } +} + +//---------------------------------------------------------------------------- +// Debug +//---------------------------------------------------------------------------- +Debug::~Debug() +{ +} + +#ifdef XMPP_TEST +TD::TD() +{ +} + +TD::~TD() +{ +} + +void TD::msg(const QString &s) +{ + if(debug_ptr) + debug_ptr->msg(s); +} + +void TD::outgoingTag(const QString &s) +{ + if(debug_ptr) + debug_ptr->outgoingTag(s); +} + +void TD::incomingTag(const QString &s) +{ + if(debug_ptr) + debug_ptr->incomingTag(s); +} + +void TD::outgoingXml(const QDomElement &e) +{ + if(debug_ptr) + debug_ptr->outgoingXml(e); +} + +void TD::incomingXml(const QDomElement &e) +{ + if(debug_ptr) + debug_ptr->incomingXml(e); +} +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/td.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/td.h new file mode 100644 index 00000000..b636e190 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/td.h @@ -0,0 +1,20 @@ +#ifndef TESTDEBUG_H +#define TESTDEBUG_H + +#include + +class TD +{ +public: + TD(); + ~TD(); + + static void msg(const QString &); + static void outgoingTag(const QString &); + static void incomingTag(const QString &); + static void outgoingXml(const QDomElement &); + static void incomingXml(const QDomElement &); +}; + +#endif + diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/tlshandler.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/tlshandler.cpp new file mode 100644 index 00000000..f3ac0067 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/tlshandler.cpp @@ -0,0 +1,138 @@ +/* + * tlshandler.cpp - abstract wrapper for TLS + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"xmpp.h" + +#include +#include"qca.h" + +using namespace XMPP; + +//---------------------------------------------------------------------------- +// TLSHandler +//---------------------------------------------------------------------------- +TLSHandler::TLSHandler(QObject *parent) +:QObject(parent) +{ +} + +TLSHandler::~TLSHandler() +{ +} + + +//---------------------------------------------------------------------------- +// QCATLSHandler +//---------------------------------------------------------------------------- +class QCATLSHandler::Private +{ +public: + QCA::TLS *tls; + int state, err; +}; + +QCATLSHandler::QCATLSHandler(QCA::TLS *parent) +:TLSHandler(parent) +{ + d = new Private; + d->tls = parent; + connect(d->tls, SIGNAL(handshaken()), SLOT(tls_handshaken())); + connect(d->tls, SIGNAL(readyRead()), SLOT(tls_readyRead())); + connect(d->tls, SIGNAL(readyReadOutgoing(int)), SLOT(tls_readyReadOutgoing(int))); + connect(d->tls, SIGNAL(closed()), SLOT(tls_closed())); + connect(d->tls, SIGNAL(error(int)), SLOT(tls_error(int))); + d->state = 0; + d->err = -1; +} + +QCATLSHandler::~QCATLSHandler() +{ + delete d; +} + +QCA::TLS *QCATLSHandler::tls() const +{ + return d->tls; +} + +int QCATLSHandler::tlsError() const +{ + return d->err; +} + +void QCATLSHandler::reset() +{ + d->tls->reset(); + d->state = 0; +} + +void QCATLSHandler::startClient(const QString &host) +{ + d->state = 0; + d->err = -1; + if(!d->tls->startClient(host)) + QTimer::singleShot(0, this, SIGNAL(fail())); +} + +void QCATLSHandler::write(const QByteArray &a) +{ + d->tls->write(a); +} + +void QCATLSHandler::writeIncoming(const QByteArray &a) +{ + d->tls->writeIncoming(a); +} + +void QCATLSHandler::continueAfterHandshake() +{ + if(d->state == 2) { + success(); + d->state = 3; + } +} + +void QCATLSHandler::tls_handshaken() +{ + d->state = 2; + tlsHandshaken(); +} + +void QCATLSHandler::tls_readyRead() +{ + readyRead(d->tls->read()); +} + +void QCATLSHandler::tls_readyReadOutgoing(int plainBytes) +{ + readyReadOutgoing(d->tls->readOutgoing(), plainBytes); +} + +void QCATLSHandler::tls_closed() +{ + closed(); +} + +void QCATLSHandler::tls_error(int x) +{ + d->err = x; + d->state = 0; + fail(); +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.cpp new file mode 100644 index 00000000..c70a04a9 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.cpp @@ -0,0 +1,543 @@ +/* + * xmlprotocol.cpp - state machine for 'jabber-like' protocols + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"xmlprotocol.h" + +#include"bytestream.h" + +using namespace XMPP; + +// stripExtraNS +// +// This function removes namespace information from various nodes for +// display purposes only (the element is pretty much useless for processing +// after this). We do this because QXml is a bit overzealous about outputting +// redundant namespaces. +static QDomElement stripExtraNS(const QDomElement &e) +{ + // find closest parent with a namespace + QDomNode par = e.parentNode(); + while(!par.isNull() && par.namespaceURI().isNull()) + par = par.parentNode(); + bool noShowNS = false; + if(!par.isNull() && par.namespaceURI() == e.namespaceURI()) + noShowNS = true; + + // build qName (prefix:localName) + QString qName; + if(!e.prefix().isEmpty()) + qName = e.prefix() + ':' + e.localName(); + else + qName = e.tagName(); + + QDomElement i; + uint x; + if(noShowNS) + i = e.ownerDocument().createElement(qName); + else + i = e.ownerDocument().createElementNS(e.namespaceURI(), qName); + + // copy attributes + QDomNamedNodeMap al = e.attributes(); + for(x = 0; x < al.count(); ++x) { + QDomAttr a = al.item(x).cloneNode().toAttr(); + + // don't show xml namespace + if(a.namespaceURI() == NS_XML) + i.setAttribute(QString("xml:") + a.name(), a.value()); + else + i.setAttributeNodeNS(a); + } + + // copy children + QDomNodeList nl = e.childNodes(); + for(x = 0; x < nl.count(); ++x) { + QDomNode n = nl.item(x); + if(n.isElement()) + i.appendChild(stripExtraNS(n.toElement())); + else + i.appendChild(n.cloneNode()); + } + return i; +} + +// xmlToString +// +// This function converts a QDomElement into a QString, using stripExtraNS +// to make it pretty. +static QString xmlToString(const QDomElement &e, const QString &fakeNS, const QString &fakeQName, bool clip) +{ + QDomElement i = e.cloneNode().toElement(); + + // It seems QDom can only have one namespace attribute at a time (see docElement 'HACK'). + // Fortunately we only need one kind depending on the input, so it is specified here. + QDomElement fake = e.ownerDocument().createElementNS(fakeNS, fakeQName); + fake.appendChild(i); + fake = stripExtraNS(fake); + QString out; + { + QTextStream ts(&out, IO_WriteOnly); + fake.firstChild().save(ts, 0); + } + // 'clip' means to remove any unwanted (and unneeded) characters, such as a trailing newline + if(clip) { + int n = out.findRev('>'); + out.truncate(n+1); + } + return out; +} + +// createRootXmlTags +// +// This function creates three QStrings, one being an processing +// instruction, and the others being the opening and closing tags of an +// element, and . This basically allows us to get the raw XML +// text needed to open/close an XML stream, without resorting to generating +// the XML ourselves. This function uses QDom to do the generation, which +// ensures proper encoding and entity output. +static void createRootXmlTags(const QDomElement &root, QString *xmlHeader, QString *tagOpen, QString *tagClose) +{ + QDomElement e = root.cloneNode(false).toElement(); + + // insert a dummy element to ensure open and closing tags are generated + QDomElement dummy = e.ownerDocument().createElement("dummy"); + e.appendChild(dummy); + + // convert to xml->text + QString str; + { + QTextStream ts(&str, IO_WriteOnly); + e.save(ts, 0); + } + + // parse the tags out + int n = str.find('<'); + int n2 = str.find('>', n); + ++n2; + *tagOpen = str.mid(n, n2-n); + n2 = str.findRev('>'); + n = str.findRev('<'); + ++n2; + *tagClose = str.mid(n, n2-n); + + // generate a nice xml processing header + *xmlHeader = ""; +} + +//---------------------------------------------------------------------------- +// Protocol +//---------------------------------------------------------------------------- +XmlProtocol::TransferItem::TransferItem() +{ +} + +XmlProtocol::TransferItem::TransferItem(const QString &_str, bool sent, bool external) +{ + isString = true; + isSent = sent; + isExternal = external; + str = _str; +} + +XmlProtocol::TransferItem::TransferItem(const QDomElement &_elem, bool sent, bool external) +{ + isString = false; + isSent = sent; + isExternal = external; + elem = _elem; +} + +XmlProtocol::XmlProtocol() +{ + init(); +} + +XmlProtocol::~XmlProtocol() +{ +} + +void XmlProtocol::init() +{ + incoming = false; + peerClosed = false; + closeWritten = false; +} + +void XmlProtocol::reset() +{ + init(); + + elem = QDomElement(); + tagOpen = QString(); + tagClose = QString(); + xml.reset(); + outData.resize(0); + trackQueue.clear(); + transferItemList.clear(); +} + +void XmlProtocol::addIncomingData(const QByteArray &a) +{ + xml.appendData(a); +} + +QByteArray XmlProtocol::takeOutgoingData() +{ + QByteArray a = outData.copy(); + outData.resize(0); + return a; +} + +void XmlProtocol::outgoingDataWritten(int bytes) +{ + for(QValueList::Iterator it = trackQueue.begin(); it != trackQueue.end();) { + TrackItem &i = *it; + + // enough bytes? + if(bytes < i.size) { + i.size -= bytes; + break; + } + int type = i.type; + int id = i.id; + int size = i.size; + bytes -= i.size; + it = trackQueue.remove(it); + + if(type == TrackItem::Raw) { + // do nothing + } + else if(type == TrackItem::Close) { + closeWritten = true; + } + else if(type == TrackItem::Custom) { + itemWritten(id, size); + } + } +} + +bool XmlProtocol::processStep() +{ + Parser::Event pe; + notify = 0; + transferItemList.clear(); + + if(state != Closing && (state == RecvOpen || stepAdvancesParser())) { + // if we get here, then it's because we're in some step that advances the parser + pe = xml.readNext(); + if(!pe.isNull()) { + // note: error/close events should be handled for ALL steps, so do them here + switch(pe.type()) { + case Parser::Event::DocumentOpen: { + transferItemList += TransferItem(pe.actualString(), false); + + //stringRecv(pe.actualString()); + break; + } + case Parser::Event::DocumentClose: { + transferItemList += TransferItem(pe.actualString(), false); + + //stringRecv(pe.actualString()); + if(incoming) { + sendTagClose(); + event = ESend; + peerClosed = true; + state = Closing; + } + else { + event = EPeerClosed; + } + return true; + } + case Parser::Event::Element: { + transferItemList += TransferItem(pe.element(), false); + + //elementRecv(pe.element()); + break; + } + case Parser::Event::Error: { + if(incoming) { + // If we get a parse error during the initial element exchange, + // flip immediately into 'open' mode so that we can report an error. + if(state == RecvOpen) { + sendTagOpen(); + state = Open; + } + return handleError(); + } + else { + event = EError; + errorCode = ErrParse; + return true; + } + } + } + } + else { + if(state == RecvOpen || stepRequiresElement()) { + need = NNotify; + notify |= NRecv; + return false; + } + } + } + + return baseStep(pe); +} + +QString XmlProtocol::xmlEncoding() const +{ + return xml.encoding(); +} + +QString XmlProtocol::elementToString(const QDomElement &e, bool clip) +{ + if(elem.isNull()) + elem = elemDoc.importNode(docElement(), true).toElement(); + + // Determine the appropriate 'fakeNS' to use + QString ns; + + // first, check root namespace + QString pre = e.prefix(); + if(pre.isNull()) + pre = ""; + if(pre == elem.prefix()) { + ns = elem.namespaceURI(); + } + else { + // scan the root attributes for 'xmlns' (oh joyous hacks) + QDomNamedNodeMap al = elem.attributes(); + uint n; + for(n = 0; n < al.count(); ++n) { + QDomAttr a = al.item(n).toAttr(); + QString s = a.name(); + int x = s.find(':'); + if(x != -1) + s = s.mid(x+1); + else + s = ""; + if(pre == s) { + ns = a.value(); + break; + } + } + if(n >= al.count()) { + // if we get here, then no appropriate ns was found. use root then.. + ns = elem.namespaceURI(); + } + } + + // build qName + QString qn; + if(!elem.prefix().isEmpty()) + qn = elem.prefix() + ':'; + qn += elem.localName(); + + // make the string + return xmlToString(e, ns, qn, clip); +} + +bool XmlProtocol::stepRequiresElement() const +{ + // default returns false + return false; +} + +void XmlProtocol::itemWritten(int, int) +{ + // default does nothing +} + +void XmlProtocol::stringSend(const QString &) +{ + // default does nothing +} + +void XmlProtocol::stringRecv(const QString &) +{ + // default does nothing +} + +void XmlProtocol::elementSend(const QDomElement &) +{ + // default does nothing +} + +void XmlProtocol::elementRecv(const QDomElement &) +{ + // default does nothing +} + +void XmlProtocol::startConnect() +{ + incoming = false; + state = SendOpen; +} + +void XmlProtocol::startAccept() +{ + incoming = true; + state = RecvOpen; +} + +bool XmlProtocol::close() +{ + sendTagClose(); + event = ESend; + state = Closing; + return true; +} + +int XmlProtocol::writeString(const QString &s, int id, bool external) +{ + transferItemList += TransferItem(s, true, external); + return internalWriteString(s, TrackItem::Custom, id); +} + +int XmlProtocol::writeElement(const QDomElement &e, int id, bool external, bool clip) +{ + if(e.isNull()) + return 0; + transferItemList += TransferItem(e, true, external); + + //elementSend(e); + QString out = elementToString(e, clip); + return internalWriteString(out, TrackItem::Custom, id); +} + +QByteArray XmlProtocol::resetStream() +{ + // reset the state + if(incoming) + state = RecvOpen; + else + state = SendOpen; + + // grab unprocessed data before resetting + QByteArray spare = xml.unprocessed(); + xml.reset(); + return spare; +} + +int XmlProtocol::internalWriteData(const QByteArray &a, TrackItem::Type t, int id) +{ + TrackItem i; + i.type = t; + i.id = id; + i.size = a.size(); + trackQueue += i; + + ByteStream::appendArray(&outData, a); + return a.size(); +} + +int XmlProtocol::internalWriteString(const QString &s, TrackItem::Type t, int id) +{ + QCString cs = s.utf8(); + QByteArray a(cs.length()); + memcpy(a.data(), cs.data(), a.size()); + return internalWriteData(a, t, id); +} + +void XmlProtocol::sendTagOpen() +{ + if(elem.isNull()) + elem = elemDoc.importNode(docElement(), true).toElement(); + + QString xmlHeader; + createRootXmlTags(elem, &xmlHeader, &tagOpen, &tagClose); + + QString s; + s += xmlHeader + '\n'; + s += tagOpen + '\n'; + + transferItemList += TransferItem(xmlHeader, true); + transferItemList += TransferItem(tagOpen, true); + + //stringSend(xmlHeader); + //stringSend(tagOpen); + internalWriteString(s, TrackItem::Raw); +} + +void XmlProtocol::sendTagClose() +{ + transferItemList += TransferItem(tagClose, true); + + //stringSend(tagClose); + internalWriteString(tagClose, TrackItem::Close); +} + +bool XmlProtocol::baseStep(const Parser::Event &pe) +{ + // Basic + if(state == SendOpen) { + sendTagOpen(); + event = ESend; + if(incoming) + state = Open; + else + state = RecvOpen; + return true; + } + else if(state == RecvOpen) { + if(incoming) + state = SendOpen; + else + state = Open; + + // note: event will always be DocumentOpen here + handleDocOpen(pe); + event = ERecvOpen; + return true; + } + else if(state == Open) { + QDomElement e; + if(pe.type() == Parser::Event::Element) + e = pe.element(); + return doStep(e); + } + // Closing + else { + if(closeWritten) { + if(peerClosed) { + event = EPeerClosed; + return true; + } + else + return handleCloseFinished(); + } + + need = NNotify; + notify = NSend; + return false; + } +} + +void XmlProtocol::setIncomingAsExternal() +{ + for(QValueList::Iterator it = transferItemList.begin(); it != transferItemList.end(); ++it) { + TransferItem &i = *it; + // look for elements received + if(!i.isString && !i.isSent) + i.isExternal = true; + } +} + diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.h new file mode 100644 index 00000000..5bf2cbda --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.h @@ -0,0 +1,145 @@ +/* + * xmlprotocol.h - state machine for 'jabber-like' protocols + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef XMLPROTOCOL_H +#define XMLPROTOCOL_H + +#include +#include +#include"parser.h" + +#define NS_XML "http://www.w3.org/XML/1998/namespace" + +namespace XMPP +{ + class XmlProtocol + { + public: + enum Need { + NNotify, // need a data send and/or recv update + NCustom = 10 + }; + enum Event { + EError, // unrecoverable error, see errorCode for details + ESend, // data needs to be sent, use takeOutgoingData() + ERecvOpen, // breakpoint after root element open tag is received + EPeerClosed, // root element close tag received + EClosed, // finished closing + ECustom = 10 + }; + enum Error { + ErrParse, // there was an error parsing the xml + ErrCustom = 10 + }; + enum Notify { + NSend = 0x01, // need to know if data has been written + NRecv = 0x02 // need incoming data + }; + + XmlProtocol(); + virtual ~XmlProtocol(); + + virtual void reset(); + + // byte I/O for the stream + void addIncomingData(const QByteArray &); + QByteArray takeOutgoingData(); + void outgoingDataWritten(int); + + // advance the state machine + bool processStep(); + + // set these before returning from a step + int need, event, errorCode, notify; + + inline bool isIncoming() const { return incoming; } + QString xmlEncoding() const; + QString elementToString(const QDomElement &e, bool clip=false); + + class TransferItem + { + public: + TransferItem(); + TransferItem(const QString &str, bool sent, bool external=false); + TransferItem(const QDomElement &elem, bool sent, bool external=false); + + bool isSent; // else, received + bool isString; // else, is element + bool isExternal; // not owned by protocol + QString str; + QDomElement elem; + }; + QValueList transferItemList; + void setIncomingAsExternal(); + + protected: + virtual QDomElement docElement()=0; + virtual void handleDocOpen(const Parser::Event &pe)=0; + virtual bool handleError()=0; + virtual bool handleCloseFinished()=0; + virtual bool stepAdvancesParser() const=0; + virtual bool stepRequiresElement() const; + virtual bool doStep(const QDomElement &e)=0; + virtual void itemWritten(int id, int size); + + // 'debug' + virtual void stringSend(const QString &s); + virtual void stringRecv(const QString &s); + virtual void elementSend(const QDomElement &e); + virtual void elementRecv(const QDomElement &e); + + void startConnect(); + void startAccept(); + bool close(); + int writeString(const QString &s, int id, bool external); + int writeElement(const QDomElement &e, int id, bool external, bool clip=false); + QByteArray resetStream(); + + private: + enum { SendOpen, RecvOpen, Open, Closing }; + class TrackItem + { + public: + enum Type { Raw, Close, Custom }; + int type, id, size; + }; + + bool incoming; + QDomDocument elemDoc; + QDomElement elem; + QString tagOpen, tagClose; + int state; + bool peerClosed; + bool closeWritten; + + Parser xml; + QByteArray outData; + QValueList trackQueue; + + void init(); + int internalWriteData(const QByteArray &a, TrackItem::Type t, int id=-1); + int internalWriteString(const QString &s, TrackItem::Type t, int id=-1); + void sendTagOpen(); + void sendTagClose(); + bool baseStep(const Parser::Event &pe); + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/Makefile.am b/kopete/protocols/jabber/libiris/iris/xmpp-im/Makefile.am new file mode 100644 index 00000000..c6dff330 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/Makefile.am @@ -0,0 +1,19 @@ +METASOURCES = AUTO + +noinst_LTLIBRARIES = libiris_xmpp_im.la +INCLUDES = -I$(srcdir)/../include -I$(srcdir)/../xmpp-core -I$(srcdir)/../xmpp-im -I$(srcdir)/../jabber -I$(srcdir)/../../cutestuff/util -I$(srcdir)/../../cutestuff/network -I$(srcdir)/../../qca/src $(all_includes) + +libiris_xmpp_im_la_SOURCES = \ + client.cpp \ + types.cpp \ + xmpp_tasks.cpp \ + xmpp_vcard.cpp \ + xmpp_xmlcommon.cpp + +CLEANFILES = types.moc +types.lo: types.moc +types.moc: $(top_builddir)/kopete/protocols/jabber/libiris/iris/xmpp-im/Makefile + ${MOC} -o types.moc $(srcdir)/../xmpp-im/types.cpp + +KDE_OPTIONS = nofinal + diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/client.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-im/client.cpp new file mode 100644 index 00000000..0baeb820 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/client.cpp @@ -0,0 +1,1522 @@ +/* + * client.cpp - IM Client + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"im.h" +#include"safedelete.h" + +//! \class Client client.h +//! \brief Communicates with the Jabber network. Start here. +//! +//! Client controls an active Jabber connection. It allows you to connect, +//! authenticate, manipulate the roster, and send / receive messages and +//! presence. It is the centerpiece of this library, and all Tasks must pass +//! through it. +//! +//! For convenience, many Tasks are handled internally to Client (such as +//! JT_Auth). However, for accessing features beyond the basics provided by +//! Client, you will need to manually invoke Tasks. Fortunately, the +//! process is very simple. +//! +//! The entire Task system is heavily founded on Qt. All Tasks have a parent, +//! except for the root Task, and are considered QObjects. By using Qt's RTTI +//! facilities (QObject::sender(), QObject::isA(), etc), you can use a +//! "fire and forget" approach with Tasks. +//! +//! \code +//! #include "client.h" +//! using namespace Jabber; +//! +//! ... +//! +//! Client *client; +//! +//! Session::Session() +//! { +//! client = new Client; +//! connect(client, SIGNAL(handshaken()), SLOT(clientHandshaken())); +//! connect(client, SIGNAL(authFinished(bool, int, const QString &)), SLOT(authFinished(bool, int, const QString &))); +//! client->connectToHost("jabber.org"); +//! } +//! +//! void Session::clientHandshaken() +//! { +//! client->authDigest("jabtest", "12345", "Psi"); +//! } +//! +//! void Session::authFinished(bool success, int, const QString &err) +//! { +//! if(success) +//! printf("Login success!"); +//! else +//! printf("Login failed. Here's why: %s\n", err.latin1()); +//! } +//! \endcode + +#include +#include +#include +#include +#include +#include"xmpp_tasks.h" +#include"xmpp_xmlcommon.h" +#include"s5b.h" +#include"xmpp_ibb.h" +#include"xmpp_jidlink.h" +#include"filetransfer.h" + +/*#include +#include +#include +#include +#include +#include +#include"xmpp_stream.h" +#include"xmpp_tasks.h" +#include"xmpp_xmlcommon.h" +#include"xmpp_dtcp.h" +#include"xmpp_ibb.h" +#include"xmpp_jidlink.h" + +using namespace Jabber;*/ + +#ifdef Q_WS_WIN +#define vsnprintf _vsnprintf +#endif + +namespace XMPP +{ + +//---------------------------------------------------------------------------- +// Client +//---------------------------------------------------------------------------- +class Client::GroupChat +{ +public: + enum { Connecting, Connected, Closing }; + GroupChat() {} + + Jid j; + int status; +}; + +class Client::ClientPrivate +{ +public: + ClientPrivate() {} + + ClientStream *stream; + QDomDocument doc; + int id_seed; + Task *root; + QString host, user, pass, resource; + QString osname, tzname, clientName, clientVersion, capsNode, capsVersion, capsExt; + DiscoItem::Identity identity; + QMap extension_features; + int tzoffset; + bool active; + + LiveRoster roster; + ResourceList resourceList; + S5BManager *s5bman; + IBBManager *ibbman; + JidLinkManager *jlman; + FileTransferManager *ftman; + bool ftEnabled; + QValueList groupChatList; +}; + + +Client::Client(QObject *par) +:QObject(par) +{ + d = new ClientPrivate; + d->tzoffset = 0; + d->active = false; + d->osname = "N/A"; + d->clientName = "N/A"; + d->clientVersion = "0.0"; + d->capsNode = ""; + d->capsVersion = ""; + d->capsExt = ""; + + d->id_seed = 0xaaaa; + d->root = new Task(this, true); + + d->stream = 0; + + d->s5bman = new S5BManager(this); + connect(d->s5bman, SIGNAL(incomingReady()), SLOT(s5b_incomingReady())); + + d->ibbman = new IBBManager(this); + connect(d->ibbman, SIGNAL(incomingReady()), SLOT(ibb_incomingReady())); + + d->jlman = new JidLinkManager(this); + + d->ftman = 0; +} + +Client::~Client() +{ + close(true); + + delete d->ftman; + delete d->jlman; + delete d->ibbman; + delete d->s5bman; + delete d->root; + //delete d->stream; + delete d; +} + +void Client::connectToServer(ClientStream *s, const Jid &j, bool auth) +{ + d->stream = s; + //connect(d->stream, SIGNAL(connected()), SLOT(streamConnected())); + //connect(d->stream, SIGNAL(handshaken()), SLOT(streamHandshaken())); + connect(d->stream, SIGNAL(error(int)), SLOT(streamError(int))); + //connect(d->stream, SIGNAL(sslCertificateReady(const QSSLCert &)), SLOT(streamSSLCertificateReady(const QSSLCert &))); + connect(d->stream, SIGNAL(readyRead()), SLOT(streamReadyRead())); + //connect(d->stream, SIGNAL(closeFinished()), SLOT(streamCloseFinished())); + connect(d->stream, SIGNAL(incomingXml(const QString &)), SLOT(streamIncomingXml(const QString &))); + connect(d->stream, SIGNAL(outgoingXml(const QString &)), SLOT(streamOutgoingXml(const QString &))); + + d->stream->connectToServer(j, auth); +} + +void Client::start(const QString &host, const QString &user, const QString &pass, const QString &_resource) +{ + // TODO + d->host = host; + d->user = user; + d->pass = pass; + d->resource = _resource; + + Status stat; + stat.setIsAvailable(false); + d->resourceList += Resource(resource(), stat); + + JT_PushPresence *pp = new JT_PushPresence(rootTask()); + connect(pp, SIGNAL(subscription(const Jid &, const QString &)), SLOT(ppSubscription(const Jid &, const QString &))); + connect(pp, SIGNAL(presence(const Jid &, const Status &)), SLOT(ppPresence(const Jid &, const Status &))); + + JT_PushMessage *pm = new JT_PushMessage(rootTask()); + connect(pm, SIGNAL(message(const Message &)), SLOT(pmMessage(const Message &))); + + JT_PushRoster *pr = new JT_PushRoster(rootTask()); + connect(pr, SIGNAL(roster(const Roster &)), SLOT(prRoster(const Roster &))); + + new JT_ServInfo(rootTask()); + + d->active = true; +} + +void Client::setFileTransferEnabled(bool b) +{ + if(b) { + if(!d->ftman) + d->ftman = new FileTransferManager(this); + } + else { + if(d->ftman) { + delete d->ftman; + d->ftman = 0; + } + } +} + +FileTransferManager *Client::fileTransferManager() const +{ + return d->ftman; +} + +JidLinkManager *Client::jidLinkManager() const +{ + return d->jlman; +} + +S5BManager *Client::s5bManager() const +{ + return d->s5bman; +} + +IBBManager *Client::ibbManager() const +{ + return d->ibbman; +} + +bool Client::isActive() const +{ + return d->active; +} + +void Client::groupChatChangeNick(const QString &host, const QString &room, const QString &nick, const Status &_s) +{ + Jid jid(room + "@" + host + "/" + nick); + for(QValueList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { + GroupChat &i = *it; + if(i.j.compare(jid, false)) { + i.j = jid; + + Status s = _s; + s.setIsAvailable(true); + + JT_Presence *j = new JT_Presence(rootTask()); + j->pres(jid, s); + j->go(true); + + break; + } + } +} + +bool Client::groupChatJoin(const QString &host, const QString &room, const QString &nick) +{ + Jid jid(room + "@" + host + "/" + nick); + for(QValueList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end();) { + GroupChat &i = *it; + if(i.j.compare(jid, false)) { + // if this room is shutting down, then free it up + if(i.status == GroupChat::Closing) + it = d->groupChatList.remove(it); + else + return false; + } + else + ++it; + } + + debug(QString("Client: Joined: [%1]\n").arg(jid.full())); + GroupChat i; + i.j = jid; + i.status = GroupChat::Connecting; + d->groupChatList += i; + + JT_Presence *j = new JT_Presence(rootTask()); + j->pres(jid, Status()); + j->go(true); + + return true; +} + +bool Client::groupChatJoin(const QString &host, const QString &room, const QString &nick, const QString &password) +{ + Jid jid(room + "@" + host + "/" + nick); + for(QValueList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end();) { + GroupChat &i = *it; + if(i.j.compare(jid, false)) { + // if this room is shutting down, then free it up + if(i.status == GroupChat::Closing) + it = d->groupChatList.remove(it); + else + return false; + } + else + ++it; + } + + debug(QString("Client: Joined: [%1]\n").arg(jid.full())); + GroupChat i; + i.j = jid; + i.status = GroupChat::Connecting; + d->groupChatList += i; + + JT_MucPresence *j = new JT_MucPresence(rootTask()); + j->pres(jid, Status(), password); + j->go(true); + + return true; +} + +void Client::groupChatSetStatus(const QString &host, const QString &room, const Status &_s) +{ + Jid jid(room + "@" + host); + bool found = false; + for(QValueList::ConstIterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { + const GroupChat &i = *it; + if(i.j.compare(jid, false)) { + found = true; + jid = i.j; + break; + } + } + if(!found) + return; + + Status s = _s; + s.setIsAvailable(true); + + JT_Presence *j = new JT_Presence(rootTask()); + j->pres(jid, s); + j->go(true); +} + +void Client::groupChatLeave(const QString &host, const QString &room) +{ + Jid jid(room + "@" + host); + for(QValueList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { + GroupChat &i = *it; + + if(!i.j.compare(jid, false)) + continue; + + i.status = GroupChat::Closing; + debug(QString("Client: Leaving: [%1]\n").arg(i.j.full())); + + JT_Presence *j = new JT_Presence(rootTask()); + Status s; + s.setIsAvailable(false); + j->pres(i.j, s); + j->go(true); + } +} + +/*void Client::start() +{ + if(d->stream->old()) { + // old has no activation step + d->active = true; + activated(); + } + else { + // TODO: IM session + } +}*/ + +// TODO: fast close +void Client::close(bool) +{ + if(d->stream) { + if(d->active) { + for(QValueList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { + GroupChat &i = *it; + i.status = GroupChat::Closing; + + JT_Presence *j = new JT_Presence(rootTask()); + Status s; + s.setIsAvailable(false); + j->pres(i.j, s); + j->go(true); + } + } + + d->stream->disconnect(this); + d->stream->close(); + d->stream = 0; + } + disconnected(); + cleanup(); +} + +void Client::cleanup() +{ + d->active = false; + //d->authed = false; + d->groupChatList.clear(); +} + +/*void Client::continueAfterCert() +{ + d->stream->continueAfterCert(); +} + +void Client::streamConnected() +{ + connected(); +} + +void Client::streamHandshaken() +{ + handshaken(); +}*/ + +void Client::streamError(int) +{ + //StreamError e = err; + //error(e); + + //if(!e.isWarning()) { + disconnected(); + cleanup(); + //} +} + +/*void Client::streamSSLCertificateReady(const QSSLCert &cert) +{ + sslCertReady(cert); +} + +void Client::streamCloseFinished() +{ + closeFinished(); +}*/ + +static QDomElement oldStyleNS(const QDomElement &e) +{ + // find closest parent with a namespace + QDomNode par = e.parentNode(); + while(!par.isNull() && par.namespaceURI().isNull()) + par = par.parentNode(); + bool noShowNS = false; + if(!par.isNull() && par.namespaceURI() == e.namespaceURI()) + noShowNS = true; + + QDomElement i; + uint x; + //if(noShowNS) + i = e.ownerDocument().createElement(e.tagName()); + //else + // i = e.ownerDocument().createElementNS(e.namespaceURI(), e.tagName()); + + // copy attributes + QDomNamedNodeMap al = e.attributes(); + for(x = 0; x < al.count(); ++x) + i.setAttributeNode(al.item(x).cloneNode().toAttr()); + + if(!noShowNS) + i.setAttribute("xmlns", e.namespaceURI()); + + // copy children + QDomNodeList nl = e.childNodes(); + for(x = 0; x < nl.count(); ++x) { + QDomNode n = nl.item(x); + if(n.isElement()) + i.appendChild(oldStyleNS(n.toElement())); + else + i.appendChild(n.cloneNode()); + } + return i; +} + +void Client::streamReadyRead() +{ + // HACK HACK HACK + QGuardedPtr pstream = d->stream; + + while(pstream && d->stream->stanzaAvailable()) { + Stanza s = d->stream->read(); + + QString out = s.toString(); + debug(QString("Client: incoming: [\n%1]\n").arg(out)); + xmlIncoming(out); + + QDomElement x = oldStyleNS(s.element()); + distribute(x); + } +} + +void Client::streamIncomingXml(const QString &s) +{ + QString str = s; + if(str.at(str.length()-1) != '\n') + str += '\n'; + xmlIncoming(str); +} + +void Client::streamOutgoingXml(const QString &s) +{ + QString str = s; + if(str.at(str.length()-1) != '\n') + str += '\n'; + xmlOutgoing(str); +} + +void Client::debug(const QString &str) +{ + debugText(str); +} + +QString Client::genUniqueId() +{ + QString s; + s.sprintf("a%x", d->id_seed); + d->id_seed += 0x10; + return s; +} + +Task *Client::rootTask() +{ + return d->root; +} + +QDomDocument *Client::doc() const +{ + return &d->doc; +} + +void Client::distribute(const QDomElement &x) +{ + if(x.hasAttribute("from")) { + Jid j(x.attribute("from")); + if(!j.isValid()) { + debug("Client: bad 'from' JID\n"); + return; + } + } + + if(!rootTask()->take(x)) { + debug("Client: packet was ignored.\n"); + } +} + +static QDomElement addCorrectNS(const QDomElement &e) +{ + uint x; + + // grab child nodes + /*QDomDocumentFragment frag = e.ownerDocument().createDocumentFragment(); + QDomNodeList nl = e.childNodes(); + for(x = 0; x < nl.count(); ++x) + frag.appendChild(nl.item(x).cloneNode());*/ + + // find closest xmlns + QDomNode n = e; + while(!n.isNull() && !n.toElement().hasAttribute("xmlns")) + n = n.parentNode(); + QString ns; + if(n.isNull() || !n.toElement().hasAttribute("xmlns")) + ns = "jabber:client"; + else + ns = n.toElement().attribute("xmlns"); + + // make a new node + QDomElement i = e.ownerDocument().createElementNS(ns, e.tagName()); + + // copy attributes + QDomNamedNodeMap al = e.attributes(); + for(x = 0; x < al.count(); ++x) { + QDomAttr a = al.item(x).toAttr(); + if(a.name() != "xmlns") + i.setAttributeNodeNS(a.cloneNode().toAttr()); + } + + // copy children + QDomNodeList nl = e.childNodes(); + for(x = 0; x < nl.count(); ++x) { + QDomNode n = nl.item(x); + if(n.isElement()) + i.appendChild(addCorrectNS(n.toElement())); + else + i.appendChild(n.cloneNode()); + } + + //i.appendChild(frag); + return i; +} + +void Client::send(const QDomElement &x) +{ + if(!d->stream) + return; + + //QString out; + //QTextStream ts(&out, IO_WriteOnly); + //x.save(ts, 0); + + //QString out = Stream::xmlToString(x); + //debug(QString("Client: outgoing: [\n%1]\n").arg(out)); + //xmlOutgoing(out); + + QDomElement e = addCorrectNS(x); + Stanza s = d->stream->createStanza(e); + if(s.isNull()) { + //printf("bad stanza??\n"); + return; + } + + QString out = s.toString(); + debug(QString("Client: outgoing: [\n%1]\n").arg(out)); + xmlOutgoing(out); + + //printf("x[%s] x2[%s] s[%s]\n", Stream::xmlToString(x).latin1(), Stream::xmlToString(e).latin1(), s.toString().latin1()); + d->stream->write(s); +} + +void Client::send(const QString &str) +{ + if(!d->stream) + return; + + debug(QString("Client: outgoing: [\n%1]\n").arg(str)); + xmlOutgoing(str); + static_cast(d->stream)->writeDirect(str); +} + +Stream & Client::stream() +{ + return *d->stream; +} + +const LiveRoster & Client::roster() const +{ + return d->roster; +} + +const ResourceList & Client::resourceList() const +{ + return d->resourceList; +} + +QString Client::host() const +{ + return d->host; +} + +QString Client::user() const +{ + return d->user; +} + +QString Client::pass() const +{ + return d->pass; +} + +QString Client::resource() const +{ + return d->resource; +} + +Jid Client::jid() const +{ + QString s; + if(!d->user.isEmpty()) + s += d->user + '@'; + s += d->host; + if(!d->resource.isEmpty()) { + s += '/'; + s += d->resource; + } + + return Jid(s); +} + +void Client::ppSubscription(const Jid &j, const QString &s) +{ + subscription(j, s); +} + +void Client::ppPresence(const Jid &j, const Status &s) +{ + if(s.isAvailable()) + debug(QString("Client: %1 is available.\n").arg(j.full())); + else + debug(QString("Client: %1 is unavailable.\n").arg(j.full())); + + for(QValueList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { + GroupChat &i = *it; + + if(i.j.compare(j, false)) { + bool us = (i.j.resource() == j.resource() || j.resource().isEmpty()) ? true: false; + + debug(QString("for groupchat i=[%1] pres=[%2], [us=%3].\n").arg(i.j.full()).arg(j.full()).arg(us)); + switch(i.status) { + case GroupChat::Connecting: + if(us && s.hasError()) { + Jid j = i.j; + d->groupChatList.remove(it); + groupChatError(j, s.errorCode(), s.errorString()); + } + else { + // don't signal success unless it is a non-error presence + if(!s.hasError()) { + i.status = GroupChat::Connected; + groupChatJoined(i.j); + } + groupChatPresence(j, s); + } + break; + case GroupChat::Connected: + groupChatPresence(j, s); + break; + case GroupChat::Closing: + if(us && !s.isAvailable()) { + Jid j = i.j; + d->groupChatList.remove(it); + groupChatLeft(j); + } + break; + default: + break; + } + + return; + } + } + + if(s.hasError()) { + presenceError(j, s.errorCode(), s.errorString()); + return; + } + + // is it me? + if(j.compare(jid(), false)) { + updateSelfPresence(j, s); + } + else { + // update all relavent roster entries + for(LiveRoster::Iterator it = d->roster.begin(); it != d->roster.end(); ++it) { + LiveRosterItem &i = *it; + + if(!i.jid().compare(j, false)) + continue; + + // roster item has its own resource? + if(!i.jid().resource().isEmpty()) { + if(i.jid().resource() != j.resource()) + continue; + } + + updatePresence(&i, j, s); + } + } +} + +void Client::updateSelfPresence(const Jid &j, const Status &s) +{ + ResourceList::Iterator rit = d->resourceList.find(j.resource()); + bool found = (rit == d->resourceList.end()) ? false: true; + + // unavailable? remove the resource + if(!s.isAvailable()) { + if(found) { + debug(QString("Client: Removing self resource: name=[%1]\n").arg(j.resource())); + (*rit).setStatus(s); + resourceUnavailable(j, *rit); + d->resourceList.remove(rit); + } + } + // available? add/update the resource + else { + Resource r; + if(!found) { + r = Resource(j.resource(), s); + d->resourceList += r; + debug(QString("Client: Adding self resource: name=[%1]\n").arg(j.resource())); + } + else { + (*rit).setStatus(s); + r = *rit; + debug(QString("Client: Updating self resource: name=[%1]\n").arg(j.resource())); + } + + resourceAvailable(j, r); + } +} + +void Client::updatePresence(LiveRosterItem *i, const Jid &j, const Status &s) +{ + ResourceList::Iterator rit = i->resourceList().find(j.resource()); + bool found = (rit == i->resourceList().end()) ? false: true; + + // unavailable? remove the resource + if(!s.isAvailable()) { + if(found) { + (*rit).setStatus(s); + debug(QString("Client: Removing resource from [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource())); + resourceUnavailable(j, *rit); + i->resourceList().remove(rit); + i->setLastUnavailableStatus(s); + } + } + // available? add/update the resource + else { + Resource r; + if(!found) { + r = Resource(j.resource(), s); + i->resourceList() += r; + debug(QString("Client: Adding resource to [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource())); + } + else { + (*rit).setStatus(s); + r = *rit; + debug(QString("Client: Updating resource to [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource())); + } + + resourceAvailable(j, r); + } +} + +void Client::pmMessage(const Message &m) +{ + debug(QString("Client: Message from %1\n").arg(m.from().full())); + + if(m.type() == "groupchat") { + for(QValueList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { + const GroupChat &i = *it; + + if(!i.j.compare(m.from(), false)) + continue; + + if(i.status == GroupChat::Connected) + messageReceived(m); + } + } + else + messageReceived(m); +} + +void Client::prRoster(const Roster &r) +{ + importRoster(r); +} + +void Client::rosterRequest() +{ + if(!d->active) + return; + + JT_Roster *r = new JT_Roster(rootTask()); + connect(r, SIGNAL(finished()), SLOT(slotRosterRequestFinished())); + r->get(); + d->roster.flagAllForDelete(); // mod_groups patch + r->go(true); +} + +void Client::slotRosterRequestFinished() +{ + JT_Roster *r = (JT_Roster *)sender(); + // on success, let's take it + if(r->success()) { + //d->roster.flagAllForDelete(); // mod_groups patch + + importRoster(r->roster()); + + for(LiveRoster::Iterator it = d->roster.begin(); it != d->roster.end();) { + LiveRosterItem &i = *it; + if(i.flagForDelete()) { + rosterItemRemoved(i); + it = d->roster.remove(it); + } + else + ++it; + } + } + else { + // don't report a disconnect. Client::error() will do that. + if(r->statusCode() == Task::ErrDisc) + return; + } + + // report success / fail + rosterRequestFinished(r->success(), r->statusCode(), r->statusString()); +} + +void Client::importRoster(const Roster &r) +{ + for(Roster::ConstIterator it = r.begin(); it != r.end(); ++it) { + importRosterItem(*it); + } +} + +void Client::importRosterItem(const RosterItem &item) +{ + QString substr; + switch(item.subscription().type()) { + case Subscription::Both: + substr = "<-->"; break; + case Subscription::From: + substr = " ->"; break; + case Subscription::To: + substr = "<- "; break; + case Subscription::Remove: + substr = "xxxx"; break; + case Subscription::None: + default: + substr = "----"; break; + } + + QString dstr, str; + str.sprintf(" %s %-32s", substr.latin1(), item.jid().full().latin1()); + if(!item.name().isEmpty()) + str += QString(" [") + item.name() + "]"; + str += '\n'; + + // Remove + if(item.subscription().type() == Subscription::Remove) { + LiveRoster::Iterator it = d->roster.find(item.jid()); + if(it != d->roster.end()) { + rosterItemRemoved(*it); + d->roster.remove(it); + } + dstr = "Client: (Removed) "; + } + // Add/Update + else { + LiveRoster::Iterator it = d->roster.find(item.jid()); + if(it != d->roster.end()) { + LiveRosterItem &i = *it; + i.setFlagForDelete(false); + i.setRosterItem(item); + rosterItemUpdated(i); + dstr = "Client: (Updated) "; + } + else { + LiveRosterItem i(item); + d->roster += i; + + // signal it + rosterItemAdded(i); + dstr = "Client: (Added) "; + } + } + + debug(dstr + str); +} + +void Client::sendMessage(const Message &m) +{ + JT_Message *j = new JT_Message(rootTask(), m); + j->go(true); +} + +void Client::sendSubscription(const Jid &jid, const QString &type) +{ + JT_Presence *j = new JT_Presence(rootTask()); + j->sub(jid, type); + j->go(true); +} + +void Client::setPresence(const Status &s) +{ + JT_Presence *j = new JT_Presence(rootTask()); + j->pres(s); + j->go(true); + + // update our resourceList + ppPresence(jid(), s); + //ResourceList::Iterator rit = d->resourceList.find(resource()); + //Resource &r = *rit; + //r.setStatus(s); +} + +QString Client::OSName() const +{ + return d->osname; +} + +QString Client::timeZone() const +{ + return d->tzname; +} + +int Client::timeZoneOffset() const +{ + return d->tzoffset; +} + +QString Client::clientName() const +{ + return d->clientName; +} + +QString Client::clientVersion() const +{ + return d->clientVersion; +} + +QString Client::capsNode() const +{ + return d->capsNode; +} + +QString Client::capsVersion() const +{ + return d->capsVersion; +} + +QString Client::capsExt() const +{ + return d->capsExt; +} + +void Client::setOSName(const QString &name) +{ + d->osname = name; +} + +void Client::setTimeZone(const QString &name, int offset) +{ + d->tzname = name; + d->tzoffset = offset; +} + +void Client::setClientName(const QString &s) +{ + d->clientName = s; +} + +void Client::setClientVersion(const QString &s) +{ + d->clientVersion = s; +} + +void Client::setCapsNode(const QString &s) +{ + d->capsNode = s; +} + +void Client::setCapsVersion(const QString &s) +{ + d->capsVersion = s; +} + +DiscoItem::Identity Client::identity() +{ + return d->identity; +} + +void Client::setIdentity(DiscoItem::Identity identity) +{ + d->identity = identity; +} + +void Client::addExtension(const QString& ext, const Features& features) +{ + if (!ext.isEmpty()) { + d->extension_features[ext] = features; + d->capsExt = extensions().join(" "); + } +} + +void Client::removeExtension(const QString& ext) +{ + if (d->extension_features.contains(ext)) { + d->extension_features.remove(ext); + d->capsExt = extensions().join(" "); + } +} + +QStringList Client::extensions() const +{ + return d->extension_features.keys(); +} + +const Features& Client::extension(const QString& ext) const +{ + return d->extension_features[ext]; +} + +void Client::s5b_incomingReady() +{ + S5BConnection *c = d->s5bman->takeIncoming(); + if(!c) + return; + if(!d->ftman) { + c->close(); + c->deleteLater(); + return; + } + d->ftman->s5b_incomingReady(c); + //d->jlman->insertStream(c); + //incomingJidLink(); +} + +void Client::ibb_incomingReady() +{ + IBBConnection *c = d->ibbman->takeIncoming(); + if(!c) + return; + c->deleteLater(); + //d->jlman->insertStream(c); + //incomingJidLink(); +} + +//---------------------------------------------------------------------------- +// Task +//---------------------------------------------------------------------------- +class Task::TaskPrivate +{ +public: + TaskPrivate() {} + + QString id; + bool success; + int statusCode; + QString statusString; + Client *client; + bool insig, deleteme, autoDelete; + bool done; +}; + +Task::Task(Task *parent) +:QObject(parent) +{ + init(); + + d->client = parent->client(); + d->id = client()->genUniqueId(); + connect(d->client, SIGNAL(disconnected()), SLOT(clientDisconnected())); +} + +Task::Task(Client *parent, bool) +:QObject(0) +{ + init(); + + d->client = parent; + connect(d->client, SIGNAL(disconnected()), SLOT(clientDisconnected())); +} + +Task::~Task() +{ + delete d; +} + +void Task::init() +{ + d = new TaskPrivate; + d->success = false; + d->insig = false; + d->deleteme = false; + d->autoDelete = false; + d->done = false; +} + +Task *Task::parent() const +{ + return (Task *)QObject::parent(); +} + +Client *Task::client() const +{ + return d->client; +} + +QDomDocument *Task::doc() const +{ + return client()->doc(); +} + +QString Task::id() const +{ + return d->id; +} + +bool Task::success() const +{ + return d->success; +} + +int Task::statusCode() const +{ + return d->statusCode; +} + +const QString & Task::statusString() const +{ + return d->statusString; +} + +void Task::go(bool autoDelete) +{ + d->autoDelete = autoDelete; + + onGo(); +} + +bool Task::take(const QDomElement &x) +{ + const QObjectList *p = children(); + if(!p) + return false; + + // pass along the xml + QObjectListIt it(*p); + Task *t; + for(; it.current(); ++it) { + QObject *obj = it.current(); + if(!obj->inherits("XMPP::Task")) + continue; + + t = static_cast(obj); + if(t->take(x)) + return true; + } + + return false; +} + +void Task::safeDelete() +{ + if(d->deleteme) + return; + + d->deleteme = true; + if(!d->insig) + SafeDelete::deleteSingle(this); +} + +void Task::onGo() +{ +} + +void Task::onDisconnect() +{ + if(!d->done) { + d->success = false; + d->statusCode = ErrDisc; + d->statusString = tr("Disconnected"); + + // delay this so that tasks that react don't block the shutdown + QTimer::singleShot(0, this, SLOT(done())); + } +} + +void Task::send(const QDomElement &x) +{ + client()->send(x); +} + +void Task::setSuccess(int code, const QString &str) +{ + if(!d->done) { + d->success = true; + d->statusCode = code; + d->statusString = str; + done(); + } +} + +void Task::setError(const QDomElement &e) +{ + if(!d->done) { + d->success = false; + getErrorFromElement(e, &d->statusCode, &d->statusString); + done(); + } +} + +void Task::setError(int code, const QString &str) +{ + if(!d->done) { + d->success = false; + d->statusCode = code; + d->statusString = str; + done(); + } +} + +void Task::done() +{ + if(d->done || d->insig) + return; + d->done = true; + + if(d->deleteme || d->autoDelete) + d->deleteme = true; + + d->insig = true; + finished(); + d->insig = false; + + if(d->deleteme) + SafeDelete::deleteSingle(this); +} + +void Task::clientDisconnected() +{ + onDisconnect(); +} + +void Task::debug(const char *fmt, ...) +{ + char *buf; + QString str; + int size = 1024; + int r; + + do { + buf = new char[size]; + va_list ap; + va_start(ap, fmt); + r = vsnprintf(buf, size, fmt, ap); + va_end(ap); + + if(r != -1) + str = QString(buf); + + delete [] buf; + + size *= 2; + } while(r == -1); + + debug(str); +} + +void Task::debug(const QString &str) +{ + client()->debug(QString("%1: ").arg(className()) + str); +} + +bool Task::iqVerify(const QDomElement &x, const Jid &to, const QString &id, const QString &xmlns) +{ + if(x.tagName() != "iq") + return false; + + Jid from(x.attribute("from")); + Jid local = client()->jid(); + Jid server = client()->host(); + + // empty 'from' ? + if(from.isEmpty()) { + // allowed if we are querying the server + if(!to.isEmpty() && !to.compare(server)) + return false; + } + // from ourself? + else if(from.compare(local, false)) { + // allowed if we are querying ourself or the server + if(!to.isEmpty() && !to.compare(local, false) && !to.compare(server)) + return false; + } + // from anywhere else? + else { + if(!from.compare(to)) + return false; + } + + if(!id.isEmpty()) { + if(x.attribute("id") != id) + return false; + } + + if(!xmlns.isEmpty()) { + if(queryNS(x) != xmlns) + return false; + } + + return true; +} + +//--------------------------------------------------------------------------- +// LiveRosterItem +//--------------------------------------------------------------------------- +LiveRosterItem::LiveRosterItem(const Jid &jid) +:RosterItem(jid) +{ + setFlagForDelete(false); +} + +LiveRosterItem::LiveRosterItem(const RosterItem &i) +{ + setRosterItem(i); + setFlagForDelete(false); +} + +LiveRosterItem::~LiveRosterItem() +{ +} + +void LiveRosterItem::setRosterItem(const RosterItem &i) +{ + setJid(i.jid()); + setName(i.name()); + setGroups(i.groups()); + setSubscription(i.subscription()); + setAsk(i.ask()); + setIsPush(i.isPush()); +} + +ResourceList & LiveRosterItem::resourceList() +{ + return v_resourceList; +} + +ResourceList::Iterator LiveRosterItem::priority() +{ + return v_resourceList.priority(); +} + +const ResourceList & LiveRosterItem::resourceList() const +{ + return v_resourceList; +} + +ResourceList::ConstIterator LiveRosterItem::priority() const +{ + return v_resourceList.priority(); +} + +bool LiveRosterItem::isAvailable() const +{ + if(v_resourceList.count() > 0) + return true; + return false; +} + +const Status & LiveRosterItem::lastUnavailableStatus() const +{ + return v_lastUnavailableStatus; +} + +bool LiveRosterItem::flagForDelete() const +{ + return v_flagForDelete; +} + +void LiveRosterItem::setLastUnavailableStatus(const Status &s) +{ + v_lastUnavailableStatus = s; +} + +void LiveRosterItem::setFlagForDelete(bool b) +{ + v_flagForDelete = b; +} + +//--------------------------------------------------------------------------- +// LiveRoster +//--------------------------------------------------------------------------- +LiveRoster::LiveRoster() +:QValueList() +{ +} + +LiveRoster::~LiveRoster() +{ +} + +void LiveRoster::flagAllForDelete() +{ + for(Iterator it = begin(); it != end(); ++it) + (*it).setFlagForDelete(true); +} + +LiveRoster::Iterator LiveRoster::find(const Jid &j, bool compareRes) +{ + Iterator it; + for(it = begin(); it != end(); ++it) { + if((*it).jid().compare(j, compareRes)) + break; + } + return it; +} + +LiveRoster::ConstIterator LiveRoster::find(const Jid &j, bool compareRes) const +{ + ConstIterator it; + for(it = begin(); it != end(); ++it) { + if((*it).jid().compare(j, compareRes)) + break; + } + return it; +} + +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/types.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-im/types.cpp new file mode 100644 index 00000000..1e457584 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/types.cpp @@ -0,0 +1,1876 @@ +/* + * types.cpp - IM data types + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"im.h" +#include "protocol.h" +#include +#include + +#define NS_XML "http://www.w3.org/XML/1998/namespace" + +static QDomElement textTag(QDomDocument *doc, const QString &name, const QString &content) +{ + QDomElement tag = doc->createElement(name); + QDomText text = doc->createTextNode(content); + tag.appendChild(text); + + return tag; +} + +static QString tagContent(const QDomElement &e) +{ + // look for some tag content + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomText i = n.toText(); + if(i.isNull()) + continue; + return i.data(); + } + + return ""; +} + +static QDateTime stamp2TS(const QString &ts) +{ + if(ts.length() != 17) + return QDateTime(); + + int year = ts.mid(0,4).toInt(); + int month = ts.mid(4,2).toInt(); + int day = ts.mid(6,2).toInt(); + + int hour = ts.mid(9,2).toInt(); + int min = ts.mid(12,2).toInt(); + int sec = ts.mid(15,2).toInt(); + + QDate xd; + xd.setYMD(year, month, day); + if(!xd.isValid()) + return QDateTime(); + + QTime xt; + xt.setHMS(hour, min, sec); + if(!xt.isValid()) + return QDateTime(); + + return QDateTime(xd, xt); +} + +/*static QString TS2stamp(const QDateTime &d) +{ + QString str; + + str.sprintf("%04d%02d%02dT%02d:%02d:%02d", + d.date().year(), + d.date().month(), + d.date().day(), + d.time().hour(), + d.time().minute(), + d.time().second()); + + return str; +}*/ + +namespace XMPP +{ + +//---------------------------------------------------------------------------- +// Url +//---------------------------------------------------------------------------- +class Url::Private +{ +public: + QString url; + QString desc; +}; + +//! \brief Construct Url object with a given URL and Description. +//! +//! This function will construct a Url object. +//! \param QString - url (default: empty string) +//! \param QString - description of url (default: empty string) +//! \sa setUrl() setDesc() +Url::Url(const QString &url, const QString &desc) +{ + d = new Private; + d->url = url; + d->desc = desc; +} + +//! \brief Construct Url object. +//! +//! Overloaded constructor which will constructs a exact copy of the Url object that was passed to the constructor. +//! \param Url - Url Object +Url::Url(const Url &from) +{ + d = new Private; + *this = from; +} + +//! \brief operator overloader needed for d pointer (Internel). +Url & Url::operator=(const Url &from) +{ + *d = *from.d; + return *this; +} + +//! \brief destroy Url object. +Url::~Url() +{ + delete d; +} + +//! \brief Get url information. +//! +//! Returns url information. +QString Url::url() const +{ + return d->url; +} + +//! \brief Get Description information. +//! +//! Returns desction of the URL. +QString Url::desc() const +{ + return d->desc; +} + +//! \brief Set Url information. +//! +//! Set url information. +//! \param url - url string (eg: http://psi.affinix.com/) +void Url::setUrl(const QString &url) +{ + d->url = url; +} + +//! \brief Set Description information. +//! +//! Set description of the url. +//! \param desc - description of url +void Url::setDesc(const QString &desc) +{ + d->desc = desc; +} + +//---------------------------------------------------------------------------- +// Message +//---------------------------------------------------------------------------- +class Message::Private +{ +public: + Jid to, from; + QString id, type, lang; + + StringMap subject, body, xHTMLBody; + + QString thread; + Stanza::Error error; + + // extensions + QDateTime timeStamp; + UrlList urlList; + QValueList eventList; + QString eventId; + QString xencrypted, invite; + + bool spooled, wasEncrypted; +}; + +//! \brief Constructs Message with given Jid information. +//! +//! This function will construct a Message container. +//! \param to - specify reciver (default: empty string) +Message::Message(const Jid &to) +{ + d = new Private; + d->to = to; + d->spooled = false; + d->wasEncrypted = false; + /*d->flag = false; + d->spooled = false; + d->wasEncrypted = false; + d->errorCode = -1;*/ +} + +//! \brief Constructs a copy of Message object +//! +//! Overloaded constructor which will constructs a exact copy of the Message +//! object that was passed to the constructor. +//! \param from - Message object you want to copy +Message::Message(const Message &from) +{ + d = new Private; + *this = from; +} + +//! \brief Required for internel use. +Message & Message::operator=(const Message &from) +{ + *d = *from.d; + return *this; +} + +//! \brief Destroy Message object. +Message::~Message() +{ + delete d; +} + +//! \brief Return receiver's Jid information. +Jid Message::to() const +{ + return d->to; +} + +//! \brief Return sender's Jid information. +Jid Message::from() const +{ + return d->from; +} + +QString Message::id() const +{ + return d->id; +} + +//! \brief Return type information +QString Message::type() const +{ + return d->type; +} + +QString Message::lang() const +{ + return d->lang; +} + +//! \brief Return subject information. +QString Message::subject(const QString &lang) const +{ + return d->subject[lang]; +} + +//! \brief Return body information. +//! +//! This function will return a plain text or the Richtext version if it +//! it exists. +//! \param rich - Returns richtext if true and plain text if false. (default: false) +//! \note Richtext is in Qt's richtext format and not in xhtml. +QString Message::body(const QString &lang) const +{ + return d->body[lang]; +} + +QString Message::xHTMLBody(const QString &lang) const +{ + return d->xHTMLBody[lang]; +} + +QString Message::thread() const +{ + return d->thread; +} + +Stanza::Error Message::error() const +{ + return d->error; +} + +//! \brief Set receivers information +//! +//! \param to - Receivers Jabber id +void Message::setTo(const Jid &j) +{ + d->to = j; + //d->flag = false; +} + +void Message::setFrom(const Jid &j) +{ + d->from = j; + //d->flag = false; +} + +void Message::setId(const QString &s) +{ + d->id = s; +} + +//! \brief Set Type of message +//! +//! \param type - type of message your going to send +void Message::setType(const QString &s) +{ + d->type = s; + //d->flag = false; +} + +void Message::setLang(const QString &s) +{ + d->lang = s; +} + +//! \brief Set subject +//! +//! \param subject - Subject information +void Message::setSubject(const QString &s, const QString &lang) +{ + d->subject[lang] = s; + //d->flag = false; +} + +//! \brief Set body +//! +//! \param body - body information +//! \param rich - set richtext if true and set plaintext if false. +//! \note Richtext support will be implemented in the future... Sorry. +void Message::setBody(const QString &s, const QString &lang) +{ + d->body[lang] = s; +} + +void Message::setXHTMLBody(const QString &s, const QString &lang, const QString &attr) +{ + //ugly but needed if s is not a node but a list of leaf + + QString content = "\n" + s +"\n"; + d->xHTMLBody[lang] = content; +} + +void Message::setThread(const QString &s) +{ + d->thread = s; +} + +void Message::setError(const Stanza::Error &err) +{ + d->error = err; +} + +QDateTime Message::timeStamp() const +{ + return d->timeStamp; +} + +void Message::setTimeStamp(const QDateTime &ts) +{ + d->timeStamp = ts; +} + +//! \brief Return list of urls attached to message. +UrlList Message::urlList() const +{ + return d->urlList; +} + +//! \brief Add Url to the url list. +//! +//! \param url - url to append +void Message::urlAdd(const Url &u) +{ + d->urlList += u; +} + +//! \brief clear out the url list. +void Message::urlsClear() +{ + d->urlList.clear(); +} + +//! \brief Set urls to send +//! +//! \param urlList - list of urls to send +void Message::setUrlList(const UrlList &list) +{ + d->urlList = list; +} + +QString Message::eventId() const +{ + return d->eventId; +} + +void Message::setEventId(const QString& id) +{ + d->eventId = id; +} + +bool Message::containsEvents() const +{ + return !d->eventList.isEmpty(); +} + +bool Message::containsEvent(MsgEvent e) const +{ + return d->eventList.contains(e); +} + +void Message::addEvent(MsgEvent e) +{ + if (!d->eventList.contains(e)) { + if (e == CancelEvent || containsEvent(CancelEvent)) + d->eventList.clear(); // Reset list + d->eventList += e; + } +} + +QString Message::xencrypted() const +{ + return d->xencrypted; +} + +void Message::setXEncrypted(const QString &s) +{ + d->xencrypted = s; +} + +QString Message::invite() const +{ + return d->invite; +} + +void Message::setInvite(const QString &s) +{ + d->invite = s; +} + +bool Message::spooled() const +{ + return d->spooled; +} + +void Message::setSpooled(bool b) +{ + d->spooled = b; +} + +bool Message::wasEncrypted() const +{ + return d->wasEncrypted; +} + +void Message::setWasEncrypted(bool b) +{ + d->wasEncrypted = b; +} + +Stanza Message::toStanza(Stream *stream) const +{ + Stanza s = stream->createStanza(Stanza::Message, d->to, d->type); + if(!d->from.isEmpty()) + s.setFrom(d->from); + if(!d->id.isEmpty()) + s.setId(d->id); + if(!d->lang.isEmpty()) + s.setLang(d->lang); + + StringMap::ConstIterator it; + for(it = d->subject.begin(); it != d->subject.end(); ++it) { + const QString &str = it.data(); + if(!str.isEmpty()) { + QDomElement e = s.createTextElement(s.baseNS(), "subject", str); + if(!it.key().isEmpty()) + e.setAttributeNS(NS_XML, "xml:lang", it.key()); + s.appendChild(e); + } + } + for(it = d->body.begin(); it != d->body.end(); ++it) { + const QString &str = it.data(); + if(!str.isEmpty()) { + QDomElement e = s.createTextElement(s.baseNS(), "body", str); + if(!it.key().isEmpty()) + e.setAttributeNS(NS_XML, "xml:lang", it.key()); + s.appendChild(e); + } + } + if ( !d->xHTMLBody.isEmpty()) { + QDomElement parent = s.createElement(s.xhtmlImNS(), "html"); + for(it = d->xHTMLBody.begin(); it != d->xHTMLBody.end(); ++it) { + const QString &str = it.data(); + if(!str.isEmpty()) { + QDomElement child = s.createXHTMLElement(str); + if(!it.key().isEmpty()) + child.setAttributeNS(NS_XML, "xml:lang", it.key()); + parent.appendChild(child); + } + } + s.appendChild(parent); + } + if(d->type == "error") + s.setError(d->error); + + // timestamp + /*if(!d->timeStamp.isNull()) { + QDomElement e = s.createElement("jabber:x:delay", "x"); + e.setAttribute("stamp", TS2stamp(d->timeStamp)); + s.appendChild(e); + }*/ + + // urls + for(QValueList::ConstIterator uit = d->urlList.begin(); uit != d->urlList.end(); ++uit) { + QDomElement x = s.createElement("jabber:x:oob", "x"); + x.appendChild(s.createTextElement("jabber:x:oob", "url", (*uit).url())); + if(!(*uit).desc().isEmpty()) + x.appendChild(s.createTextElement("jabber:x:oob", "desc", (*uit).desc())); + s.appendChild(x); + } + + // events + if (!d->eventList.isEmpty()) { + QDomElement x = s.createElement("jabber:x:event", "x"); + + if (d->body.isEmpty()) { + if (d->eventId.isEmpty()) + x.appendChild(s.createElement("jabber:x:event","id")); + else + x.appendChild(s.createTextElement("jabber:x:event","id",d->eventId)); + } + else if (d->type=="chat" || d->type=="groupchat") + s.appendChild( s.createElement(NS_CHATSTATES , "active" ) ); + + bool need_x_event=false; + for(QValueList::ConstIterator ev = d->eventList.begin(); ev != d->eventList.end(); ++ev) { + switch (*ev) { + case OfflineEvent: + x.appendChild(s.createElement("jabber:x:event", "offline")); + need_x_event=true; + break; + case DeliveredEvent: + x.appendChild(s.createElement("jabber:x:event", "delivered")); + need_x_event=true; + break; + case DisplayedEvent: + x.appendChild(s.createElement("jabber:x:event", "displayed")); + need_x_event=true; + break; + case ComposingEvent: + x.appendChild(s.createElement("jabber:x:event", "composing")); + need_x_event=true; + if (d->body.isEmpty() && (d->type=="chat" || d->type=="groupchat") ) + s.appendChild( s.createElement(NS_CHATSTATES , "composing" ) ); + break; + case CancelEvent: + need_x_event=true; + if (d->body.isEmpty() && (d->type=="chat" || d->type=="groupchat") ) + s.appendChild( s.createElement(NS_CHATSTATES , "paused" ) ); + break; + case InactiveEvent: + if (d->body.isEmpty() && (d->type=="chat" || d->type=="groupchat") ) + s.appendChild( s.createElement(NS_CHATSTATES , "inactive" ) ); + break; + case GoneEvent: + if (d->body.isEmpty() && (d->type=="chat" || d->type=="groupchat") ) + s.appendChild( s.createElement(NS_CHATSTATES , "gone" ) ); + break; + } + } + if(need_x_event) //we don't need to have the (empty) x:event element if this is only or + s.appendChild(x); + } + + + // xencrypted + if(!d->xencrypted.isEmpty()) + s.appendChild(s.createTextElement("jabber:x:encrypted", "x", d->xencrypted)); + + // invite + if(!d->invite.isEmpty()) { + QDomElement e = s.createElement("jabber:x:conference", "x"); + e.setAttribute("jid", d->invite); + s.appendChild(e); + } + + return s; +} + +bool Message::fromStanza(const Stanza &s, int timeZoneOffset) +{ + if(s.kind() != Stanza::Message) + return false; + + setTo(s.to()); + setFrom(s.from()); + setId(s.id()); + setType(s.type()); + setLang(s.lang()); + + d->subject.clear(); + d->body.clear(); + d->thread = QString(); + d->eventList.clear(); + + QDomElement root = s.element(); + + QDomNodeList nl = root.childNodes(); + uint n; + for(n = 0; n < nl.count(); ++n) { + QDomNode i = nl.item(n); + if(i.isElement()) { + QDomElement e = i.toElement(); + if(e.namespaceURI() == s.baseNS()) { + if(e.tagName() == "subject") { + QString lang = e.attributeNS(NS_XML, "lang", ""); + d->subject[lang] = e.text(); + } + else if(e.tagName() == "body") { + QString lang = e.attributeNS(NS_XML, "lang", ""); + d->body[lang] = e.text(); + } + else if(e.tagName() == "thread") + d->thread = e.text(); + } + else if (e.namespaceURI() == s.xhtmlImNS()) { + if (e.tagName() == "html") { + QDomNodeList htmlNL= e.childNodes(); + for (unsigned int x = 0; x < htmlNL.count(); x++) { + QDomElement i = htmlNL.item(x).toElement(); + + if (i.tagName() == "body") { + QDomDocument RichText; + QString lang = i.attributeNS(NS_XML, "lang", ""); + RichText.appendChild(i); + d-> xHTMLBody[lang] = RichText.toString(); + } + } + } + } + else if (e.namespaceURI() == NS_CHATSTATES) + { + if(e.tagName() == "active") + { + //like in JEP-0022 we let the client know that we can receive ComposingEvent + // (we can do that according to §4.6 of the JEP-0085) + d->eventList += ComposingEvent; + d->eventList += InactiveEvent; + d->eventList += GoneEvent; + } + else if (e.tagName() == "composing") + { + d->eventList += ComposingEvent; + } + else if (e.tagName() == "paused") + { + d->eventList += CancelEvent; + } + else if (e.tagName() == "inactive") + { + d->eventList += InactiveEvent; + } + else if (e.tagName() == "gone") + { + d->eventList += GoneEvent; + } + } + else { + //printf("extension element: [%s]\n", e.tagName().latin1()); + } + } + } + + if(s.type() == "error") + d->error = s.error(); + + // timestamp + QDomElement t = root.elementsByTagNameNS("jabber:x:delay", "x").item(0).toElement(); + if(!t.isNull()) { + d->timeStamp = stamp2TS(t.attribute("stamp")); + d->timeStamp = d->timeStamp.addSecs(timeZoneOffset * 3600); + d->spooled = true; + } + else { + d->timeStamp = QDateTime::currentDateTime(); + d->spooled = false; + } + + // urls + d->urlList.clear(); + nl = root.elementsByTagNameNS("jabber:x:oob", "x"); + for(n = 0; n < nl.count(); ++n) { + QDomElement t = nl.item(n).toElement(); + Url u; + u.setUrl(t.elementsByTagName("url").item(0).toElement().text()); + u.setDesc(t.elementsByTagName("desc").item(0).toElement().text()); + d->urlList += u; + } + + // events + nl = root.elementsByTagNameNS("jabber:x:event", "x"); + if (nl.count()) { + nl = nl.item(0).childNodes(); + for(n = 0; n < nl.count(); ++n) { + QString evtag = nl.item(n).toElement().tagName(); + if (evtag == "id") { + d->eventId = nl.item(n).toElement().text(); + } + else if (evtag == "displayed") + d->eventList += DisplayedEvent; + else if (evtag == "composing") + d->eventList += ComposingEvent; + else if (evtag == "delivered") + d->eventList += DeliveredEvent; + else if (evtag == "offline") + d->eventList += OfflineEvent; + } + if (d->eventList.isEmpty()) + d->eventList += CancelEvent; + } + + // xencrypted + t = root.elementsByTagNameNS("jabber:x:encrypted", "x").item(0).toElement(); + if(!t.isNull()) + d->xencrypted = t.text(); + else + d->xencrypted = QString(); + + // invite + t = root.elementsByTagNameNS("jabber:x:conference", "x").item(0).toElement(); + if(!t.isNull()) + d->invite = t.attribute("jid"); + else + d->invite = QString(); + + return true; +} + +//--------------------------------------------------------------------------- +// Subscription +//--------------------------------------------------------------------------- +Subscription::Subscription(SubType type) +{ + value = type; +} + +int Subscription::type() const +{ + return value; +} + +QString Subscription::toString() const +{ + switch(value) { + case Remove: + return "remove"; + case Both: + return "both"; + case From: + return "from"; + case To: + return "to"; + case None: + default: + return "none"; + } +} + +bool Subscription::fromString(const QString &s) +{ + if(s == "remove") + value = Remove; + else if(s == "both") + value = Both; + else if(s == "from") + value = From; + else if(s == "to") + value = To; + else if(s == "none") + value = None; + else + return false; + + return true; +} + + +//--------------------------------------------------------------------------- +// Status +//--------------------------------------------------------------------------- +Status::Status(const QString &show, const QString &status, int priority, bool available) +{ + v_isAvailable = available; + v_show = show; + v_status = status; + v_priority = priority; + v_timeStamp = QDateTime::currentDateTime(); + v_isInvisible = false; + ecode = -1; +} + +Status::~Status() +{ +} + +bool Status::hasError() const +{ + return (ecode != -1); +} + +void Status::setError(int code, const QString &str) +{ + ecode = code; + estr = str; +} + +void Status::setIsAvailable(bool available) +{ + v_isAvailable = available; +} + +void Status::setIsInvisible(bool invisible) +{ + v_isInvisible = invisible; +} + +void Status::setPriority(int x) +{ + v_priority = x; +} + +void Status::setShow(const QString & _show) +{ + v_show = _show; +} + +void Status::setStatus(const QString & _status) +{ + v_status = _status; +} + +void Status::setTimeStamp(const QDateTime & _timestamp) +{ + v_timeStamp = _timestamp; +} + +void Status::setKeyID(const QString &key) +{ + v_key = key; +} + +void Status::setXSigned(const QString &s) +{ + v_xsigned = s; +} + +void Status::setSongTitle(const QString & _songtitle) +{ + v_songTitle = _songtitle; +} + +void Status::setCapsNode(const QString & _capsNode) +{ + v_capsNode = _capsNode; +} + +void Status::setCapsVersion(const QString & _capsVersion) +{ + v_capsVersion = _capsVersion; +} + +void Status::setCapsExt(const QString & _capsExt) +{ + v_capsExt = _capsExt; +} + +bool Status::isAvailable() const +{ + return v_isAvailable; +} + +bool Status::isAway() const +{ + if(v_show == "away" || v_show == "xa" || v_show == "dnd") + return true; + + return false; +} + +bool Status::isInvisible() const +{ + return v_isInvisible; +} + +int Status::priority() const +{ + return v_priority; +} + +const QString & Status::show() const +{ + return v_show; +} +const QString & Status::status() const +{ + return v_status; +} + +QDateTime Status::timeStamp() const +{ + return v_timeStamp; +} + +const QString & Status::keyID() const +{ + return v_key; +} + +const QString & Status::xsigned() const +{ + return v_xsigned; +} + +const QString & Status::songTitle() const +{ + return v_songTitle; +} + +const QString & Status::capsNode() const +{ + return v_capsNode; +} + +const QString & Status::capsVersion() const +{ + return v_capsVersion; +} + +const QString & Status::capsExt() const +{ + return v_capsExt; +} + +int Status::errorCode() const +{ + return ecode; +} + +const QString & Status::errorString() const +{ + return estr; +} + + +//--------------------------------------------------------------------------- +// Resource +//--------------------------------------------------------------------------- +Resource::Resource(const QString &name, const Status &stat) +{ + v_name = name; + v_status = stat; +} + +Resource::~Resource() +{ +} + +const QString & Resource::name() const +{ + return v_name; +} + +int Resource::priority() const +{ + return v_status.priority(); +} + +const Status & Resource::status() const +{ + return v_status; +} + +void Resource::setName(const QString & _name) +{ + v_name = _name; +} + +void Resource::setStatus(const Status & _status) +{ + v_status = _status; +} + + +//--------------------------------------------------------------------------- +// ResourceList +//--------------------------------------------------------------------------- +ResourceList::ResourceList() +:QValueList() +{ +} + +ResourceList::~ResourceList() +{ +} + +ResourceList::Iterator ResourceList::find(const QString & _find) +{ + for(ResourceList::Iterator it = begin(); it != end(); ++it) { + if((*it).name() == _find) + return it; + } + + return end(); +} + +ResourceList::Iterator ResourceList::priority() +{ + ResourceList::Iterator highest = end(); + + for(ResourceList::Iterator it = begin(); it != end(); ++it) { + if(highest == end() || (*it).priority() > (*highest).priority()) + highest = it; + } + + return highest; +} + +ResourceList::ConstIterator ResourceList::find(const QString & _find) const +{ + for(ResourceList::ConstIterator it = begin(); it != end(); ++it) { + if((*it).name() == _find) + return it; + } + + return end(); +} + +ResourceList::ConstIterator ResourceList::priority() const +{ + ResourceList::ConstIterator highest = end(); + + for(ResourceList::ConstIterator it = begin(); it != end(); ++it) { + if(highest == end() || (*it).priority() > (*highest).priority()) + highest = it; + } + + return highest; +} + + +//--------------------------------------------------------------------------- +// RosterItem +//--------------------------------------------------------------------------- +RosterItem::RosterItem(const Jid &_jid) +{ + v_jid = _jid; +} + +RosterItem::~RosterItem() +{ +} + +const Jid & RosterItem::jid() const +{ + return v_jid; +} + +const QString & RosterItem::name() const +{ + return v_name; +} + +const QStringList & RosterItem::groups() const +{ + return v_groups; +} + +const Subscription & RosterItem::subscription() const +{ + return v_subscription; +} + +const QString & RosterItem::ask() const +{ + return v_ask; +} + +bool RosterItem::isPush() const +{ + return v_push; +} + +bool RosterItem::inGroup(const QString &g) const +{ + for(QStringList::ConstIterator it = v_groups.begin(); it != v_groups.end(); ++it) { + if(*it == g) + return true; + } + return false; +} + +void RosterItem::setJid(const Jid &_jid) +{ + v_jid = _jid; +} + +void RosterItem::setName(const QString &_name) +{ + v_name = _name; +} + +void RosterItem::setGroups(const QStringList &_groups) +{ + v_groups = _groups; +} + +void RosterItem::setSubscription(const Subscription &type) +{ + v_subscription = type; +} + +void RosterItem::setAsk(const QString &_ask) +{ + v_ask = _ask; +} + +void RosterItem::setIsPush(bool b) +{ + v_push = b; +} + +bool RosterItem::addGroup(const QString &g) +{ + if(inGroup(g)) + return false; + + v_groups += g; + return true; +} + +bool RosterItem::removeGroup(const QString &g) +{ + for(QStringList::Iterator it = v_groups.begin(); it != v_groups.end(); ++it) { + if(*it == g) { + v_groups.remove(it); + return true; + } + } + + return false; +} + +QDomElement RosterItem::toXml(QDomDocument *doc) const +{ + QDomElement item = doc->createElement("item"); + item.setAttribute("jid", v_jid.full()); + item.setAttribute("name", v_name); + item.setAttribute("subscription", v_subscription.toString()); + if(!v_ask.isEmpty()) + item.setAttribute("ask", v_ask); + for(QStringList::ConstIterator it = v_groups.begin(); it != v_groups.end(); ++it) + item.appendChild(textTag(doc, "group", *it)); + + return item; +} + +bool RosterItem::fromXml(const QDomElement &item) +{ + if(item.tagName() != "item") + return false; + Jid j(item.attribute("jid")); + if(!j.isValid()) + return false; + QString na = item.attribute("name"); + Subscription s; + if(!s.fromString(item.attribute("subscription")) ) + return false; + QStringList g; + for(QDomNode n = item.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + if(i.tagName() == "group") + g += tagContent(i); + } + QString a = item.attribute("ask"); + + v_jid = j; + v_name = na; + v_subscription = s; + v_groups = g; + v_ask = a; + + return true; +} + + +//--------------------------------------------------------------------------- +// Roster +//--------------------------------------------------------------------------- +Roster::Roster() +:QValueList() +{ +} + +Roster::~Roster() +{ +} + +Roster::Iterator Roster::find(const Jid &j) +{ + for(Roster::Iterator it = begin(); it != end(); ++it) { + if((*it).jid().compare(j)) + return it; + } + + return end(); +} + +Roster::ConstIterator Roster::find(const Jid &j) const +{ + for(Roster::ConstIterator it = begin(); it != end(); ++it) { + if((*it).jid().compare(j)) + return it; + } + + return end(); +} + + +//--------------------------------------------------------------------------- +// FormField +//--------------------------------------------------------------------------- +FormField::FormField(const QString &type, const QString &value) +{ + v_type = misc; + if(!type.isEmpty()) { + int x = tagNameToType(type); + if(x != -1) + v_type = x; + } + v_value = value; +} + +FormField::~FormField() +{ +} + +int FormField::type() const +{ + return v_type; +} + +QString FormField::realName() const +{ + return typeToTagName(v_type); +} + +QString FormField::fieldName() const +{ + switch(v_type) { + case username: return QObject::tr("Username"); + case nick: return QObject::tr("Nickname"); + case password: return QObject::tr("Password"); + case name: return QObject::tr("Name"); + case first: return QObject::tr("First Name"); + case last: return QObject::tr("Last Name"); + case email: return QObject::tr("E-mail"); + case address: return QObject::tr("Address"); + case city: return QObject::tr("City"); + case state: return QObject::tr("State"); + case zip: return QObject::tr("Zipcode"); + case phone: return QObject::tr("Phone"); + case url: return QObject::tr("URL"); + case date: return QObject::tr("Date"); + case misc: return QObject::tr("Misc"); + default: return ""; + }; +} + +bool FormField::isSecret() const +{ + return (type() == password); +} + +const QString & FormField::value() const +{ + return v_value; +} + +void FormField::setType(int x) +{ + v_type = x; +} + +bool FormField::setType(const QString &in) +{ + int x = tagNameToType(in); + if(x == -1) + return false; + + v_type = x; + return true; +} + +void FormField::setValue(const QString &in) +{ + v_value = in; +} + +int FormField::tagNameToType(const QString &in) const +{ + if(!in.compare("username")) return username; + if(!in.compare("nick")) return nick; + if(!in.compare("password")) return password; + if(!in.compare("name")) return name; + if(!in.compare("first")) return first; + if(!in.compare("last")) return last; + if(!in.compare("email")) return email; + if(!in.compare("address")) return address; + if(!in.compare("city")) return city; + if(!in.compare("state")) return state; + if(!in.compare("zip")) return zip; + if(!in.compare("phone")) return phone; + if(!in.compare("url")) return url; + if(!in.compare("date")) return date; + if(!in.compare("misc")) return misc; + + return -1; +} + +QString FormField::typeToTagName(int type) const +{ + switch(type) { + case username: return "username"; + case nick: return "nick"; + case password: return "password"; + case name: return "name"; + case first: return "first"; + case last: return "last"; + case email: return "email"; + case address: return "address"; + case city: return "city"; + case state: return "state"; + case zip: return "zipcode"; + case phone: return "phone"; + case url: return "url"; + case date: return "date"; + case misc: return "misc"; + default: return ""; + }; +} + + +//--------------------------------------------------------------------------- +// Form +//--------------------------------------------------------------------------- +Form::Form(const Jid &j) +:QValueList() +{ + setJid(j); +} + +Form::~Form() +{ +} + +Jid Form::jid() const +{ + return v_jid; +} + +QString Form::instructions() const +{ + return v_instructions; +} + +QString Form::key() const +{ + return v_key; +} + +void Form::setJid(const Jid &j) +{ + v_jid = j; +} + +void Form::setInstructions(const QString &s) +{ + v_instructions = s; +} + +void Form::setKey(const QString &s) +{ + v_key = s; +} + + +//--------------------------------------------------------------------------- +// SearchResult +//--------------------------------------------------------------------------- +SearchResult::SearchResult(const Jid &jid) +{ + setJid(jid); +} + +SearchResult::~SearchResult() +{ +} + +const Jid & SearchResult::jid() const +{ + return v_jid; +} + +const QString & SearchResult::nick() const +{ + return v_nick; +} + +const QString & SearchResult::first() const +{ + return v_first; +} + +const QString & SearchResult::last() const +{ + return v_last; +} + +const QString & SearchResult::email() const +{ + return v_email; +} + +void SearchResult::setJid(const Jid &jid) +{ + v_jid = jid; +} + +void SearchResult::setNick(const QString &nick) +{ + v_nick = nick; +} + +void SearchResult::setFirst(const QString &first) +{ + v_first = first; +} + +void SearchResult::setLast(const QString &last) +{ + v_last = last; +} + +void SearchResult::setEmail(const QString &email) +{ + v_email = email; +} + +//--------------------------------------------------------------------------- +// Features +//--------------------------------------------------------------------------- + +Features::Features() +{ +} + +Features::Features(const QStringList &l) +{ + setList(l); +} + +Features::Features(const QString &str) +{ + QStringList l; + l << str; + + setList(l); +} + +Features::~Features() +{ +} + +QStringList Features::list() const +{ + return _list; +} + +void Features::setList(const QStringList &l) +{ + _list = l; +} + +bool Features::test(const QStringList &ns) const +{ + QStringList::ConstIterator it = ns.begin(); + for ( ; it != ns.end(); ++it) + if ( _list.find( *it ) != _list.end() ) + return true; + + return false; +} + +#define FID_REGISTER "jabber:iq:register" +bool Features::canRegister() const +{ + QStringList ns; + ns << FID_REGISTER; + + return test(ns); +} + +#define FID_SEARCH "jabber:iq:search" +bool Features::canSearch() const +{ + QStringList ns; + ns << FID_SEARCH; + + return test(ns); +} + +#define FID_XHTML "http://jabber.org/protocol/xhtml-im" +bool Features::canXHTML() const +{ + QStringList ns; + + ns << FID_XHTML; + + return test(ns); +} + +#define FID_GROUPCHAT "jabber:iq:conference" +bool Features::canGroupchat() const +{ + QStringList ns; + ns << "http://jabber.org/protocol/muc"; + ns << FID_GROUPCHAT; + + return test(ns); +} + +#define FID_VOICE "http://www.google.com/xmpp/protocol/voice/v1" +bool Features::canVoice() const +{ + QStringList ns; + ns << FID_VOICE; + + return test(ns); +} + +#define FID_GATEWAY "jabber:iq:gateway" +bool Features::isGateway() const +{ + QStringList ns; + ns << FID_GATEWAY; + + return test(ns); +} + +#define FID_DISCO "http://jabber.org/protocol/disco" +bool Features::canDisco() const +{ + QStringList ns; + ns << FID_DISCO; + ns << "http://jabber.org/protocol/disco#info"; + ns << "http://jabber.org/protocol/disco#items"; + + return test(ns); +} + +#define FID_VCARD "vcard-temp" +bool Features::haveVCard() const +{ + QStringList ns; + ns << FID_VCARD; + + return test(ns); +} + +// custom Psi acitons +#define FID_ADD "psi:add" + +class Features::FeatureName : public QObject +{ + Q_OBJECT +public: + FeatureName() + : QObject(qApp) + { + id2s[FID_Invalid] = tr("ERROR: Incorrect usage of Features class"); + id2s[FID_None] = tr("None"); + id2s[FID_Register] = tr("Register"); + id2s[FID_Search] = tr("Search"); + id2s[FID_Groupchat] = tr("Groupchat"); + id2s[FID_Gateway] = tr("Gateway"); + id2s[FID_Disco] = tr("Service Discovery"); + id2s[FID_VCard] = tr("VCard"); + + // custom Psi actions + id2s[FID_Add] = tr("Add to roster"); + + // compute reverse map + //QMap::Iterator it = id2s.begin(); + //for ( ; it != id2s.end(); ++it) + // s2id[it.data()] = it.key(); + + id2f[FID_Register] = FID_REGISTER; + id2f[FID_Search] = FID_SEARCH; + id2f[FID_Groupchat] = FID_GROUPCHAT; + id2f[FID_Gateway] = FID_GATEWAY; + id2f[FID_Disco] = FID_DISCO; + id2f[FID_VCard] = FID_VCARD; + + // custom Psi actions + id2f[FID_Add] = FID_ADD; + } + + //QMap s2id; + QMap id2s; + QMap id2f; +}; + +static Features::FeatureName *featureName = 0; + +long Features::id() const +{ + if ( _list.count() > 1 ) + return FID_Invalid; + else if ( canRegister() ) + return FID_Register; + else if ( canSearch() ) + return FID_Search; + else if ( canGroupchat() ) + return FID_Groupchat; + else if ( isGateway() ) + return FID_Gateway; + else if ( canDisco() ) + return FID_Disco; + else if ( haveVCard() ) + return FID_VCard; + else if ( test(FID_ADD) ) + return FID_Add; + + return FID_None; +} + +long Features::id(const QString &feature) +{ + Features f(feature); + return f.id(); +} + +QString Features::feature(long id) +{ + if ( !featureName ) + featureName = new FeatureName(); + + return featureName->id2f[id]; +} + +QString Features::name(long id) +{ + if ( !featureName ) + featureName = new FeatureName(); + + return featureName->id2s[id]; +} + +QString Features::name() const +{ + return name(id()); +} + +QString Features::name(const QString &feature) +{ + Features f(feature); + return f.name(f.id()); +} + +//--------------------------------------------------------------------------- +// DiscoItem +//--------------------------------------------------------------------------- +class DiscoItem::Private +{ +public: + Private() + { + action = None; + } + + Jid jid; + QString name; + QString node; + Action action; + + Features features; + Identities identities; +}; + +DiscoItem::DiscoItem() +{ + d = new Private; +} + +DiscoItem::DiscoItem(const DiscoItem &from) +{ + d = new Private; + *this = from; +} + +DiscoItem & DiscoItem::operator= (const DiscoItem &from) +{ + d->jid = from.d->jid; + d->name = from.d->name; + d->node = from.d->node; + d->action = from.d->action; + d->features = from.d->features; + d->identities = from.d->identities; + + return *this; +} + +DiscoItem::~DiscoItem() +{ + delete d; +} + +AgentItem DiscoItem::toAgentItem() const +{ + AgentItem ai; + + ai.setJid( jid() ); + ai.setName( name() ); + + Identity id; + if ( !identities().isEmpty() ) + id = identities().first(); + + ai.setCategory( id.category ); + ai.setType( id.type ); + + ai.setFeatures( d->features ); + + return ai; +} + +void DiscoItem::fromAgentItem(const AgentItem &ai) +{ + setJid( ai.jid() ); + setName( ai.name() ); + + Identity id; + id.category = ai.category(); + id.type = ai.type(); + id.name = ai.name(); + + Identities idList; + idList << id; + + setIdentities( idList ); + + setFeatures( ai.features() ); +} + +const Jid &DiscoItem::jid() const +{ + return d->jid; +} + +void DiscoItem::setJid(const Jid &j) +{ + d->jid = j; +} + +const QString &DiscoItem::name() const +{ + return d->name; +} + +void DiscoItem::setName(const QString &n) +{ + d->name = n; +} + +const QString &DiscoItem::node() const +{ + return d->node; +} + +void DiscoItem::setNode(const QString &n) +{ + d->node = n; +} + +DiscoItem::Action DiscoItem::action() const +{ + return d->action; +} + +void DiscoItem::setAction(Action a) +{ + d->action = a; +} + +const Features &DiscoItem::features() const +{ + return d->features; +} + +void DiscoItem::setFeatures(const Features &f) +{ + d->features = f; +} + +const DiscoItem::Identities &DiscoItem::identities() const +{ + return d->identities; +} + +void DiscoItem::setIdentities(const Identities &i) +{ + d->identities = i; + + if ( name().isEmpty() && i.count() ) + setName( i.first().name ); +} + + +DiscoItem::Action DiscoItem::string2action(QString s) +{ + Action a; + + if ( s == "update" ) + a = Update; + else if ( s == "remove" ) + a = Remove; + else + a = None; + + return a; +} + +QString DiscoItem::action2string(Action a) +{ + QString s; + + if ( a == Update ) + s = "update"; + else if ( a == Remove ) + s = "remove"; + else + s = QString::null; + + return s; +} + +} + +#include"types.moc" diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_tasks.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_tasks.cpp new file mode 100644 index 00000000..ffd7e6ae --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_tasks.cpp @@ -0,0 +1,2120 @@ +/* + * tasks.cpp - basic tasks + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"xmpp_tasks.h" + +#include"base64.h" +//#include"sha1.h" +#include"xmpp_xmlcommon.h" +//#include"xmpp_stream.h" +//#include"xmpp_types.h" +#include"xmpp_vcard.h" + +#include +#include + +using namespace XMPP; + + +static QString lineEncode(QString str) +{ + str.replace(QRegExp("\\\\"), "\\\\"); // backslash to double-backslash + str.replace(QRegExp("\\|"), "\\p"); // pipe to \p + str.replace(QRegExp("\n"), "\\n"); // newline to \n + return str; +} + +static QString lineDecode(const QString &str) +{ + QString ret; + + for(unsigned int n = 0; n < str.length(); ++n) { + if(str.at(n) == '\\') { + ++n; + if(n >= str.length()) + break; + + if(str.at(n) == 'n') + ret.append('\n'); + if(str.at(n) == 'p') + ret.append('|'); + if(str.at(n) == '\\') + ret.append('\\'); + } + else { + ret.append(str.at(n)); + } + } + + return ret; +} + +static Roster xmlReadRoster(const QDomElement &q, bool push) +{ + Roster r; + + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + if(i.tagName() == "item") { + RosterItem item; + item.fromXml(i); + + if(push) + item.setIsPush(true); + + r += item; + } + } + + return r; +} + + +//---------------------------------------------------------------------------- +// JT_Register +//---------------------------------------------------------------------------- +class JT_Register::Private +{ +public: + Private() {} + + Form form; + Jid jid; + int type; +}; + +JT_Register::JT_Register(Task *parent) +:Task(parent) +{ + d = new Private; + d->type = -1; +} + +JT_Register::~JT_Register() +{ + delete d; +} + +void JT_Register::reg(const QString &user, const QString &pass) +{ + d->type = 0; + to = client()->host(); + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:register"); + iq.appendChild(query); + query.appendChild(textTag(doc(), "username", user)); + query.appendChild(textTag(doc(), "password", pass)); +} + +void JT_Register::changepw(const QString &pass) +{ + d->type = 1; + to = client()->host(); + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:register"); + iq.appendChild(query); + query.appendChild(textTag(doc(), "username", client()->user())); + query.appendChild(textTag(doc(), "password", pass)); +} + +void JT_Register::unreg(const Jid &j) +{ + d->type = 2; + to = j.isEmpty() ? client()->host() : j.full(); + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:register"); + iq.appendChild(query); + + // this may be useful + if(!d->form.key().isEmpty()) + query.appendChild(textTag(doc(), "key", d->form.key())); + + query.appendChild(doc()->createElement("remove")); +} + +void JT_Register::getForm(const Jid &j) +{ + d->type = 3; + to = j; + iq = createIQ(doc(), "get", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:register"); + iq.appendChild(query); +} + +void JT_Register::setForm(const Form &form) +{ + d->type = 4; + to = form.jid(); + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:register"); + iq.appendChild(query); + + // key? + if(!form.key().isEmpty()) + query.appendChild(textTag(doc(), "key", form.key())); + + // fields + for(Form::ConstIterator it = form.begin(); it != form.end(); ++it) { + const FormField &f = *it; + query.appendChild(textTag(doc(), f.realName(), f.value())); + } +} + +const Form & JT_Register::form() const +{ + return d->form; +} + +void JT_Register::onGo() +{ + send(iq); +} + +bool JT_Register::take(const QDomElement &x) +{ + if(!iqVerify(x, to, id())) + return false; + + Jid from(x.attribute("from")); + if(x.attribute("type") == "result") { + if(d->type == 3) { + d->form.clear(); + d->form.setJid(from); + + QDomElement q = queryTag(x); + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + if(i.tagName() == "instructions") + d->form.setInstructions(tagContent(i)); + else if(i.tagName() == "key") + d->form.setKey(tagContent(i)); + else { + FormField f; + if(f.setType(i.tagName())) { + f.setValue(tagContent(i)); + d->form += f; + } + } + } + } + + setSuccess(); + } + else + setError(x); + + return true; +} + +//---------------------------------------------------------------------------- +// JT_UnRegister +//---------------------------------------------------------------------------- +class JT_UnRegister::Private +{ +public: + Private() { } + + Jid j; + JT_Register *jt_reg; +}; + +JT_UnRegister::JT_UnRegister(Task *parent) +: Task(parent) +{ + d = new Private; + d->jt_reg = 0; +} + +JT_UnRegister::~JT_UnRegister() +{ + delete d->jt_reg; + delete d; +} + +void JT_UnRegister::unreg(const Jid &j) +{ + d->j = j; +} + +void JT_UnRegister::onGo() +{ + delete d->jt_reg; + + d->jt_reg = new JT_Register(this); + d->jt_reg->getForm(d->j); + connect(d->jt_reg, SIGNAL(finished()), SLOT(getFormFinished())); + d->jt_reg->go(false); +} + +void JT_UnRegister::getFormFinished() +{ + disconnect(d->jt_reg, 0, this, 0); + + d->jt_reg->unreg(d->j); + connect(d->jt_reg, SIGNAL(finished()), SLOT(unregFinished())); + d->jt_reg->go(false); +} + +void JT_UnRegister::unregFinished() +{ + if ( d->jt_reg->success() ) + setSuccess(); + else + setError(d->jt_reg->statusCode(), d->jt_reg->statusString()); + + delete d->jt_reg; + d->jt_reg = 0; +} + +//---------------------------------------------------------------------------- +// JT_Roster +//---------------------------------------------------------------------------- +class JT_Roster::Private +{ +public: + Private() {} + + Roster roster; + QValueList itemList; +}; + +JT_Roster::JT_Roster(Task *parent) +:Task(parent) +{ + type = -1; + d = new Private; +} + +JT_Roster::~JT_Roster() +{ + delete d; +} + +void JT_Roster::get() +{ + type = 0; + //to = client()->host(); + iq = createIQ(doc(), "get", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:roster"); + iq.appendChild(query); +} + +void JT_Roster::set(const Jid &jid, const QString &name, const QStringList &groups) +{ + type = 1; + //to = client()->host(); + QDomElement item = doc()->createElement("item"); + item.setAttribute("jid", jid.full()); + if(!name.isEmpty()) + item.setAttribute("name", name); + for(QStringList::ConstIterator it = groups.begin(); it != groups.end(); ++it) + item.appendChild(textTag(doc(), "group", *it)); + d->itemList += item; +} + +void JT_Roster::remove(const Jid &jid) +{ + type = 1; + //to = client()->host(); + QDomElement item = doc()->createElement("item"); + item.setAttribute("jid", jid.full()); + item.setAttribute("subscription", "remove"); + d->itemList += item; +} + +void JT_Roster::onGo() +{ + if(type == 0) + send(iq); + else if(type == 1) { + //to = client()->host(); + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:roster"); + iq.appendChild(query); + for(QValueList::ConstIterator it = d->itemList.begin(); it != d->itemList.end(); ++it) + query.appendChild(*it); + send(iq); + } +} + +const Roster & JT_Roster::roster() const +{ + return d->roster; +} + +QString JT_Roster::toString() const +{ + if(type != 1) + return ""; + + QDomElement i = doc()->createElement("request"); + i.setAttribute("type", "JT_Roster"); + for(QValueList::ConstIterator it = d->itemList.begin(); it != d->itemList.end(); ++it) + i.appendChild(*it); + return lineEncode(Stream::xmlToString(i)); + return ""; +} + +bool JT_Roster::fromString(const QString &str) +{ + QDomDocument *dd = new QDomDocument; + if(!dd->setContent(lineDecode(str).utf8())) + return false; + QDomElement e = doc()->importNode(dd->documentElement(), true).toElement(); + delete dd; + + if(e.tagName() != "request" || e.attribute("type") != "JT_Roster") + return false; + + type = 1; + d->itemList.clear(); + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + d->itemList += i; + } + + return true; +} + +bool JT_Roster::take(const QDomElement &x) +{ + if(!iqVerify(x, client()->host(), id())) + return false; + + // get + if(type == 0) { + if(x.attribute("type") == "result") { + QDomElement q = queryTag(x); + d->roster = xmlReadRoster(q, false); + setSuccess(); + } + else { + setError(x); + } + + return true; + } + // set + else if(type == 1) { + if(x.attribute("type") == "result") + setSuccess(); + else + setError(x); + + return true; + } + // remove + else if(type == 2) { + setSuccess(); + return true; + } + + return false; +} + + +//---------------------------------------------------------------------------- +// JT_PushRoster +//---------------------------------------------------------------------------- +JT_PushRoster::JT_PushRoster(Task *parent) +:Task(parent) +{ +} + +JT_PushRoster::~JT_PushRoster() +{ +} + +bool JT_PushRoster::take(const QDomElement &e) +{ + // must be an iq-set tag + if(e.tagName() != "iq" || e.attribute("type") != "set") + return false; + + if(!iqVerify(e, client()->host(), "", "jabber:iq:roster")) + return false; + + roster(xmlReadRoster(queryTag(e), true)); + + return true; +} + + +//---------------------------------------------------------------------------- +// JT_Presence +//---------------------------------------------------------------------------- +JT_Presence::JT_Presence(Task *parent) +:Task(parent) +{ + type = -1; +} + +JT_Presence::~JT_Presence() +{ +} + +void JT_Presence::pres(const Status &s) +{ + type = 0; + + tag = doc()->createElement("presence"); + if(!s.isAvailable()) { + tag.setAttribute("type", "unavailable"); + if(!s.status().isEmpty()) + tag.appendChild(textTag(doc(), "status", s.status())); + } + else { + if(s.isInvisible()) + tag.setAttribute("type", "invisible"); + + if(!s.show().isEmpty()) + tag.appendChild(textTag(doc(), "show", s.show())); + if(!s.status().isEmpty()) + tag.appendChild(textTag(doc(), "status", s.status())); + + tag.appendChild( textTag(doc(), "priority", QString("%1").arg(s.priority()) ) ); + + if(!s.keyID().isEmpty()) { + QDomElement x = textTag(doc(), "x", s.keyID()); + x.setAttribute("xmlns", "http://jabber.org/protocol/e2e"); + tag.appendChild(x); + } + if(!s.xsigned().isEmpty()) { + QDomElement x = textTag(doc(), "x", s.xsigned()); + x.setAttribute("xmlns", "jabber:x:signed"); + tag.appendChild(x); + } + + if(!s.capsNode().isEmpty() && !s.capsVersion().isEmpty()) { + QDomElement c = doc()->createElement("c"); + c.setAttribute("xmlns","http://jabber.org/protocol/caps"); + c.setAttribute("node",s.capsNode()); + c.setAttribute("ver",s.capsVersion()); + if (!s.capsExt().isEmpty()) + c.setAttribute("ext",s.capsExt()); + tag.appendChild(c); + } + } +} + +void JT_Presence::pres(const Jid &to, const Status &s) +{ + pres(s); + tag.setAttribute("to", to.full()); +} + +void JT_Presence::sub(const Jid &to, const QString &subType) +{ + type = 1; + + tag = doc()->createElement("presence"); + tag.setAttribute("to", to.full()); + tag.setAttribute("type", subType); +} + +void JT_Presence::onGo() +{ + send(tag); + setSuccess(); +} + + +//---------------------------------------------------------------------------- +// JT_PushPresence +//---------------------------------------------------------------------------- +JT_PushPresence::JT_PushPresence(Task *parent) +:Task(parent) +{ +} + +JT_PushPresence::~JT_PushPresence() +{ +} + +bool JT_PushPresence::take(const QDomElement &e) +{ + if(e.tagName() != "presence") + return false; + + Jid j(e.attribute("from")); + Status p; + + if(e.hasAttribute("type")) { + QString type = e.attribute("type"); + if(type == "unavailable") { + p.setIsAvailable(false); + } + else if(type == "error") { + QString str = ""; + int code = 0; + getErrorFromElement(e, &code, &str); + p.setError(code, str); + } + else { + subscription(j, type); + return true; + } + } + + QDomElement tag; + bool found; + + tag = findSubTag(e, "status", &found); + if(found) + p.setStatus(tagContent(tag)); + tag = findSubTag(e, "show", &found); + if(found) + p.setShow(tagContent(tag)); + tag = findSubTag(e, "priority", &found); + if(found) + p.setPriority(tagContent(tag).toInt()); + + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + if(i.tagName() == "x" && i.attribute("xmlns") == "jabber:x:delay") { + if(i.hasAttribute("stamp")) { + QDateTime dt; + if(stamp2TS(i.attribute("stamp"), &dt)) + dt = dt.addSecs(client()->timeZoneOffset() * 3600); + p.setTimeStamp(dt); + } + } + else if(i.tagName() == "x" && i.attribute("xmlns") == "gabber:x:music:info") { + QDomElement t; + bool found; + QString title, state; + + t = findSubTag(i, "title", &found); + if(found) + title = tagContent(t); + t = findSubTag(i, "state", &found); + if(found) + state = tagContent(t); + + if(!title.isEmpty() && state == "playing") + p.setSongTitle(title); + } + else if(i.tagName() == "x" && i.attribute("xmlns") == "jabber:x:signed") { + p.setXSigned(tagContent(i)); + } + else if(i.tagName() == "x" && i.attribute("xmlns") == "http://jabber.org/protocol/e2e") { + p.setKeyID(tagContent(i)); + } + else if(i.tagName() == "c" && i.attribute("xmlns") == "http://jabber.org/protocol/caps") { + p.setCapsNode(i.attribute("node")); + p.setCapsVersion(i.attribute("ver")); + p.setCapsExt(i.attribute("ext")); + } + } + + presence(j, p); + + return true; +} + + +//---------------------------------------------------------------------------- +// JT_Message +//---------------------------------------------------------------------------- +static QDomElement oldStyleNS(const QDomElement &e) +{ + // find closest parent with a namespace + QDomNode par = e.parentNode(); + while(!par.isNull() && par.namespaceURI().isNull()) + par = par.parentNode(); + bool noShowNS = false; + if(!par.isNull() && par.namespaceURI() == e.namespaceURI()) + noShowNS = true; + + QDomElement i; + uint x; + //if(noShowNS) + i = e.ownerDocument().createElement(e.tagName()); + //else + // i = e.ownerDocument().createElementNS(e.namespaceURI(), e.tagName()); + + // copy attributes + QDomNamedNodeMap al = e.attributes(); + for(x = 0; x < al.count(); ++x) + i.setAttributeNode(al.item(x).cloneNode().toAttr()); + + if(!noShowNS) + i.setAttribute("xmlns", e.namespaceURI()); + + // copy children + QDomNodeList nl = e.childNodes(); + for(x = 0; x < nl.count(); ++x) { + QDomNode n = nl.item(x); + if(n.isElement()) + i.appendChild(oldStyleNS(n.toElement())); + else + i.appendChild(n.cloneNode()); + } + return i; +} + +JT_Message::JT_Message(Task *parent, const Message &msg) +:Task(parent) +{ + m = msg; + m.setId(id()); +} + +JT_Message::~JT_Message() +{ +} + +void JT_Message::onGo() +{ + Stanza s = m.toStanza(&(client()->stream())); + QDomElement e = oldStyleNS(s.element()); + send(e); + setSuccess(); +} + + +//---------------------------------------------------------------------------- +// JT_PushMessage +//---------------------------------------------------------------------------- +static QDomElement addCorrectNS(const QDomElement &e) +{ + uint x; + + // grab child nodes + /*QDomDocumentFragment frag = e.ownerDocument().createDocumentFragment(); + QDomNodeList nl = e.childNodes(); + for(x = 0; x < nl.count(); ++x) + frag.appendChild(nl.item(x).cloneNode());*/ + + // find closest xmlns + QDomNode n = e; + while(!n.isNull() && !n.toElement().hasAttribute("xmlns")) + n = n.parentNode(); + QString ns; + if(n.isNull() || !n.toElement().hasAttribute("xmlns")) + ns = "jabber:client"; + else + ns = n.toElement().attribute("xmlns"); + + // make a new node + QDomElement i = e.ownerDocument().createElementNS(ns, e.tagName()); + + // copy attributes + QDomNamedNodeMap al = e.attributes(); + for(x = 0; x < al.count(); ++x) { + QDomAttr a = al.item(x).toAttr(); + if(a.name() != "xmlns") + i.setAttributeNodeNS(al.item(x).cloneNode().toAttr()); + } + + // copy children + QDomNodeList nl = e.childNodes(); + for(x = 0; x < nl.count(); ++x) { + QDomNode n = nl.item(x); + if(n.isElement()) + i.appendChild(addCorrectNS(n.toElement())); + else + i.appendChild(n.cloneNode()); + } + + //i.appendChild(frag); + return i; +} + +JT_PushMessage::JT_PushMessage(Task *parent) +:Task(parent) +{ +} + +JT_PushMessage::~JT_PushMessage() +{ +} + +bool JT_PushMessage::take(const QDomElement &e) +{ + if(e.tagName() != "message") + return false; + + Stanza s = client()->stream().createStanza(addCorrectNS(e)); + if(s.isNull()) { + //printf("take: bad stanza??\n"); + return false; + } + + Message m; + if(!m.fromStanza(s, client()->timeZoneOffset())) { + //printf("bad message\n"); + return false; + } + + message(m); + return true; +} + + +//---------------------------------------------------------------------------- +// JT_GetLastActivity +//---------------------------------------------------------------------------- +class JT_GetLastActivity::Private +{ +public: + Private() {} + + int seconds; + QString message; +}; + +JT_GetLastActivity::JT_GetLastActivity(Task *parent) +:Task(parent) +{ + d = new Private; +} + +JT_GetLastActivity::~JT_GetLastActivity() +{ + delete d; +} + +void JT_GetLastActivity::get(const Jid &j) +{ + jid = j; + iq = createIQ(doc(), "get", jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:last"); + iq.appendChild(query); +} + +int JT_GetLastActivity::seconds() const +{ + return d->seconds; +} + +const QString &JT_GetLastActivity::message() const +{ + return d->message; +} + +void JT_GetLastActivity::onGo() +{ + send(iq); +} + +bool JT_GetLastActivity::take(const QDomElement &x) +{ + if(!iqVerify(x, jid, id())) + return false; + + if(x.attribute("type") == "result") { + QDomElement q = queryTag(x); + + d->message = q.text(); + bool ok; + d->seconds = q.attribute("seconds").toInt(&ok); + + setSuccess(ok); + } + else { + setError(x); + } + + return true; +} + +//---------------------------------------------------------------------------- +// JT_GetServices +//---------------------------------------------------------------------------- +JT_GetServices::JT_GetServices(Task *parent) +:Task(parent) +{ +} + +void JT_GetServices::get(const Jid &j) +{ + agentList.clear(); + + jid = j; + iq = createIQ(doc(), "get", jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:agents"); + iq.appendChild(query); +} + +const AgentList & JT_GetServices::agents() const +{ + return agentList; +} + +void JT_GetServices::onGo() +{ + send(iq); +} + +bool JT_GetServices::take(const QDomElement &x) +{ + if(!iqVerify(x, jid, id())) + return false; + + if(x.attribute("type") == "result") { + QDomElement q = queryTag(x); + + // agents + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + if(i.tagName() == "agent") { + AgentItem a; + + a.setJid(Jid(i.attribute("jid"))); + + QDomElement tag; + bool found; + + tag = findSubTag(i, "name", &found); + if(found) + a.setName(tagContent(tag)); + + // determine which namespaces does item support + QStringList ns; + + tag = findSubTag(i, "register", &found); + if(found) + ns << "jabber:iq:register"; + tag = findSubTag(i, "search", &found); + if(found) + ns << "jabber:iq:search"; + tag = findSubTag(i, "groupchat", &found); + if(found) + ns << "jabber:iq:conference"; + tag = findSubTag(i, "transport", &found); + if(found) + ns << "jabber:iq:gateway"; + + a.setFeatures(ns); + + agentList += a; + } + } + + setSuccess(true); + } + else { + setError(x); + } + + return true; +} + + +//---------------------------------------------------------------------------- +// JT_VCard +//---------------------------------------------------------------------------- +class JT_VCard::Private +{ +public: + Private() {} + + QDomElement iq; + Jid jid; + VCard vcard; +}; + +JT_VCard::JT_VCard(Task *parent) +:Task(parent) +{ + type = -1; + d = new Private; +} + +JT_VCard::~JT_VCard() +{ + delete d; +} + +void JT_VCard::get(const Jid &_jid) +{ + type = 0; + d->jid = _jid; + d->iq = createIQ(doc(), "get", d->jid.full(), id()); + QDomElement v = doc()->createElement("vCard"); + v.setAttribute("xmlns", "vcard-temp"); + v.setAttribute("version", "2.0"); + v.setAttribute("prodid", "-//HandGen//NONSGML vGen v1.0//EN"); + d->iq.appendChild(v); +} + +const Jid & JT_VCard::jid() const +{ + return d->jid; +} + +const VCard & JT_VCard::vcard() const +{ + return d->vcard; +} + +void JT_VCard::set(const VCard &card) +{ + type = 1; + d->vcard = card; + d->jid = ""; + d->iq = createIQ(doc(), "set", d->jid.full(), id()); + d->iq.appendChild(card.toXml(doc()) ); +} + +void JT_VCard::onGo() +{ + send(d->iq); +} + +bool JT_VCard::take(const QDomElement &x) +{ + Jid to = d->jid; + if (to.userHost() == client()->jid().userHost()) + to = client()->host(); + if(!iqVerify(x, to, id())) + return false; + + if(x.attribute("type") == "result") { + if(type == 0) { + for(QDomNode n = x.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement q = n.toElement(); + if(q.isNull()) + continue; + + if(q.tagName().upper() == "VCARD") { + if(d->vcard.fromXml(q)) { + setSuccess(); + return true; + } + } + } + + setError(ErrDisc + 1, tr("No VCard available")); + return true; + } + else { + setSuccess(); + return true; + } + } + else { + setError(x); + } + + return true; +} + + +//---------------------------------------------------------------------------- +// JT_Search +//---------------------------------------------------------------------------- +class JT_Search::Private +{ +public: + Private() {} + + Jid jid; + Form form; + QValueList resultList; +}; + +JT_Search::JT_Search(Task *parent) +:Task(parent) +{ + d = new Private; + type = -1; +} + +JT_Search::~JT_Search() +{ + delete d; +} + +void JT_Search::get(const Jid &jid) +{ + type = 0; + d->jid = jid; + iq = createIQ(doc(), "get", d->jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:search"); + iq.appendChild(query); +} + +void JT_Search::set(const Form &form) +{ + type = 1; + d->jid = form.jid(); + iq = createIQ(doc(), "set", d->jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:search"); + iq.appendChild(query); + + // key? + if(!form.key().isEmpty()) + query.appendChild(textTag(doc(), "key", form.key())); + + // fields + for(Form::ConstIterator it = form.begin(); it != form.end(); ++it) { + const FormField &f = *it; + query.appendChild(textTag(doc(), f.realName(), f.value())); + } +} + +const Form & JT_Search::form() const +{ + return d->form; +} + +const QValueList & JT_Search::results() const +{ + return d->resultList; +} + +void JT_Search::onGo() +{ + send(iq); +} + +bool JT_Search::take(const QDomElement &x) +{ + if(!iqVerify(x, d->jid, id())) + return false; + + Jid from(x.attribute("from")); + if(x.attribute("type") == "result") { + if(type == 0) { + d->form.clear(); + d->form.setJid(from); + + QDomElement q = queryTag(x); + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + if(i.tagName() == "instructions") + d->form.setInstructions(tagContent(i)); + else if(i.tagName() == "key") + d->form.setKey(tagContent(i)); + else { + FormField f; + if(f.setType(i.tagName())) { + f.setValue(tagContent(i)); + d->form += f; + } + } + } + } + else { + d->resultList.clear(); + + QDomElement q = queryTag(x); + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + if(i.tagName() == "item") { + SearchResult r(Jid(i.attribute("jid"))); + + QDomElement tag; + bool found; + + tag = findSubTag(i, "nick", &found); + if(found) + r.setNick(tagContent(tag)); + tag = findSubTag(i, "first", &found); + if(found) + r.setFirst(tagContent(tag)); + tag = findSubTag(i, "last", &found); + if(found) + r.setLast(tagContent(tag)); + tag = findSubTag(i, "email", &found); + if(found) + r.setEmail(tagContent(tag)); + + d->resultList += r; + } + } + } + setSuccess(); + } + else { + setError(x); + } + + return true; +} + + +//---------------------------------------------------------------------------- +// JT_ClientVersion +//---------------------------------------------------------------------------- +JT_ClientVersion::JT_ClientVersion(Task *parent) +:Task(parent) +{ +} + +void JT_ClientVersion::get(const Jid &jid) +{ + j = jid; + iq = createIQ(doc(), "get", j.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:version"); + iq.appendChild(query); +} + +void JT_ClientVersion::onGo() +{ + send(iq); +} + +bool JT_ClientVersion::take(const QDomElement &x) +{ + if(!iqVerify(x, j, id())) + return false; + + if(x.attribute("type") == "result") { + bool found; + QDomElement q = queryTag(x); + QDomElement tag; + tag = findSubTag(q, "name", &found); + if(found) + v_name = tagContent(tag); + tag = findSubTag(q, "version", &found); + if(found) + v_ver = tagContent(tag); + tag = findSubTag(q, "os", &found); + if(found) + v_os = tagContent(tag); + + setSuccess(); + } + else { + setError(x); + } + + return true; +} + +const Jid & JT_ClientVersion::jid() const +{ + return j; +} + +const QString & JT_ClientVersion::name() const +{ + return v_name; +} + +const QString & JT_ClientVersion::version() const +{ + return v_ver; +} + +const QString & JT_ClientVersion::os() const +{ + return v_os; +} + + +//---------------------------------------------------------------------------- +// JT_ClientTime +//---------------------------------------------------------------------------- +/*JT_ClientTime::JT_ClientTime(Task *parent, const Jid &_j) +:Task(parent) +{ + j = _j; + iq = createIQ("get", j.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:time"); + iq.appendChild(query); +} + +void JT_ClientTime::go() +{ + send(iq); +} + +bool JT_ClientTime::take(const QDomElement &x) +{ + if(x.attribute("id") != id()) + return FALSE; + + if(x.attribute("type") == "result") { + bool found; + QDomElement q = queryTag(x); + QDomElement tag; + tag = findSubTag(q, "utc", &found); + if(found) + stamp2TS(tagContent(tag), &utc); + tag = findSubTag(q, "tz", &found); + if(found) + timezone = tagContent(tag); + tag = findSubTag(q, "display", &found); + if(found) + display = tagContent(tag); + + setSuccess(TRUE); + } + else { + setError(getErrorString(x)); + setSuccess(FALSE); + } + + return TRUE; +} +*/ + + +//---------------------------------------------------------------------------- +// JT_ServInfo +//---------------------------------------------------------------------------- +JT_ServInfo::JT_ServInfo(Task *parent) +:Task(parent) +{ +} + +JT_ServInfo::~JT_ServInfo() +{ +} + +bool JT_ServInfo::take(const QDomElement &e) +{ + if(e.tagName() != "iq" || e.attribute("type") != "get") + return false; + + QString ns = queryNS(e); + if(ns == "jabber:iq:version") { + QDomElement iq = createIQ(doc(), "result", e.attribute("from"), e.attribute("id")); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:version"); + iq.appendChild(query); + query.appendChild(textTag(doc(), "name", client()->clientName())); + query.appendChild(textTag(doc(), "version", client()->clientVersion())); + query.appendChild(textTag(doc(), "os", client()->OSName())); + send(iq); + return true; + } + //else if(ns == "jabber:iq:time") { + // QDomElement iq = createIQ("result", e.attribute("from"), e.attribute("id")); + // QDomElement query = doc()->createElement("query"); + // query.setAttribute("xmlns", "jabber:iq:time"); + // iq.appendChild(query); + // QDateTime local = QDateTime::currentDateTime(); + // QDateTime utc = local.addSecs(-getTZOffset() * 3600); + // QString str = getTZString(); + // query.appendChild(textTag("utc", TS2stamp(utc))); + // query.appendChild(textTag("tz", str)); + // query.appendChild(textTag("display", QString("%1 %2").arg(local.toString()).arg(str))); + // send(iq); + // return TRUE; + //} + else if(ns == "http://jabber.org/protocol/disco#info") { + // Find out the node + QString node; + bool found; + QDomElement q = findSubTag(e, "query", &found); + if(found) // NOTE: Should always be true, since a NS was found above + node = q.attribute("node"); + + QDomElement iq = createIQ(doc(), "result", e.attribute("from"), e.attribute("id")); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/disco#info"); + if (!node.isEmpty()) + query.setAttribute("node", node); + iq.appendChild(query); + + // Identity + DiscoItem::Identity identity = client()->identity(); + QDomElement id = doc()->createElement("identity"); + if (!identity.category.isEmpty() && !identity.type.isEmpty()) { + id.setAttribute("category",identity.category); + id.setAttribute("type",identity.type); + if (!identity.name.isEmpty()) { + id.setAttribute("name",identity.name); + } + } + else { + // Default values + id.setAttribute("category","client"); + id.setAttribute("type","pc"); + } + query.appendChild(id); + + QDomElement feature; + if (node.isEmpty() || node == client()->capsNode() + "#" + client()->capsVersion()) { + // Standard features + feature = doc()->createElement("feature"); + feature.setAttribute("var", "http://jabber.org/protocol/bytestreams"); + query.appendChild(feature); + + feature = doc()->createElement("feature"); + feature.setAttribute("var", "http://jabber.org/protocol/si"); + query.appendChild(feature); + + feature = doc()->createElement("feature"); + feature.setAttribute("var", "http://jabber.org/protocol/si/profile/file-transfer"); + query.appendChild(feature); + + feature = doc()->createElement("feature"); + feature.setAttribute("var", "http://jabber.org/protocol/xhtml-im"); + query.appendChild(feature); + + feature = doc()->createElement("feature"); + feature.setAttribute("var", "http://jabber.org/protocol/disco#info"); + query.appendChild(feature); + + if (node.isEmpty()) { + // Extended features + QStringList exts = client()->extensions(); + for (QStringList::ConstIterator i = exts.begin(); i != exts.end(); ++i) { + const QStringList& l = client()->extension(*i).list(); + for ( QStringList::ConstIterator j = l.begin(); j != l.end(); ++j ) { + feature = doc()->createElement("feature"); + feature.setAttribute("var", *j); + query.appendChild(feature); + } + } + } + } + else if (node.startsWith(client()->capsNode() + "#")) { + QString ext = node.right(node.length()-client()->capsNode().length()-1); + if (client()->extensions().contains(ext)) { + const QStringList& l = client()->extension(ext).list(); + for ( QStringList::ConstIterator it = l.begin(); it != l.end(); ++it ) { + feature = doc()->createElement("feature"); + feature.setAttribute("var", *it); + query.appendChild(feature); + } + } + else { + // TODO: ERROR + } + } + else { + // TODO: ERROR + } + + send(iq); + return true; + } + + return false; +} + + +//---------------------------------------------------------------------------- +// JT_Gateway +//---------------------------------------------------------------------------- +JT_Gateway::JT_Gateway(Task *parent) +:Task(parent) +{ + type = -1; +} + +void JT_Gateway::get(const Jid &jid) +{ + type = 0; + v_jid = jid; + iq = createIQ(doc(), "get", v_jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:gateway"); + iq.appendChild(query); +} + +void JT_Gateway::set(const Jid &jid, const QString &prompt) +{ + type = 1; + v_jid = jid; + v_prompt = prompt; + iq = createIQ(doc(), "set", v_jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:gateway"); + iq.appendChild(query); + query.appendChild(textTag(doc(), "prompt", v_prompt)); +} + +void JT_Gateway::onGo() +{ + send(iq); +} + +Jid JT_Gateway::jid() const +{ + return v_jid; +} + +QString JT_Gateway::desc() const +{ + return v_desc; +} + +QString JT_Gateway::prompt() const +{ + return v_prompt; +} + +bool JT_Gateway::take(const QDomElement &x) +{ + if(!iqVerify(x, v_jid, id())) + return false; + + if(x.attribute("type") == "result") { + if(type == 0) { + QDomElement query = queryTag(x); + bool found; + QDomElement tag; + tag = findSubTag(query, "desc", &found); + if(found) + v_desc = tagContent(tag); + tag = findSubTag(query, "prompt", &found); + if(found) + v_prompt = tagContent(tag); + } + else { + QDomElement query = queryTag(x); + bool found; + QDomElement tag; + tag = findSubTag(query, "prompt", &found); + if(found) + v_prompt = tagContent(tag); + } + + setSuccess(); + } + else { + setError(x); + } + + return true; +} + +//---------------------------------------------------------------------------- +// JT_Browse +//---------------------------------------------------------------------------- +class JT_Browse::Private +{ +public: + QDomElement iq; + Jid jid; + AgentList agentList; + AgentItem root; +}; + +JT_Browse::JT_Browse (Task *parent) +:Task (parent) +{ + d = new Private; +} + +JT_Browse::~JT_Browse () +{ + delete d; +} + +void JT_Browse::get (const Jid &j) +{ + d->agentList.clear(); + + d->jid = j; + d->iq = createIQ(doc(), "get", d->jid.full(), id()); + QDomElement query = doc()->createElement("item"); + query.setAttribute("xmlns", "jabber:iq:browse"); + d->iq.appendChild(query); +} + +const AgentList & JT_Browse::agents() const +{ + return d->agentList; +} + +const AgentItem & JT_Browse::root() const +{ + return d->root; +} + +void JT_Browse::onGo () +{ + send(d->iq); +} + +AgentItem JT_Browse::browseHelper (const QDomElement &i) +{ + AgentItem a; + + if ( i.tagName() == "ns" ) + return a; + + a.setName ( i.attribute("name") ); + a.setJid ( i.attribute("jid") ); + + // there are two types of category/type specification: + // + // 1. + // 2. + + if ( i.tagName() == "item" || i.tagName() == "query" ) + a.setCategory ( i.attribute("category") ); + else + a.setCategory ( i.tagName() ); + + a.setType ( i.attribute("type") ); + + QStringList ns; + for(QDomNode n = i.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + if ( i.tagName() == "ns" ) + ns << i.text(); + } + + // For now, conference.jabber.org returns proper namespace only + // when browsing individual rooms. So it's a quick client-side fix. + if ( !a.features().canGroupchat() && a.category() == "conference" ) + ns << "jabber:iq:conference"; + + a.setFeatures (ns); + + return a; +} + +bool JT_Browse::take(const QDomElement &x) +{ + if(!iqVerify(x, d->jid, id())) + return false; + + if(x.attribute("type") == "result") { + for(QDomNode n = x.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + d->root = browseHelper (i); + + for(QDomNode nn = i.firstChild(); !nn.isNull(); nn = nn.nextSibling()) { + QDomElement e = nn.toElement(); + if ( e.isNull() ) + continue; + if ( e.tagName() == "ns" ) + continue; + + d->agentList += browseHelper (e); + } + } + + setSuccess(true); + } + else { + setError(x); + } + + return true; +} + +//---------------------------------------------------------------------------- +// JT_DiscoItems +//---------------------------------------------------------------------------- +class JT_DiscoItems::Private +{ +public: + Private() { } + + QDomElement iq; + Jid jid; + DiscoList items; +}; + +JT_DiscoItems::JT_DiscoItems(Task *parent) +: Task(parent) +{ + d = new Private; +} + +JT_DiscoItems::~JT_DiscoItems() +{ + delete d; +} + +void JT_DiscoItems::get(const DiscoItem &item) +{ + get(item.jid(), item.node()); +} + +void JT_DiscoItems::get (const Jid &j, const QString &node) +{ + d->items.clear(); + + d->jid = j; + d->iq = createIQ(doc(), "get", d->jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/disco#items"); + + if ( !node.isEmpty() ) + query.setAttribute("node", node); + + d->iq.appendChild(query); +} + +const DiscoList &JT_DiscoItems::items() const +{ + return d->items; +} + +void JT_DiscoItems::onGo () +{ + send(d->iq); +} + +bool JT_DiscoItems::take(const QDomElement &x) +{ + if(!iqVerify(x, d->jid, id())) + return false; + + if(x.attribute("type") == "result") { + QDomElement q = queryTag(x); + + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement e = n.toElement(); + if( e.isNull() ) + continue; + + if ( e.tagName() == "item" ) { + DiscoItem item; + + item.setJid ( e.attribute("jid") ); + item.setName( e.attribute("name") ); + item.setNode( e.attribute("node") ); + item.setAction( DiscoItem::string2action(e.attribute("action")) ); + + d->items.append( item ); + } + } + + setSuccess(true); + } + else { + setError(x); + } + + return true; +} + +//---------------------------------------------------------------------------- +// JT_DiscoInfo +//---------------------------------------------------------------------------- +class JT_DiscoInfo::Private +{ +public: + Private() { } + + QDomElement iq; + Jid jid; + QString node; + DiscoItem item; +}; + +JT_DiscoInfo::JT_DiscoInfo(Task *parent) +: Task(parent) +{ + d = new Private; +} + +JT_DiscoInfo::~JT_DiscoInfo() +{ + delete d; +} + +void JT_DiscoInfo::get(const DiscoItem &item) +{ + DiscoItem::Identity id; + if ( item.identities().count() == 1 ) + id = item.identities().first(); + get(item.jid(), item.node(), id); +} + +void JT_DiscoInfo::get (const Jid &j, const QString &node, DiscoItem::Identity ident) +{ + d->item = DiscoItem(); // clear item + + d->jid = j; + d->node = node; + d->iq = createIQ(doc(), "get", d->jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/disco#info"); + + if ( !node.isEmpty() ) + query.setAttribute("node", node); + + if ( !ident.category.isEmpty() && !ident.type.isEmpty() ) { + QDomElement i = doc()->createElement("item"); + + i.setAttribute("category", ident.category); + i.setAttribute("type", ident.type); + if ( !ident.name.isEmpty() ) + i.setAttribute("name", ident.name); + + query.appendChild( i ); + + } + + d->iq.appendChild(query); +} + + +/** + * Original requested jid. + * Is here because sometimes the responder does not include this information + * in the reply. + */ +const Jid& JT_DiscoInfo::jid() const +{ + return d->jid; +} + +/** + * Original requested node. + * Is here because sometimes the responder does not include this information + * in the reply. + */ +const QString& JT_DiscoInfo::node() const +{ + return d->node; +} + + + +const DiscoItem &JT_DiscoInfo::item() const +{ + return d->item; +} + +void JT_DiscoInfo::onGo () +{ + send(d->iq); +} + +bool JT_DiscoInfo::take(const QDomElement &x) +{ + if(!iqVerify(x, d->jid, id())) + return false; + + if(x.attribute("type") == "result") { + QDomElement q = queryTag(x); + + DiscoItem item; + + item.setJid( d->jid ); + item.setNode( q.attribute("node") ); + + QStringList features; + DiscoItem::Identities identities; + + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement e = n.toElement(); + if( e.isNull() ) + continue; + + if ( e.tagName() == "feature" ) { + features << e.attribute("var"); + } + else if ( e.tagName() == "identity" ) { + DiscoItem::Identity id; + + id.category = e.attribute("category"); + id.name = e.attribute("name"); + id.type = e.attribute("type"); + + identities.append( id ); + } + } + + item.setFeatures( features ); + item.setIdentities( identities ); + + d->item = item; + + setSuccess(true); + } + else { + setError(x); + } + + return true; +} + +//---------------------------------------------------------------------------- +// JT_DiscoPublish +//---------------------------------------------------------------------------- +class JT_DiscoPublish::Private +{ +public: + Private() { } + + QDomElement iq; + Jid jid; + DiscoList list; +}; + +JT_DiscoPublish::JT_DiscoPublish(Task *parent) +: Task(parent) +{ + d = new Private; +} + +JT_DiscoPublish::~JT_DiscoPublish() +{ + delete d; +} + +void JT_DiscoPublish::set(const Jid &j, const DiscoList &list) +{ + d->list = list; + d->jid = j; + + d->iq = createIQ(doc(), "set", d->jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/disco#items"); + + // FIXME: unsure about this + //if ( !node.isEmpty() ) + // query.setAttribute("node", node); + + DiscoList::ConstIterator it = list.begin(); + for ( ; it != list.end(); ++it) { + QDomElement w = doc()->createElement("item"); + + w.setAttribute("jid", (*it).jid().full()); + if ( !(*it).name().isEmpty() ) + w.setAttribute("name", (*it).name()); + if ( !(*it).node().isEmpty() ) + w.setAttribute("node", (*it).node()); + w.setAttribute("action", DiscoItem::action2string((*it).action())); + + query.appendChild( w ); + } + + d->iq.appendChild(query); +} + +void JT_DiscoPublish::onGo () +{ + send(d->iq); +} + +bool JT_DiscoPublish::take(const QDomElement &x) +{ + if(!iqVerify(x, d->jid, id())) + return false; + + if(x.attribute("type") == "result") { + setSuccess(true); + } + else { + setError(x); + } + + return true; +} + +//---------------------------------------------------------------------------- +// JT_MucPresence +//---------------------------------------------------------------------------- +JT_MucPresence::JT_MucPresence(Task *parent) +:Task(parent) +{ + type = -1; +} + +JT_MucPresence::~JT_MucPresence() +{ +} + +void JT_MucPresence::pres(const Status &s) +{ + type = 0; + + tag = doc()->createElement("presence"); + if(!s.isAvailable()) { + tag.setAttribute("type", "unavailable"); + if(!s.status().isEmpty()) + tag.appendChild(textTag(doc(), "status", s.status())); + } + else { + if(s.isInvisible()) + tag.setAttribute("type", "invisible"); + + if(!s.show().isEmpty()) + tag.appendChild(textTag(doc(), "show", s.show())); + if(!s.status().isEmpty()) + tag.appendChild(textTag(doc(), "status", s.status())); + + tag.appendChild( textTag(doc(), "priority", QString("%1").arg(s.priority()) ) ); + + if(!s.keyID().isEmpty()) { + QDomElement x = textTag(doc(), "x", s.keyID()); + x.setAttribute("xmlns", "http://jabber.org/protocol/e2e"); + tag.appendChild(x); + } + if(!s.xsigned().isEmpty()) { + QDomElement x = textTag(doc(), "x", s.xsigned()); + x.setAttribute("xmlns", "jabber:x:signed"); + tag.appendChild(x); + } + + if(!s.capsNode().isEmpty() && !s.capsVersion().isEmpty()) { + QDomElement c = doc()->createElement("c"); + c.setAttribute("xmlns","http://jabber.org/protocol/caps"); + c.setAttribute("node",s.capsNode()); + c.setAttribute("ver",s.capsVersion()); + if (!s.capsExt().isEmpty()) + c.setAttribute("ext",s.capsExt()); + tag.appendChild(c); + } + } +} + +void JT_MucPresence::pres(const Jid &to, const Status &s, const QString &password) +{ + pres(s); + tag.setAttribute("to", to.full()); + QDomElement x = textTag(doc(), "x", s.xsigned()); + x.setAttribute("xmlns", "http://jabber.org/protocol/muc"); + x.appendChild( textTag(doc(), "password", password.latin1()) ); + tag.appendChild(x); +} + +void JT_MucPresence::onGo() +{ + send(tag); + setSuccess(); +} + + +//---------------------------------------------------------------------------- +// JT_PrivateStorage +//---------------------------------------------------------------------------- +class JT_PrivateStorage::Private +{ + public: + Private() : type(-1) {} + + QDomElement iq; + QDomElement elem; + int type; +}; + +JT_PrivateStorage::JT_PrivateStorage(Task *parent) + :Task(parent) +{ + d = new Private; +} + +JT_PrivateStorage::~JT_PrivateStorage() +{ + delete d; +} + +void JT_PrivateStorage::get(const QString& tag, const QString& xmlns) +{ + d->type = 0; + d->iq = createIQ(doc(), "get" , QString() , id() ); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:private"); + d->iq.appendChild(query); + QDomElement s = doc()->createElement(tag); + if(!xmlns.isEmpty()) + s.setAttribute("xmlns", xmlns); + query.appendChild(s); +} + +void JT_PrivateStorage::set(const QDomElement& element) +{ + d->type = 1; + d->elem=element; + QDomNode n=doc()->importNode(element,true); + + d->iq = createIQ(doc(), "set" , QString() , id() ); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:private"); + d->iq.appendChild(query); + query.appendChild(n); +} + +void JT_PrivateStorage::onGo() +{ + send(d->iq); +} + +bool JT_PrivateStorage::take(const QDomElement &x) +{ + QString to = client()->host(); + if(!iqVerify(x, to, id())) + return false; + + if(x.attribute("type") == "result") { + if(d->type == 0) { + QDomElement q = queryTag(x); + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + d->elem=i; + break; + } + } + setSuccess(); + return true; + } + else { + setError(x); + } + + return true; +} + + +QDomElement JT_PrivateStorage::element( ) +{ + return d->elem; +} + diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_tasks.h b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_tasks.h new file mode 100644 index 00000000..ceb1e294 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_tasks.h @@ -0,0 +1,485 @@ +/* + * tasks.h - basic tasks + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef JABBER_TASKS_H +#define JABBER_TASKS_H + +#include +#include + +#include"im.h" +#include"xmpp_vcard.h" + +namespace XMPP +{ + class Roster; + class Status; + + class JT_Register : public Task + { + Q_OBJECT + public: + JT_Register(Task *parent); + ~JT_Register(); + + void reg(const QString &user, const QString &pass); + void changepw(const QString &pass); + void unreg(const Jid &j=""); + + const Form & form() const; + void getForm(const Jid &); + void setForm(const Form &); + + void onGo(); + bool take(const QDomElement &); + + private: + QDomElement iq; + Jid to; + + class Private; + Private *d; + }; + + class JT_UnRegister : public Task + { + Q_OBJECT + public: + JT_UnRegister(Task *parent); + ~JT_UnRegister(); + + void unreg(const Jid &); + + void onGo(); + + private slots: + void getFormFinished(); + void unregFinished(); + + private: + class Private; + Private *d; + }; + + class JT_Roster : public Task + { + Q_OBJECT + public: + JT_Roster(Task *parent); + ~JT_Roster(); + + void get(); + void set(const Jid &, const QString &name, const QStringList &groups); + void remove(const Jid &); + + const Roster & roster() const; + + QString toString() const; + bool fromString(const QString &); + + void onGo(); + bool take(const QDomElement &x); + + private: + int type; + QDomElement iq; + Jid to; + + class Private; + Private *d; + }; + + class JT_PushRoster : public Task + { + Q_OBJECT + public: + JT_PushRoster(Task *parent); + ~JT_PushRoster(); + + bool take(const QDomElement &); + + signals: + void roster(const Roster &); + + private: + class Private; + Private *d; + }; + + class JT_Presence : public Task + { + Q_OBJECT + public: + JT_Presence(Task *parent); + ~JT_Presence(); + + void pres(const Status &); + void pres(const Jid &, const Status &); + void sub(const Jid &, const QString &subType); + + void onGo(); + + private: + QDomElement tag; + int type; + + class Private; + Private *d; + }; + + class JT_PushPresence : public Task + { + Q_OBJECT + public: + JT_PushPresence(Task *parent); + ~JT_PushPresence(); + + bool take(const QDomElement &); + + signals: + void presence(const Jid &, const Status &); + void subscription(const Jid &, const QString &); + + private: + class Private; + Private *d; + }; + + class JT_Message : public Task + { + Q_OBJECT + public: + JT_Message(Task *parent, const Message &); + ~JT_Message(); + + void onGo(); + + private: + Message m; + + class Private; + Private *d; + }; + + class JT_PushMessage : public Task + { + Q_OBJECT + public: + JT_PushMessage(Task *parent); + ~JT_PushMessage(); + + bool take(const QDomElement &); + + signals: + void message(const Message &); + + private: + class Private; + Private *d; + }; + + class JT_GetLastActivity : public Task + { + Q_OBJECT + public: + JT_GetLastActivity(Task *); + ~JT_GetLastActivity(); + + void get(const Jid &); + + int seconds() const; + const QString &message() const; + + void onGo(); + bool take(const QDomElement &x); + + private: + class Private; + Private *d; + + QDomElement iq; + Jid jid; + }; + + class JT_GetServices : public Task + { + Q_OBJECT + public: + JT_GetServices(Task *); + + void get(const Jid &); + + const AgentList & agents() const; + + void onGo(); + bool take(const QDomElement &x); + + private: + class Private; + Private *d; + + QDomElement iq; + Jid jid; + AgentList agentList; + }; + + class JT_VCard : public Task + { + Q_OBJECT + public: + JT_VCard(Task *parent); + ~JT_VCard(); + + void get(const Jid &); + void set(const VCard &); + + const Jid & jid() const; + const VCard & vcard() const; + + void onGo(); + bool take(const QDomElement &x); + + private: + int type; + + class Private; + Private *d; + }; + + class JT_Search : public Task + { + Q_OBJECT + public: + JT_Search(Task *parent); + ~JT_Search(); + + const Form & form() const; + const QValueList & results() const; + + void get(const Jid &); + void set(const Form &); + + void onGo(); + bool take(const QDomElement &x); + + private: + QDomElement iq; + int type; + + class Private; + Private *d; + }; + + class JT_ClientVersion : public Task + { + Q_OBJECT + public: + JT_ClientVersion(Task *); + + void get(const Jid &); + void onGo(); + bool take(const QDomElement &); + + const Jid & jid() const; + const QString & name() const; + const QString & version() const; + const QString & os() const; + + private: + QDomElement iq; + + Jid j; + QString v_name, v_ver, v_os; + }; +/* + class JT_ClientTime : public Task + { + Q_OBJECT + public: + JT_ClientTime(Task *, const Jid &); + + void go(); + bool take(const QDomElement &); + + Jid j; + QDateTime utc; + QString timezone, display; + + private: + QDomElement iq; + }; +*/ + class JT_ServInfo : public Task + { + Q_OBJECT + public: + JT_ServInfo(Task *); + ~JT_ServInfo(); + + bool take(const QDomElement &); + }; + + class JT_Gateway : public Task + { + Q_OBJECT + public: + JT_Gateway(Task *); + + void get(const Jid &); + void set(const Jid &, const QString &prompt); + void onGo(); + bool take(const QDomElement &); + + Jid jid() const; + QString desc() const; + QString prompt() const; + + private: + QDomElement iq; + + int type; + Jid v_jid; + QString v_prompt, v_desc; + }; + + class JT_Browse : public Task + { + Q_OBJECT + public: + JT_Browse(Task *); + ~JT_Browse(); + + void get(const Jid &); + + const AgentList & agents() const; + const AgentItem & root() const; + + void onGo(); + bool take(const QDomElement &); + + private: + class Private; + Private *d; + + AgentItem browseHelper (const QDomElement &i); + }; + + class JT_DiscoItems : public Task + { + Q_OBJECT + public: + JT_DiscoItems(Task *); + ~JT_DiscoItems(); + + void get(const Jid &, const QString &node = QString::null); + void get(const DiscoItem &); + + const DiscoList &items() const; + + void onGo(); + bool take(const QDomElement &); + + private: + class Private; + Private *d; + }; + + class JT_DiscoInfo : public Task + { + Q_OBJECT + public: + JT_DiscoInfo(Task *); + ~JT_DiscoInfo(); + + void get(const Jid &, const QString &node = QString::null, const DiscoItem::Identity = DiscoItem::Identity()); + void get(const DiscoItem &); + + const DiscoItem &item() const; + const Jid& jid() const; + const QString& node() const; + + void onGo(); + bool take(const QDomElement &); + + private: + class Private; + Private *d; + }; + + class JT_DiscoPublish : public Task + { + Q_OBJECT + public: + JT_DiscoPublish(Task *); + ~JT_DiscoPublish(); + + void set(const Jid &, const DiscoList &); + + void onGo(); + bool take(const QDomElement &); + + private: + class Private; + Private *d; + }; + + class JT_MucPresence : public Task + { + Q_OBJECT + public: + JT_MucPresence(Task *parent); + ~JT_MucPresence(); + + void pres(const Status &); + void pres(const Jid &, const Status &, const QString &password); + + void onGo(); + + private: + QDomElement tag; + int type; + + class Private; + Private *d; + }; + + class JT_PrivateStorage : public Task + { + Q_OBJECT + public: + JT_PrivateStorage(Task *parent); + ~JT_PrivateStorage(); + + void set(const QDomElement &); + void get(const QString &tag, const QString& xmlns); + + QDomElement element(); + + void onGo(); + bool take(const QDomElement &); + + private: + class Private; + Private *d; + }; + +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_vcard.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_vcard.cpp new file mode 100644 index 00000000..296c53c6 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_vcard.cpp @@ -0,0 +1,1241 @@ +/* + * xmpp_vcard.cpp - classes for handling vCards + * Copyright (C) 2003 Michail Pishchagin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "xmpp_vcard.h" + +#include "base64.h" + +#include +#include + +#include // needed for image format recognition +#include + +// Justin's XML helper functions + +static QDomElement textTag(QDomDocument *doc, const QString &name, const QString &content) +{ + QDomElement tag = doc->createElement(name); + QDomText text = doc->createTextNode(content); + tag.appendChild(text); + + return tag; +} + +static QDomElement findSubTag(const QDomElement &e, const QString &name, bool *found) +{ + if(found) + *found = FALSE; + + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + if(i.tagName().upper() == name.upper()) { // mblsha: ignore case when searching + if(found) + *found = TRUE; + return i; + } + } + + QDomElement tmp; + return tmp; +} + +// mblsha's own functions + +static QDomElement emptyTag(QDomDocument *doc, const QString &name) +{ + QDomElement tag = doc->createElement(name); + + return tag; +} + +static bool hasSubTag(const QDomElement &e, const QString &name) +{ + bool found; + findSubTag(e, name, &found); + return found; +} + +static QString subTagText(const QDomElement &e, const QString &name) +{ + bool found; + QDomElement i = findSubTag(e, name, &found); + if ( found ) + return i.text().stripWhiteSpace(); + return QString::null; +} + +using namespace XMPP; + +//---------------------------------------------------------------------------- +// VCard +//---------------------------------------------------------------------------- +static QString image2type(const QByteArray &ba) +{ + QBuffer buf(ba); + buf.open(IO_ReadOnly); + QString format = QImageIO::imageFormat( &buf ); + + // TODO: add more formats + if ( format == "PNG" || format == "PsiPNG" ) + return "image/png"; + if ( format == "MNG" ) + return "video/x-mng"; + if ( format == "GIF" ) + return "image/gif"; + if ( format == "BMP" ) + return "image/bmp"; + if ( format == "XPM" ) + return "image/x-xpm"; + if ( format == "SVG" ) + return "image/svg+xml"; + if ( format == "JPEG" ) + return "image/jpeg"; + + qWarning("WARNING! VCard::image2type: unknown format = '%s'", format.latin1()); + + return "image/unknown"; +} + +// Long lines of encoded binary data SHOULD BE folded to 75 characters using the folding method defined in [MIME-DIR]. +static QString foldString(const QString &s) +{ + QString ret; + + for (int i = 0; i < (int)s.length(); i++) { + if ( !(i % 75) ) + ret += '\n'; + ret += s[i]; + } + + return ret; +} + +class VCard::Private +{ +public: + Private(); + ~Private(); + + QString version; + QString fullName; + QString familyName, givenName, middleName, prefixName, suffixName; + QString nickName; + + QByteArray photo; + QString photoURI; + + QString bday; + AddressList addressList; + LabelList labelList; + PhoneList phoneList; + EmailList emailList; + QString jid; + QString mailer; + QString timezone; + Geo geo; + QString title; + QString role; + + QByteArray logo; + QString logoURI; + + VCard *agent; + QString agentURI; + + Org org; + QStringList categories; + QString note; + QString prodId; + QString rev; + QString sortString; + + QByteArray sound; + QString soundURI, soundPhonetic; + + QString uid; + QString url; + QString desc; + PrivacyClass privacyClass; + QByteArray key; + + bool isEmpty(); +}; + +VCard::Private::Private() +{ + privacyClass = pcNone; + agent = 0; +} + +VCard::Private::~Private() +{ + delete agent; +} + +bool VCard::Private::isEmpty() +{ + if ( !version.isEmpty() || + !fullName.isEmpty() || + !familyName.isEmpty() || !givenName.isEmpty() || !middleName.isEmpty() || !prefixName.isEmpty() || !suffixName.isEmpty() || + !nickName.isEmpty() || + !photo.isEmpty() || !photoURI.isEmpty() || + !bday.isEmpty() || + !addressList.isEmpty() || + !labelList.isEmpty() || + !phoneList.isEmpty() || + !emailList.isEmpty() || + !jid.isEmpty() || + !mailer.isEmpty() || + !timezone.isEmpty() || + !geo.lat.isEmpty() || !geo.lon.isEmpty() || + !title.isEmpty() || + !role.isEmpty() || + !logo.isEmpty() || !logoURI.isEmpty() || + (agent && !agent->isEmpty()) || !agentURI.isEmpty() || + !org.name.isEmpty() || !org.unit.isEmpty() || + !categories.isEmpty() || + !note.isEmpty() || + !prodId.isEmpty() || + !rev.isEmpty() || + !sortString.isEmpty() || + !sound.isEmpty() || !soundURI.isEmpty() || !soundPhonetic.isEmpty() || + !uid.isEmpty() || + !url.isEmpty() || + !desc.isEmpty() || + (privacyClass != pcNone) || + !key.isEmpty() ) + { + return false; + } + return true; +} + +VCard::VCard() +{ + d = new Private; +} + +VCard::VCard(const VCard &from) +{ + d = new Private; + *this = from; +} + +VCard & VCard::operator=(const VCard &from) +{ + if(d->agent) { + delete d->agent; + d->agent = 0; + } + + *d = *from.d; + + if(from.d->agent) { + // dup the agent + d->agent = new VCard(*from.d->agent); + } + + return *this; +} + +VCard::~VCard() +{ + delete d; +} + +QDomElement VCard::toXml(QDomDocument *doc) const +{ + QDomElement v = doc->createElement("vCard"); + v.setAttribute("version", "2.0"); + v.setAttribute("prodid", "-//HandGen//NONSGML vGen v1.0//EN"); + v.setAttribute("xmlns", "vcard-temp"); + + if ( !d->version.isEmpty() ) + v.appendChild( textTag(doc, "VERSION", d->version) ); + if ( !d->fullName.isEmpty() ) + v.appendChild( textTag(doc, "FN", d->fullName) ); + + if ( !d->familyName.isEmpty() || !d->givenName.isEmpty() || !d->middleName.isEmpty() || + !d->prefixName.isEmpty() || !d->suffixName.isEmpty() ) { + QDomElement w = doc->createElement("N"); + + if ( !d->familyName.isEmpty() ) + w.appendChild( textTag(doc, "FAMILY", d->familyName) ); + if ( !d->givenName.isEmpty() ) + w.appendChild( textTag(doc, "GIVEN", d->givenName) ); + if ( !d->middleName.isEmpty() ) + w.appendChild( textTag(doc, "MIDDLE", d->middleName) ); + if ( !d->prefixName.isEmpty() ) + w.appendChild( textTag(doc, "PREFIX", d->prefixName) ); + if ( !d->suffixName.isEmpty() ) + w.appendChild( textTag(doc, "SUFFIX", d->suffixName) ); + + v.appendChild(w); + } + + if ( !d->nickName.isEmpty() ) + v.appendChild( textTag(doc, "NICKNAME", d->nickName) ); + + if ( !d->photo.isEmpty() || !d->photoURI.isEmpty() ) { + QDomElement w = doc->createElement("PHOTO"); + + if ( !d->photo.isEmpty() ) { + w.appendChild( textTag(doc, "TYPE", image2type(d->photo)) ); + w.appendChild( textTag(doc, "BINVAL", foldString( Base64::arrayToString(d->photo)) ) ); + } + else if ( !d->photoURI.isEmpty() ) + w.appendChild( textTag(doc, "EXTVAL", d->photoURI) ); + + v.appendChild(w); + } + + if ( !d->bday.isEmpty() ) + v.appendChild( textTag(doc, "BDAY", d->bday) ); + + if ( !d->addressList.isEmpty() ) { + AddressList::Iterator it = d->addressList.begin(); + for ( ; it != d->addressList.end(); ++it ) { + QDomElement w = doc->createElement("ADR"); + Address a = *it; + + if ( a.home ) + w.appendChild( emptyTag(doc, "HOME") ); + if ( a.work ) + w.appendChild( emptyTag(doc, "WORK") ); + if ( a.postal ) + w.appendChild( emptyTag(doc, "POSTAL") ); + if ( a.parcel ) + w.appendChild( emptyTag(doc, "PARCEL") ); + if ( a.dom ) + w.appendChild( emptyTag(doc, "DOM") ); + if ( a.intl ) + w.appendChild( emptyTag(doc, "INTL") ); + if ( a.pref ) + w.appendChild( emptyTag(doc, "PREF") ); + + if ( !a.pobox.isEmpty() ) + w.appendChild( textTag(doc, "POBOX", a.pobox) ); + if ( !a.extaddr.isEmpty() ) + w.appendChild( textTag(doc, "EXTADR", a.extaddr) ); + if ( !a.street.isEmpty() ) + w.appendChild( textTag(doc, "STREET", a.street) ); + if ( !a.locality.isEmpty() ) + w.appendChild( textTag(doc, "LOCALITY", a.locality) ); + if ( !a.region.isEmpty() ) + w.appendChild( textTag(doc, "REGION", a.region) ); + if ( !a.pcode.isEmpty() ) + w.appendChild( textTag(doc, "PCODE", a.pcode) ); + if ( !a.country.isEmpty() ) + w.appendChild( textTag(doc, "CTRY", a.country) ); + + v.appendChild(w); + } + } + + if ( !d->labelList.isEmpty() ) { + LabelList::Iterator it = d->labelList.begin(); + for ( ; it != d->labelList.end(); ++it ) { + QDomElement w = doc->createElement("LABEL"); + Label l = *it; + + if ( l.home ) + w.appendChild( emptyTag(doc, "HOME") ); + if ( l.work ) + w.appendChild( emptyTag(doc, "WORK") ); + if ( l.postal ) + w.appendChild( emptyTag(doc, "POSTAL") ); + if ( l.parcel ) + w.appendChild( emptyTag(doc, "PARCEL") ); + if ( l.dom ) + w.appendChild( emptyTag(doc, "DOM") ); + if ( l.intl ) + w.appendChild( emptyTag(doc, "INTL") ); + if ( l.pref ) + w.appendChild( emptyTag(doc, "PREF") ); + + if ( !l.lines.isEmpty() ) { + QStringList::Iterator it = l.lines.begin(); + for ( ; it != l.lines.end(); ++it ) + w.appendChild( textTag(doc, "LINE", *it) ); + } + + v.appendChild(w); + } + } + + if ( !d->phoneList.isEmpty() ) { + PhoneList::Iterator it = d->phoneList.begin(); + for ( ; it != d->phoneList.end(); ++it ) { + QDomElement w = doc->createElement("TEL"); + Phone p = *it; + + if ( p.home ) + w.appendChild( emptyTag(doc, "HOME") ); + if ( p.work ) + w.appendChild( emptyTag(doc, "WORK") ); + if ( p.voice ) + w.appendChild( emptyTag(doc, "VOICE") ); + if ( p.fax ) + w.appendChild( emptyTag(doc, "FAX") ); + if ( p.pager ) + w.appendChild( emptyTag(doc, "PAGER") ); + if ( p.msg ) + w.appendChild( emptyTag(doc, "MSG") ); + if ( p.cell ) + w.appendChild( emptyTag(doc, "CELL") ); + if ( p.video ) + w.appendChild( emptyTag(doc, "VIDEO") ); + if ( p.bbs ) + w.appendChild( emptyTag(doc, "BBS") ); + if ( p.modem ) + w.appendChild( emptyTag(doc, "MODEM") ); + if ( p.isdn ) + w.appendChild( emptyTag(doc, "ISDN") ); + if ( p.pcs ) + w.appendChild( emptyTag(doc, "PCS") ); + if ( p.pref ) + w.appendChild( emptyTag(doc, "PREF") ); + + if ( !p.number.isEmpty() ) + w.appendChild( textTag(doc, "NUMBER", p.number) ); + + v.appendChild(w); + } + } + + if ( !d->emailList.isEmpty() ) { + EmailList::Iterator it = d->emailList.begin(); + for ( ; it != d->emailList.end(); ++it ) { + QDomElement w = doc->createElement("EMAIL"); + Email e = *it; + + if ( e.home ) + w.appendChild( emptyTag(doc, "HOME") ); + if ( e.work ) + w.appendChild( emptyTag(doc, "WORK") ); + if ( e.internet ) + w.appendChild( emptyTag(doc, "INTERNET") ); + if ( e.x400 ) + w.appendChild( emptyTag(doc, "X400") ); + + if ( !e.userid.isEmpty() ) + w.appendChild( textTag(doc, "USERID", e.userid) ); + + v.appendChild(w); + } + } + + if ( !d->jid.isEmpty() ) + v.appendChild( textTag(doc, "JABBERID", d->jid) ); + if ( !d->mailer.isEmpty() ) + v.appendChild( textTag(doc, "MAILER", d->mailer) ); + if ( !d->timezone.isEmpty() ) + v.appendChild( textTag(doc, "TZ", d->timezone) ); + + if ( !d->geo.lat.isEmpty() || !d->geo.lon.isEmpty() ) { + QDomElement w = doc->createElement("GEO"); + + if ( !d->geo.lat.isEmpty() ) + w.appendChild( textTag(doc, "LAT", d->geo.lat) ); + if ( !d->geo.lon.isEmpty() ) + w.appendChild( textTag(doc, "LON", d->geo.lon)); + + v.appendChild(w); + } + + if ( !d->title.isEmpty() ) + v.appendChild( textTag(doc, "TITLE", d->title) ); + if ( !d->role.isEmpty() ) + v.appendChild( textTag(doc, "ROLE", d->role) ); + + if ( !d->logo.isEmpty() || !d->logoURI.isEmpty() ) { + QDomElement w = doc->createElement("LOGO"); + + if ( !d->logo.isEmpty() ) { + w.appendChild( textTag(doc, "TYPE", image2type(d->logo)) ); + w.appendChild( textTag(doc, "BINVAL", foldString( Base64::arrayToString(d->logo)) ) ); + } + else if ( !d->logoURI.isEmpty() ) + w.appendChild( textTag(doc, "EXTVAL", d->logoURI) ); + + v.appendChild(w); + } + + if ( !d->agentURI.isEmpty() || (d->agent && d->agent->isEmpty()) ) { + QDomElement w = doc->createElement("AGENT"); + + if ( d->agent && !d->agent->isEmpty() ) + w.appendChild( d->agent->toXml(doc) ); + else if ( !d->agentURI.isEmpty() ) + w.appendChild( textTag(doc, "EXTVAL", d->agentURI) ); + + v.appendChild(w); + } + + if ( !d->org.name.isEmpty() || !d->org.unit.isEmpty() ) { + QDomElement w = doc->createElement("ORG"); + + if ( !d->org.name.isEmpty() ) + w.appendChild( textTag(doc, "ORGNAME", d->org.name) ); + + if ( !d->org.unit.isEmpty() ) { + QStringList::Iterator it = d->org.unit.begin(); + for ( ; it != d->org.unit.end(); ++it ) + w.appendChild( textTag(doc, "ORGUNIT", *it) ); + } + + v.appendChild(w); + } + + if ( !d->categories.isEmpty() ) { + QDomElement w = doc->createElement("CATEGORIES"); + + QStringList::Iterator it = d->categories.begin(); + for ( ; it != d->categories.end(); ++it ) + w.appendChild( textTag(doc, "KEYWORD", *it) ); + + v.appendChild(w); + } + + if ( !d->note.isEmpty() ) + v.appendChild( textTag(doc, "NOTE", d->note) ); + if ( !d->prodId.isEmpty() ) + v.appendChild( textTag(doc, "PRODID", d->prodId) ); + if ( !d->rev.isEmpty() ) + v.appendChild( textTag(doc, "REV", d->rev) ); + if ( !d->sortString.isEmpty() ) + v.appendChild( textTag(doc, "SORT-STRING", d->sortString) ); + + if ( !d->sound.isEmpty() || !d->soundURI.isEmpty() || !d->soundPhonetic.isEmpty() ) { + QDomElement w = doc->createElement("SOUND"); + + if ( !d->sound.isEmpty() ) + w.appendChild( textTag(doc, "BINVAL", foldString( Base64::arrayToString(d->sound)) ) ); + else if ( !d->soundURI.isEmpty() ) + w.appendChild( textTag(doc, "EXTVAL", d->soundURI) ); + else if ( !d->soundPhonetic.isEmpty() ) + w.appendChild( textTag(doc, "PHONETIC", d->soundPhonetic) ); + + v.appendChild(w); + } + + if ( !d->uid.isEmpty() ) + v.appendChild( textTag(doc, "UID", d->uid) ); + if ( !d->url.isEmpty() ) + v.appendChild( textTag(doc, "URL", d->url) ); + if ( !d->desc.isEmpty() ) + v.appendChild( textTag(doc, "DESC", d->desc) ); + + if ( d->privacyClass != pcNone ) { + QDomElement w = doc->createElement("CLASS"); + + if ( d->privacyClass == pcPublic ) + w.appendChild( emptyTag(doc, "PUBLIC") ); + else if ( d->privacyClass == pcPrivate ) + w.appendChild( emptyTag(doc, "PRIVATE") ); + else if ( d->privacyClass == pcConfidential ) + w.appendChild( emptyTag(doc, "CONFIDENTIAL") ); + + v.appendChild(w); + } + + if ( !d->key.isEmpty() ) { + QDomElement w = doc->createElement("KEY"); + + // TODO: Justin, please check out this code + w.appendChild( textTag(doc, "TYPE", "text/plain")); // FIXME + w.appendChild( textTag(doc, "CRED", QString::fromUtf8(d->key)) ); // FIXME + + v.appendChild(w); + } + + return v; +} + +bool VCard::fromXml(const QDomElement &q) +{ + if ( q.tagName().upper() != "VCARD" ) + return false; + + QDomNode n = q.firstChild(); + for ( ; !n.isNull(); n = n.nextSibling() ) { + QDomElement i = n.toElement(); + if ( i.isNull() ) + continue; + + QString tag = i.tagName().upper(); + + bool found; + QDomElement e; + + if ( tag == "VERSION" ) + d->version = i.text().stripWhiteSpace(); + else if ( tag == "FN" ) + d->fullName = i.text().stripWhiteSpace(); + else if ( tag == "N" ) { + d->familyName = subTagText(i, "FAMILY"); + d->givenName = subTagText(i, "GIVEN"); + d->middleName = subTagText(i, "MIDDLE"); + d->prefixName = subTagText(i, "PREFIX"); + d->suffixName = subTagText(i, "SUFFIX"); + } + else if ( tag == "NICKNAME" ) + d->nickName = i.text().stripWhiteSpace(); + else if ( tag == "PHOTO" ) { + d->photo = Base64::stringToArray( subTagText(i, "BINVAL") ); + d->photoURI = subTagText(i, "EXTVAL"); + } + else if ( tag == "BDAY" ) + d->bday = i.text().stripWhiteSpace(); + else if ( tag == "ADR" ) { + Address a; + + a.home = hasSubTag(i, "HOME"); + a.work = hasSubTag(i, "WORK"); + a.postal = hasSubTag(i, "POSTAL"); + a.parcel = hasSubTag(i, "PARCEL"); + a.dom = hasSubTag(i, "DOM"); + a.intl = hasSubTag(i, "INTL"); + a.pref = hasSubTag(i, "PREF"); + + a.pobox = subTagText(i, "POBOX"); + a.extaddr = subTagText(i, "EXTADR"); + a.street = subTagText(i, "STREET"); + a.locality = subTagText(i, "LOCALITY"); + a.region = subTagText(i, "REGION"); + a.pcode = subTagText(i, "PCODE"); + a.country = subTagText(i, "CTRY"); + + if ( a.country.isEmpty() ) // FIXME: Workaround for Psi prior to 0.9 + if ( hasSubTag(i, "COUNTRY") ) + a.country = subTagText(i, "COUNTRY"); + + if ( a.extaddr.isEmpty() ) // FIXME: Workaround for Psi prior to 0.9 + if ( hasSubTag(i, "EXTADD") ) + a.extaddr = subTagText(i, "EXTADD"); + + d->addressList.append ( a ); + } + else if ( tag == "LABEL" ) { + Label l; + + l.home = hasSubTag(i, "HOME"); + l.work = hasSubTag(i, "WORK"); + l.postal = hasSubTag(i, "POSTAL"); + l.parcel = hasSubTag(i, "PARCEL"); + l.dom = hasSubTag(i, "DOM"); + l.intl = hasSubTag(i, "INTL"); + l.pref = hasSubTag(i, "PREF"); + + QDomNode nn = i.firstChild(); + for ( ; !nn.isNull(); nn = nn.nextSibling() ) { + QDomElement ii = nn.toElement(); + if ( ii.isNull() ) + continue; + + if ( ii.tagName().upper() == "LINE" ) + l.lines.append ( ii.text().stripWhiteSpace() ); + } + + d->labelList.append ( l ); + } + else if ( tag == "TEL" ) { + Phone p; + + p.home = hasSubTag(i, "HOME"); + p.work = hasSubTag(i, "WORK"); + p.voice = hasSubTag(i, "VOICE"); + p.fax = hasSubTag(i, "FAX"); + p.pager = hasSubTag(i, "PAGER"); + p.msg = hasSubTag(i, "MSG"); + p.cell = hasSubTag(i, "CELL"); + p.video = hasSubTag(i, "VIDEO"); + p.bbs = hasSubTag(i, "BBS"); + p.modem = hasSubTag(i, "MODEM"); + p.isdn = hasSubTag(i, "ISDN"); + p.pcs = hasSubTag(i, "PCS"); + p.pref = hasSubTag(i, "PREF"); + + p.number = subTagText(i, "NUMBER"); + + if ( p.number.isEmpty() ) // FIXME: Workaround for Psi prior to 0.9 + if ( hasSubTag(i, "VOICE") ) + p.number = subTagText(i, "VOICE"); + + d->phoneList.append ( p ); + } + else if ( tag == "EMAIL" ) { + Email m; + + m.home = hasSubTag(i, "HOME"); + m.work = hasSubTag(i, "WORK"); + m.internet = hasSubTag(i, "INTERNET"); + m.x400 = hasSubTag(i, "X400"); + + m.userid = subTagText(i, "USERID"); + + if ( m.userid.isEmpty() ) // FIXME: Workaround for Psi prior to 0.9 + if ( !i.text().isEmpty() ) + m.userid = i.text().stripWhiteSpace(); + + d->emailList.append ( m ); + } + else if ( tag == "JABBERID" ) + d->jid = i.text().stripWhiteSpace(); + else if ( tag == "MAILER" ) + d->mailer = i.text().stripWhiteSpace(); + else if ( tag == "TZ" ) + d->timezone = i.text().stripWhiteSpace(); + else if ( tag == "GEO" ) { + d->geo.lat = subTagText(i, "LAT"); + d->geo.lon = subTagText(i, "LON"); + } + else if ( tag == "TITLE" ) + d->title = i.text().stripWhiteSpace(); + else if ( tag == "ROLE" ) + d->role = i.text().stripWhiteSpace(); + else if ( tag == "LOGO" ) { + d->logo = Base64::stringToArray( subTagText(i, "BINVAL") ); + d->logoURI = subTagText(i, "EXTVAL"); + } + else if ( tag == "AGENT" ) { + e = findSubTag(i, "VCARD", &found); + if ( found ) { + VCard a; + if ( a.fromXml(e) ) { + if ( !d->agent ) + d->agent = new VCard; + *(d->agent) = a; + } + } + + d->agentURI = subTagText(i, "EXTVAL"); + } + else if ( tag == "ORG" ) { + d->org.name = subTagText(i, "ORGNAME"); + + QDomNode nn = i.firstChild(); + for ( ; !nn.isNull(); nn = nn.nextSibling() ) { + QDomElement ii = nn.toElement(); + if ( ii.isNull() ) + continue; + + if ( ii.tagName().upper() == "ORGUNIT" ) + d->org.unit.append( ii.text().stripWhiteSpace() ); + } + } + else if ( tag == "CATEGORIES") { + QDomNode nn = i.firstChild(); + for ( ; !nn.isNull(); nn = nn.nextSibling() ) { + QDomElement ee = nn.toElement(); + if ( ee.isNull() ) + continue; + + if ( ee.tagName().upper() == "KEYWORD" ) + d->categories << ee.text().stripWhiteSpace(); + } + } + else if ( tag == "NOTE" ) + d->note = i.text().stripWhiteSpace(); + else if ( tag == "PRODID" ) + d->prodId = i.text().stripWhiteSpace(); + else if ( tag == "REV" ) + d->rev = i.text().stripWhiteSpace(); + else if ( tag == "SORT-STRING" ) + d->sortString = i.text().stripWhiteSpace(); + else if ( tag == "SOUND" ) { + d->sound = Base64::stringToArray( subTagText(i, "BINVAL") ); + d->soundURI = subTagText(i, "EXTVAL"); + d->soundPhonetic = subTagText(i, "PHONETIC"); + } + else if ( tag == "UID" ) + d->uid = i.text().stripWhiteSpace(); + else if ( tag == "URL") + d->url = i.text().stripWhiteSpace(); + else if ( tag == "DESC" ) + d->desc = i.text().stripWhiteSpace(); + else if ( tag == "CLASS" ) { + if ( hasSubTag(i, "PUBLIC") ) + d->privacyClass = pcPublic; + else if ( hasSubTag(i, "PRIVATE") ) + d->privacyClass = pcPrivate; + else if ( hasSubTag(i, "CONFIDENTIAL") ) + d->privacyClass = pcConfidential; + } + else if ( tag == "KEY" ) { + // TODO: Justin, please check out this code + e = findSubTag(i, "TYPE", &found); + QString type = "text/plain"; + if ( found ) + type = e.text().stripWhiteSpace(); + + e = findSubTag(i, "CRED", &found ); + if ( !found ) + e = findSubTag(i, "BINVAL", &found); // case for very clever clients ;-) + + if ( found ) + d->key = e.text().utf8(); // FIXME + } + } + + return true; +} + +bool VCard::isEmpty() const +{ + return d->isEmpty(); +} + +// Some constructors + +VCard::Address::Address() +{ + home = work = postal = parcel = dom = intl = pref = false; +} + +VCard::Label::Label() +{ + home = work = postal = parcel = dom = intl = pref = false; +} + +VCard::Phone::Phone() +{ + home = work = voice = fax = pager = msg = cell = video = bbs = modem = isdn = pcs = pref = false; +} + +VCard::Email::Email() +{ + home = work = internet = x400 = false; +} + +VCard::Geo::Geo() +{ +} + +VCard::Org::Org() +{ +} + +// vCard properties... + +const QString &VCard::version() const +{ + return d->version; +} + +void VCard::setVersion(const QString &v) +{ + d->version = v; +} + +const QString &VCard::fullName() const +{ + return d->fullName; +} + +void VCard::setFullName(const QString &n) +{ + d->fullName = n; +} + +const QString &VCard::familyName() const +{ + return d->familyName; +} + +void VCard::setFamilyName(const QString &n) +{ + d->familyName = n; +} + +const QString &VCard::givenName() const +{ + return d->givenName; +} + +void VCard::setGivenName(const QString &n) +{ + d->givenName = n; +} + +const QString &VCard::middleName() const +{ + return d->middleName; +} + +void VCard::setMiddleName(const QString &n) +{ + d->middleName = n; +} + +const QString &VCard::prefixName() const +{ + return d->prefixName; +} + +void VCard::setPrefixName(const QString &p) +{ + d->prefixName = p; +} + +const QString &VCard::suffixName() const +{ + return d->suffixName; +} + +void VCard::setSuffixName(const QString &s) +{ + d->suffixName = s; +} + +const QString &VCard::nickName() const +{ + return d->nickName; +} + +void VCard::setNickName(const QString &n) +{ + d->nickName = n; +} + +const QByteArray &VCard::photo() const +{ + return d->photo; +} + +void VCard::setPhoto(const QByteArray &i) +{ + d->photo = i; +} + +const QString &VCard::photoURI() const +{ + return d->photoURI; +} + +void VCard::setPhotoURI(const QString &p) +{ + d->photoURI = p; +} + +const QDate VCard::bday() const +{ + return QDate::fromString(d->bday); +} + +void VCard::setBday(const QDate &date) +{ + d->bday = date.toString(); +} + +const QString &VCard::bdayStr() const +{ + return d->bday; +} + +void VCard::setBdayStr(const QString &date) +{ + d->bday = date; +} + +const VCard::AddressList &VCard::addressList() const +{ + return d->addressList; +} + +void VCard::setAddressList(const VCard::AddressList &a) +{ + d->addressList = a; +} + +const VCard::LabelList &VCard::labelList() const +{ + return d->labelList; +} + +void VCard::setLabelList(const VCard::LabelList &l) +{ + d->labelList = l; +} + +const VCard::PhoneList &VCard::phoneList() const +{ + return d->phoneList; +} + +void VCard::setPhoneList(const VCard::PhoneList &p) +{ + d->phoneList = p; +} + +const VCard::EmailList &VCard::emailList() const +{ + return d->emailList; +} + +void VCard::setEmailList(const VCard::EmailList &e) +{ + d->emailList = e; +} + +const QString &VCard::jid() const +{ + return d->jid; +} + +void VCard::setJid(const QString &j) +{ + d->jid = j; +} + +const QString &VCard::mailer() const +{ + return d->mailer; +} + +void VCard::setMailer(const QString &m) +{ + d->mailer = m; +} + +const QString &VCard::timezone() const +{ + return d->timezone; +} + +void VCard::setTimezone(const QString &t) +{ + d->timezone = t; +} + +const VCard::Geo &VCard::geo() const +{ + return d->geo; +} + +void VCard::setGeo(const VCard::Geo &g) +{ + d->geo = g; +} + +const QString &VCard::title() const +{ + return d->title; +} + +void VCard::setTitle(const QString &t) +{ + d->title = t; +} + +const QString &VCard::role() const +{ + return d->role; +} + +void VCard::setRole(const QString &r) +{ + d->role = r; +} + +const QByteArray &VCard::logo() const +{ + return d->logo; +} + +void VCard::setLogo(const QByteArray &i) +{ + d->logo = i; +} + +const QString &VCard::logoURI() const +{ + return d->logoURI; +} + +void VCard::setLogoURI(const QString &l) +{ + d->logoURI = l; +} + +const VCard *VCard::agent() const +{ + return d->agent; +} + +void VCard::setAgent(const VCard &v) +{ + if ( !d->agent ) + d->agent = new VCard; + *(d->agent) = v; +} + +const QString VCard::agentURI() const +{ + return d->agentURI; +} + +void VCard::setAgentURI(const QString &a) +{ + d->agentURI = a; +} + +const VCard::Org &VCard::org() const +{ + return d->org; +} + +void VCard::setOrg(const VCard::Org &o) +{ + d->org = o; +} + +const QStringList &VCard::categories() const +{ + return d->categories; +} + +void VCard::setCategories(const QStringList &c) +{ + d->categories = c; +} + +const QString &VCard::note() const +{ + return d->note; +} + +void VCard::setNote(const QString &n) +{ + d->note = n; +} + +const QString &VCard::prodId() const +{ + return d->prodId; +} + +void VCard::setProdId(const QString &p) +{ + d->prodId = p; +} + +const QString &VCard::rev() const +{ + return d->rev; +} + +void VCard::setRev(const QString &r) +{ + d->rev = r; +} + +const QString &VCard::sortString() const +{ + return d->sortString; +} + +void VCard::setSortString(const QString &s) +{ + d->sortString = s; +} + +const QByteArray &VCard::sound() const +{ + return d->sound; +} + +void VCard::setSound(const QByteArray &s) +{ + d->sound = s; +} + +const QString &VCard::soundURI() const +{ + return d->soundURI; +} + +void VCard::setSoundURI(const QString &s) +{ + d->soundURI = s; +} + +const QString &VCard::soundPhonetic() const +{ + return d->soundPhonetic; +} + +void VCard::setSoundPhonetic(const QString &s) +{ + d->soundPhonetic = s; +} + +const QString &VCard::uid() const +{ + return d->uid; +} + +void VCard::setUid(const QString &u) +{ + d->uid = u; +} + +const QString &VCard::url() const +{ + return d->url; +} + +void VCard::setUrl(const QString &u) +{ + d->url = u; +} + +const QString &VCard::desc() const +{ + return d->desc; +} + +void VCard::setDesc(const QString &desc) +{ + d->desc = desc; +} + +const VCard::PrivacyClass &VCard::privacyClass() const +{ + return d->privacyClass; +} + +void VCard::setPrivacyClass(const VCard::PrivacyClass &c) +{ + d->privacyClass = c; +} + +const QByteArray &VCard::key() const +{ + return d->key; +} + +void VCard::setKey(const QByteArray &k) +{ + d->key = k; +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_vcard.h b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_vcard.h new file mode 100644 index 00000000..ae8cc873 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_vcard.h @@ -0,0 +1,284 @@ +/* + * xmpp_vcard.h - classes for handling vCards + * Copyright (C) 2003 Michail Pishchagin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef JABBER_VCARD_H +#define JABBER_VCARD_H + +#include +#include +#include + +#include +#include + +class QDate; + +namespace XMPP +{ + class VCard + { + public: + VCard(); + VCard(const VCard &); + VCard & operator=(const VCard &); + ~VCard(); + + QDomElement toXml(QDomDocument *) const; + bool fromXml(const QDomElement &); + bool isEmpty() const; + + const QString &version() const; + void setVersion(const QString &); + + const QString &fullName() const; + void setFullName(const QString &); + + + const QString &familyName() const; + void setFamilyName(const QString &); + + const QString &givenName() const; + void setGivenName(const QString &); + + const QString &middleName() const; + void setMiddleName(const QString &); + + const QString &prefixName() const; + void setPrefixName(const QString &); + + const QString &suffixName() const; + void setSuffixName(const QString &); + + + const QString &nickName() const; + void setNickName(const QString &); + + + const QByteArray &photo() const; + void setPhoto(const QByteArray &); + + const QString &photoURI() const; + void setPhotoURI(const QString &); + + + const QDate bday() const; + void setBday(const QDate &); + + const QString &bdayStr() const; + void setBdayStr(const QString &); + + + class Address { + public: + Address(); + + bool home; + bool work; + bool postal; + bool parcel; + + bool dom; + bool intl; + + bool pref; + + QString pobox; + QString extaddr; + QString street; + QString locality; + QString region; + QString pcode; + QString country; + }; + typedef QValueList
AddressList; + const AddressList &addressList() const; + void setAddressList(const AddressList &); + + class Label { + public: + Label(); + + bool home; + bool work; + bool postal; + bool parcel; + + bool dom; + bool intl; + + bool pref; + + QStringList lines; + }; + typedef QValueList