From 269fb4bc90b79bebbb8311da0110ccd6803fd0a8 Mon Sep 17 00:00:00 2001 From: "henrike@webrtc.org" Date: Tue, 28 Oct 2014 22:20:11 +0000 Subject: [PATCH] move xmpp and p2p to webrtc Create a copy of talk/xmpp and talk/p2p under webrtc/libjingle/xmpp and webrtc/p2p. Also makes libjingle use those version instead of the one in the talk folder. BUG=3379 Review URL: https://webrtc-codereview.appspot.com/26999004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@7549 4adac7df-926f-26a2-2b94-8c16560cd09d --- talk/app/webrtc/fakeportallocatorfactory.h | 2 +- talk/app/webrtc/jsepicecandidate.h | 2 +- .../webrtc/jsepsessiondescription_unittest.cc | 6 +- .../webrtc/mediastreamsignaling_unittest.cc | 4 +- talk/app/webrtc/peerconnection.cc | 2 +- talk/app/webrtc/peerconnection_unittest.cc | 4 +- talk/app/webrtc/portallocatorfactory.cc | 4 +- talk/app/webrtc/statscollector_unittest.cc | 2 +- talk/app/webrtc/webrtcsdp.cc | 6 +- talk/app/webrtc/webrtcsdp_unittest.cc | 2 +- talk/app/webrtc/webrtcsession.h | 2 +- talk/app/webrtc/webrtcsession_unittest.cc | 8 +- .../webrtc/webrtcsessiondescriptionfactory.h | 2 +- talk/examples/call/call_main.cc | 10 +- talk/examples/call/callclient.cc | 18 +- talk/examples/call/callclient.h | 8 +- talk/examples/call/callclient_unittest.cc | 2 +- talk/examples/call/friendinvitesendtask.cc | 2 +- talk/examples/call/friendinvitesendtask.h | 4 +- talk/examples/call/muc.h | 4 +- talk/examples/call/mucinviterecvtask.cc | 2 +- talk/examples/call/mucinviterecvtask.h | 4 +- talk/examples/call/mucinvitesendtask.cc | 4 +- talk/examples/call/mucinvitesendtask.h | 4 +- talk/examples/call/presencepushtask.cc | 2 +- talk/examples/call/presencepushtask.h | 6 +- talk/examples/login/login_main.cc | 8 +- talk/examples/relayserver/relayserver_main.cc | 2 +- talk/examples/stunserver/stunserver_main.cc | 2 +- talk/examples/turnserver/turnserver_main.cc | 4 +- talk/libjingle.gyp | 163 +- talk/libjingle_examples.gyp | 4 +- talk/libjingle_tests.gyp | 52 +- talk/media/base/fakemediaengine.h | 2 +- .../webrtc/webrtcvoiceengine_unittest.cc | 2 +- talk/p2p/base/asyncstuntcpsocket.cc | 4 +- talk/p2p/base/asyncstuntcpsocket.h | 6 +- talk/p2p/base/asyncstuntcpsocket_unittest.cc | 2 +- talk/p2p/base/basicpacketsocketfactory.cc | 6 +- talk/p2p/base/basicpacketsocketfactory.h | 8 +- talk/p2p/base/candidate.h | 8 +- talk/p2p/base/common.h | 6 +- talk/p2p/base/constants.cc | 2 +- talk/p2p/base/constants.h | 6 +- talk/p2p/base/dtlstransport.h | 10 +- talk/p2p/base/dtlstransportchannel.cc | 4 +- talk/p2p/base/dtlstransportchannel.h | 8 +- .../p2p/base/dtlstransportchannel_unittest.cc | 4 +- talk/p2p/base/fakesession.h | 14 +- talk/p2p/base/p2ptransport.cc | 14 +- talk/p2p/base/p2ptransport.h | 8 +- talk/p2p/base/p2ptransportchannel.cc | 8 +- talk/p2p/base/p2ptransportchannel.h | 18 +- talk/p2p/base/p2ptransportchannel_unittest.cc | 10 +- talk/p2p/base/packetsocketfactory.h | 6 +- talk/p2p/base/parsing.cc | 2 +- talk/p2p/base/parsing.h | 6 +- talk/p2p/base/port.cc | 6 +- talk/p2p/base/port.h | 18 +- talk/p2p/base/port_unittest.cc | 20 +- talk/p2p/base/portallocator.cc | 4 +- talk/p2p/base/portallocator.h | 8 +- talk/p2p/base/portallocatorsessionproxy.cc | 6 +- talk/p2p/base/portallocatorsessionproxy.h | 10 +- .../portallocatorsessionproxy_unittest.cc | 8 +- talk/p2p/base/portinterface.h | 8 +- talk/p2p/base/portproxy.cc | 2 +- talk/p2p/base/portproxy.h | 8 +- talk/p2p/base/pseudotcp.cc | 2 +- talk/p2p/base/pseudotcp.h | 6 +- talk/p2p/base/pseudotcp_unittest.cc | 2 +- talk/p2p/base/rawtransport.cc | 12 +- talk/p2p/base/rawtransport.h | 8 +- talk/p2p/base/rawtransportchannel.cc | 18 +- talk/p2p/base/rawtransportchannel.h | 12 +- talk/p2p/base/relayport.cc | 2 +- talk/p2p/base/relayport.h | 10 +- talk/p2p/base/relayport_unittest.cc | 6 +- talk/p2p/base/relayserver.cc | 2 +- talk/p2p/base/relayserver.h | 10 +- talk/p2p/base/relayserver_unittest.cc | 2 +- talk/p2p/base/session.cc | 20 +- talk/p2p/base/session.h | 20 +- talk/p2p/base/session_unittest.cc | 32 +- talk/p2p/base/sessionclient.h | 8 +- talk/p2p/base/sessiondescription.cc | 2 +- talk/p2p/base/sessiondescription.h | 8 +- talk/p2p/base/sessionid.h | 6 +- talk/p2p/base/sessionmanager.cc | 12 +- talk/p2p/base/sessionmanager.h | 10 +- talk/p2p/base/sessionmessages.cc | 16 +- talk/p2p/base/sessionmessages.h | 14 +- talk/p2p/base/stun.cc | 2 +- talk/p2p/base/stun.h | 6 +- talk/p2p/base/stun_unittest.cc | 2 +- talk/p2p/base/stunport.cc | 8 +- talk/p2p/base/stunport.h | 10 +- talk/p2p/base/stunport_unittest.cc | 6 +- talk/p2p/base/stunrequest.cc | 2 +- talk/p2p/base/stunrequest.h | 8 +- talk/p2p/base/stunrequest_unittest.cc | 2 +- talk/p2p/base/stunserver.cc | 2 +- talk/p2p/base/stunserver.h | 8 +- talk/p2p/base/stunserver_unittest.cc | 2 +- talk/p2p/base/tcpport.cc | 4 +- talk/p2p/base/tcpport.h | 8 +- talk/p2p/base/testrelayserver.h | 8 +- talk/p2p/base/teststunserver.h | 8 +- talk/p2p/base/testturnserver.h | 12 +- talk/p2p/base/transport.cc | 16 +- talk/p2p/base/transport.h | 14 +- talk/p2p/base/transport_unittest.cc | 14 +- talk/p2p/base/transportchannel.cc | 2 +- talk/p2p/base/transportchannel.h | 12 +- talk/p2p/base/transportchannelimpl.h | 10 +- talk/p2p/base/transportchannelproxy.cc | 6 +- talk/p2p/base/transportchannelproxy.h | 8 +- talk/p2p/base/transportdescription.cc | 4 +- talk/p2p/base/transportdescription.h | 10 +- talk/p2p/base/transportdescriptionfactory.cc | 4 +- talk/p2p/base/transportdescriptionfactory.h | 8 +- .../transportdescriptionfactory_unittest.cc | 6 +- talk/p2p/base/transportinfo.h | 12 +- talk/p2p/base/turnport.cc | 6 +- talk/p2p/base/turnport.h | 10 +- talk/p2p/base/turnport_unittest.cc | 12 +- talk/p2p/base/turnserver.cc | 10 +- talk/p2p/base/turnserver.h | 8 +- talk/p2p/base/udpport.h | 8 +- talk/p2p/client/autoportallocator.h | 12 +- talk/p2p/client/basicportallocator.cc | 18 +- talk/p2p/client/basicportallocator.h | 10 +- talk/p2p/client/connectivitychecker.cc | 14 +- talk/p2p/client/connectivitychecker.h | 10 +- .../client/connectivitychecker_unittest.cc | 10 +- talk/p2p/client/fakeportallocator.h | 12 +- talk/p2p/client/httpportallocator.cc | 2 +- talk/p2p/client/httpportallocator.h | 8 +- talk/p2p/client/portallocator_unittest.cc | 18 +- talk/p2p/client/sessionmanagertask.h | 14 +- talk/p2p/client/sessionsendtask.h | 16 +- talk/p2p/client/socketmonitor.cc | 2 +- talk/p2p/client/socketmonitor.h | 8 +- talk/session/media/audiomonitor.h | 2 +- talk/session/media/call.cc | 2 +- talk/session/media/call.h | 6 +- talk/session/media/channel.cc | 2 +- talk/session/media/channel.h | 4 +- talk/session/media/channel_unittest.cc | 2 +- talk/session/media/channelmanager.h | 2 +- talk/session/media/channelmanager_unittest.cc | 2 +- talk/session/media/mediamessages.cc | 4 +- talk/session/media/mediamessages.h | 4 +- talk/session/media/mediamessages_unittest.cc | 2 +- talk/session/media/mediarecorder_unittest.cc | 2 +- talk/session/media/mediasession.cc | 4 +- talk/session/media/mediasession.h | 6 +- talk/session/media/mediasession_unittest.cc | 6 +- talk/session/media/mediasessionclient.cc | 6 +- talk/session/media/mediasessionclient.h | 8 +- .../media/mediasessionclient_unittest.cc | 6 +- talk/session/media/rtcpmuxfilter.h | 2 +- talk/session/media/srtpfilter.h | 2 +- talk/session/media/srtpfilter_unittest.cc | 2 +- talk/session/media/typingmonitor_unittest.cc | 2 +- talk/session/tunnel/pseudotcpchannel.cc | 4 +- talk/session/tunnel/pseudotcpchannel.h | 4 +- .../tunnel/securetunnelsessionclient.cc | 2 +- talk/session/tunnel/tunnelsessionclient.cc | 4 +- talk/session/tunnel/tunnelsessionclient.h | 14 +- .../tunnel/tunnelsessionclient_unittest.cc | 6 +- talk/xmpp/chatroommodule.h | 4 +- talk/xmpp/chatroommoduleimpl.cc | 6 +- talk/xmpp/constants.cc | 4 +- talk/xmpp/constants.h | 2 +- talk/xmpp/discoitemsquerytask.cc | 6 +- talk/xmpp/discoitemsquerytask.h | 2 +- talk/xmpp/fakexmppclient.h | 2 +- talk/xmpp/hangoutpubsubclient.cc | 6 +- talk/xmpp/hangoutpubsubclient.h | 6 +- talk/xmpp/hangoutpubsubclient_unittest.cc | 8 +- talk/xmpp/iqtask.cc | 6 +- talk/xmpp/iqtask.h | 4 +- talk/xmpp/jid.cc | 4 +- talk/xmpp/jid_unittest.cc | 2 +- talk/xmpp/jingleinfotask.cc | 8 +- talk/xmpp/jingleinfotask.h | 6 +- talk/xmpp/module.h | 2 +- talk/xmpp/moduleimpl.cc | 2 +- talk/xmpp/moduleimpl.h | 4 +- talk/xmpp/mucroomconfigtask.cc | 4 +- talk/xmpp/mucroomconfigtask.h | 2 +- talk/xmpp/mucroomconfigtask_unittest.cc | 6 +- talk/xmpp/mucroomdiscoverytask.cc | 4 +- talk/xmpp/mucroomdiscoverytask.h | 2 +- talk/xmpp/mucroomdiscoverytask_unittest.cc | 6 +- talk/xmpp/mucroomlookuptask.cc | 4 +- talk/xmpp/mucroomlookuptask.h | 2 +- talk/xmpp/mucroomlookuptask_unittest.cc | 6 +- talk/xmpp/mucroomuniquehangoutidtask.cc | 4 +- talk/xmpp/mucroomuniquehangoutidtask.h | 2 +- .../mucroomuniquehangoutidtask_unittest.cc | 6 +- talk/xmpp/pingtask.cc | 4 +- talk/xmpp/pingtask.h | 2 +- talk/xmpp/pingtask_unittest.cc | 6 +- talk/xmpp/plainsaslhandler.h | 4 +- talk/xmpp/presenceouttask.cc | 6 +- talk/xmpp/presenceouttask.h | 6 +- talk/xmpp/presencereceivetask.cc | 4 +- talk/xmpp/presencereceivetask.h | 4 +- talk/xmpp/presencestatus.cc | 2 +- talk/xmpp/presencestatus.h | 4 +- talk/xmpp/prexmppauth.h | 2 +- talk/xmpp/pubsub_task.cc | 6 +- talk/xmpp/pubsub_task.h | 4 +- talk/xmpp/pubsubclient.cc | 8 +- talk/xmpp/pubsubclient.h | 4 +- talk/xmpp/pubsubclient_unittest.cc | 8 +- talk/xmpp/pubsubstateclient.cc | 2 +- talk/xmpp/pubsubstateclient.h | 6 +- talk/xmpp/pubsubtasks.cc | 6 +- talk/xmpp/pubsubtasks.h | 4 +- talk/xmpp/pubsubtasks_unittest.cc | 10 +- talk/xmpp/receivetask.cc | 4 +- talk/xmpp/receivetask.h | 2 +- talk/xmpp/rostermodule.h | 2 +- talk/xmpp/rostermodule_unittest.cc | 8 +- talk/xmpp/rostermoduleimpl.cc | 4 +- talk/xmpp/rostermoduleimpl.h | 4 +- talk/xmpp/saslcookiemechanism.h | 4 +- talk/xmpp/saslmechanism.cc | 4 +- talk/xmpp/saslplainmechanism.h | 2 +- talk/xmpp/util_unittest.cc | 4 +- talk/xmpp/util_unittest.h | 2 +- talk/xmpp/xmppauth.cc | 8 +- talk/xmpp/xmppauth.h | 6 +- talk/xmpp/xmppclient.cc | 10 +- talk/xmpp/xmppclient.h | 8 +- talk/xmpp/xmppclientsettings.h | 4 +- talk/xmpp/xmppengine.h | 2 +- talk/xmpp/xmppengine_unittest.cc | 10 +- talk/xmpp/xmppengineimpl.cc | 8 +- talk/xmpp/xmppengineimpl.h | 4 +- talk/xmpp/xmppengineimpl_iq.cc | 4 +- talk/xmpp/xmpplogintask.cc | 10 +- talk/xmpp/xmpplogintask.h | 4 +- talk/xmpp/xmpplogintask_unittest.cc | 10 +- talk/xmpp/xmpppump.cc | 4 +- talk/xmpp/xmpppump.h | 6 +- talk/xmpp/xmppsocket.h | 4 +- talk/xmpp/xmppstanzaparser.cc | 4 +- talk/xmpp/xmppstanzaparser_unittest.cc | 2 +- talk/xmpp/xmpptask.cc | 8 +- talk/xmpp/xmpptask.h | 2 +- talk/xmpp/xmppthread.cc | 6 +- talk/xmpp/xmppthread.h | 8 +- webrtc/build/merge_libs.gyp | 2 + webrtc/libjingle/xmpp/OWNERS | 13 + webrtc/libjingle/xmpp/asyncsocket.h | 72 + webrtc/libjingle/xmpp/chatroommodule.h | 253 ++ .../libjingle/xmpp/chatroommodule_unittest.cc | 280 ++ webrtc/libjingle/xmpp/chatroommoduleimpl.cc | 735 +++++ webrtc/libjingle/xmpp/constants.cc | 614 ++++ webrtc/libjingle/xmpp/constants.h | 551 ++++ webrtc/libjingle/xmpp/discoitemsquerytask.cc | 62 + webrtc/libjingle/xmpp/discoitemsquerytask.h | 65 + webrtc/libjingle/xmpp/fakexmppclient.h | 106 + webrtc/libjingle/xmpp/hangoutpubsubclient.cc | 400 +++ webrtc/libjingle/xmpp/hangoutpubsubclient.h | 178 ++ .../xmpp/hangoutpubsubclient_unittest.cc | 753 +++++ webrtc/libjingle/xmpp/iqtask.cc | 69 + webrtc/libjingle/xmpp/iqtask.h | 48 + webrtc/libjingle/xmpp/jid.cc | 379 +++ webrtc/libjingle/xmpp/jid.h | 81 + webrtc/libjingle/xmpp/jid_unittest.cc | 122 + webrtc/libjingle/xmpp/jingleinfotask.cc | 121 + webrtc/libjingle/xmpp/jingleinfotask.h | 44 + webrtc/libjingle/xmpp/module.h | 35 + webrtc/libjingle/xmpp/moduleimpl.cc | 48 + webrtc/libjingle/xmpp/moduleimpl.h | 76 + webrtc/libjingle/xmpp/mucroomconfigtask.cc | 74 + webrtc/libjingle/xmpp/mucroomconfigtask.h | 47 + .../xmpp/mucroomconfigtask_unittest.cc | 127 + webrtc/libjingle/xmpp/mucroomdiscoverytask.cc | 66 + webrtc/libjingle/xmpp/mucroomdiscoverytask.h | 41 + .../xmpp/mucroomdiscoverytask_unittest.cc | 145 + webrtc/libjingle/xmpp/mucroomlookuptask.cc | 159 ++ webrtc/libjingle/xmpp/mucroomlookuptask.h | 76 + .../xmpp/mucroomlookuptask_unittest.cc | 187 ++ .../xmpp/mucroomuniquehangoutidtask.cc | 51 + .../xmpp/mucroomuniquehangoutidtask.h | 38 + .../mucroomuniquehangoutidtask_unittest.cc | 99 + webrtc/libjingle/xmpp/pingtask.cc | 92 + webrtc/libjingle/xmpp/pingtask.h | 54 + webrtc/libjingle/xmpp/pingtask_unittest.cc | 101 + webrtc/libjingle/xmpp/plainsaslhandler.h | 64 + webrtc/libjingle/xmpp/presenceouttask.cc | 140 + webrtc/libjingle/xmpp/presenceouttask.h | 37 + webrtc/libjingle/xmpp/presencereceivetask.cc | 141 + webrtc/libjingle/xmpp/presencereceivetask.h | 56 + webrtc/libjingle/xmpp/presencestatus.cc | 45 + webrtc/libjingle/xmpp/presencestatus.h | 188 ++ webrtc/libjingle/xmpp/prexmppauth.h | 71 + webrtc/libjingle/xmpp/pubsub_task.cc | 200 ++ webrtc/libjingle/xmpp/pubsub_task.h | 58 + webrtc/libjingle/xmpp/pubsubclient.cc | 129 + webrtc/libjingle/xmpp/pubsubclient.h | 111 + .../libjingle/xmpp/pubsubclient_unittest.cc | 278 ++ webrtc/libjingle/xmpp/pubsubstateclient.cc | 25 + webrtc/libjingle/xmpp/pubsubstateclient.h | 270 ++ webrtc/libjingle/xmpp/pubsubtasks.cc | 204 ++ webrtc/libjingle/xmpp/pubsubtasks.h | 114 + webrtc/libjingle/xmpp/pubsubtasks_unittest.cc | 280 ++ webrtc/libjingle/xmpp/receivetask.cc | 34 + webrtc/libjingle/xmpp/receivetask.h | 41 + webrtc/libjingle/xmpp/rostermodule.h | 331 +++ .../libjingle/xmpp/rostermodule_unittest.cc | 832 ++++++ webrtc/libjingle/xmpp/rostermoduleimpl.cc | 1064 +++++++ webrtc/libjingle/xmpp/rostermoduleimpl.h | 285 ++ webrtc/libjingle/xmpp/saslcookiemechanism.h | 69 + webrtc/libjingle/xmpp/saslhandler.h | 42 + webrtc/libjingle/xmpp/saslmechanism.cc | 55 + webrtc/libjingle/xmpp/saslmechanism.h | 57 + webrtc/libjingle/xmpp/saslplainmechanism.h | 48 + webrtc/libjingle/xmpp/util_unittest.cc | 109 + webrtc/libjingle/xmpp/util_unittest.h | 58 + webrtc/libjingle/xmpp/xmpp.gyp | 141 + webrtc/libjingle/xmpp/xmpp_tests.gypi | 37 + webrtc/libjingle/xmpp/xmppauth.cc | 88 + webrtc/libjingle/xmpp/xmppauth.h | 61 + webrtc/libjingle/xmpp/xmppclient.cc | 424 +++ webrtc/libjingle/xmpp/xmppclient.h | 148 + webrtc/libjingle/xmpp/xmppclientsettings.h | 111 + webrtc/libjingle/xmpp/xmppengine.h | 332 +++ webrtc/libjingle/xmpp/xmppengine_unittest.cc | 325 +++ webrtc/libjingle/xmpp/xmppengineimpl.cc | 446 +++ webrtc/libjingle/xmpp/xmppengineimpl.h | 265 ++ webrtc/libjingle/xmpp/xmppengineimpl_iq.cc | 260 ++ webrtc/libjingle/xmpp/xmpplogintask.cc | 380 +++ webrtc/libjingle/xmpp/xmpplogintask.h | 87 + .../libjingle/xmpp/xmpplogintask_unittest.cc | 621 ++++ webrtc/libjingle/xmpp/xmpppump.cc | 67 + webrtc/libjingle/xmpp/xmpppump.h | 62 + webrtc/libjingle/xmpp/xmppsocket.cc | 245 ++ webrtc/libjingle/xmpp/xmppsocket.h | 72 + webrtc/libjingle/xmpp/xmppstanzaparser.cc | 89 + webrtc/libjingle/xmpp/xmppstanzaparser.h | 80 + .../xmpp/xmppstanzaparser_unittest.cc | 175 ++ webrtc/libjingle/xmpp/xmpptask.cc | 158 ++ webrtc/libjingle/xmpp/xmpptask.h | 172 ++ webrtc/libjingle/xmpp/xmppthread.cc | 69 + webrtc/libjingle/xmpp/xmppthread.h | 45 + webrtc/p2p/OWNERS | 13 + webrtc/p2p/base/asyncstuntcpsocket.cc | 153 + webrtc/p2p/base/asyncstuntcpsocket.h | 50 + .../p2p/base/asyncstuntcpsocket_unittest.cc | 263 ++ webrtc/p2p/base/basicpacketsocketfactory.cc | 204 ++ webrtc/p2p/base/basicpacketsocketfactory.h | 51 + webrtc/p2p/base/candidate.h | 212 ++ webrtc/p2p/base/common.h | 20 + webrtc/p2p/base/constants.cc | 257 ++ webrtc/p2p/base/constants.h | 259 ++ webrtc/p2p/base/dtlstransport.h | 240 ++ webrtc/p2p/base/dtlstransportchannel.cc | 623 ++++ webrtc/p2p/base/dtlstransportchannel.h | 246 ++ .../p2p/base/dtlstransportchannel_unittest.cc | 823 ++++++ webrtc/p2p/base/fakesession.h | 492 ++++ webrtc/p2p/base/p2ptransport.cc | 246 ++ webrtc/p2p/base/p2ptransport.h | 86 + webrtc/p2p/base/p2ptransportchannel.cc | 1274 +++++++++ webrtc/p2p/base/p2ptransportchannel.h | 242 ++ .../p2p/base/p2ptransportchannel_unittest.cc | 1702 +++++++++++ webrtc/p2p/base/packetsocketfactory.h | 52 + webrtc/p2p/base/parsing.cc | 141 + webrtc/p2p/base/parsing.h | 140 + webrtc/p2p/base/port.cc | 1430 ++++++++++ webrtc/p2p/base/port.h | 602 ++++ webrtc/p2p/base/port_unittest.cc | 2494 +++++++++++++++++ webrtc/p2p/base/portallocator.cc | 92 + webrtc/p2p/base/portallocator.h | 192 ++ webrtc/p2p/base/portallocatorsessionproxy.cc | 222 ++ webrtc/p2p/base/portallocatorsessionproxy.h | 106 + .../portallocatorsessionproxy_unittest.cc | 146 + webrtc/p2p/base/portinterface.h | 126 + webrtc/p2p/base/portproxy.cc | 163 ++ webrtc/p2p/base/portproxy.h | 87 + webrtc/p2p/base/pseudotcp.cc | 1274 +++++++++ webrtc/p2p/base/pseudotcp.h | 241 ++ webrtc/p2p/base/pseudotcp_unittest.cc | 841 ++++++ webrtc/p2p/base/rawtransport.cc | 115 + webrtc/p2p/base/rawtransport.h | 64 + webrtc/p2p/base/rawtransportchannel.cc | 260 ++ webrtc/p2p/base/rawtransportchannel.h | 189 ++ webrtc/p2p/base/relayport.cc | 818 ++++++ webrtc/p2p/base/relayport.h | 101 + webrtc/p2p/base/relayport_unittest.cc | 272 ++ webrtc/p2p/base/relayserver.cc | 746 +++++ webrtc/p2p/base/relayserver.h | 235 ++ webrtc/p2p/base/relayserver_unittest.cc | 519 ++++ webrtc/p2p/base/session.cc | 1760 ++++++++++++ webrtc/p2p/base/session.h | 730 +++++ webrtc/p2p/base/session_unittest.cc | 2430 ++++++++++++++++ webrtc/p2p/base/sessionclient.h | 78 + webrtc/p2p/base/sessiondescription.cc | 222 ++ webrtc/p2p/base/sessiondescription.h | 185 ++ webrtc/p2p/base/sessionid.h | 20 + webrtc/p2p/base/sessionmanager.cc | 309 ++ webrtc/p2p/base/sessionmanager.h | 194 ++ webrtc/p2p/base/sessionmessages.cc | 1132 ++++++++ webrtc/p2p/base/sessionmessages.h | 226 ++ webrtc/p2p/base/stun.cc | 915 ++++++ webrtc/p2p/base/stun.h | 632 +++++ webrtc/p2p/base/stun_unittest.cc | 1402 +++++++++ webrtc/p2p/base/stunport.cc | 451 +++ webrtc/p2p/base/stunport.h | 238 ++ webrtc/p2p/base/stunport_unittest.cc | 283 ++ webrtc/p2p/base/stunrequest.cc | 193 ++ webrtc/p2p/base/stunrequest.h | 116 + webrtc/p2p/base/stunrequest_unittest.cc | 203 ++ webrtc/p2p/base/stunserver.cc | 99 + webrtc/p2p/base/stunserver.h | 66 + webrtc/p2p/base/stunserver_unittest.cc | 109 + webrtc/p2p/base/tcpport.cc | 321 +++ webrtc/p2p/base/tcpport.h | 136 + webrtc/p2p/base/testrelayserver.h | 101 + webrtc/p2p/base/teststunserver.h | 58 + webrtc/p2p/base/testturnserver.h | 103 + webrtc/p2p/base/transport.cc | 960 +++++++ webrtc/p2p/base/transport.h | 513 ++++ webrtc/p2p/base/transport_unittest.cc | 438 +++ webrtc/p2p/base/transportchannel.cc | 43 + webrtc/p2p/base/transportchannel.h | 143 + webrtc/p2p/base/transportchannelimpl.h | 111 + webrtc/p2p/base/transportchannelproxy.cc | 249 ++ webrtc/p2p/base/transportchannelproxy.h | 95 + webrtc/p2p/base/transportdescription.cc | 55 + webrtc/p2p/base/transportdescription.h | 171 ++ .../p2p/base/transportdescriptionfactory.cc | 160 ++ webrtc/p2p/base/transportdescriptionfactory.h | 66 + .../transportdescriptionfactory_unittest.cc | 365 +++ webrtc/p2p/base/transportinfo.h | 43 + webrtc/p2p/base/turnport.cc | 1196 ++++++++ webrtc/p2p/base/turnport.h | 237 ++ webrtc/p2p/base/turnport_unittest.cc | 668 +++++ webrtc/p2p/base/turnserver.cc | 1011 +++++++ webrtc/p2p/base/turnserver.h | 190 ++ webrtc/p2p/base/udpport.h | 17 + webrtc/p2p/client/autoportallocator.h | 52 + webrtc/p2p/client/basicportallocator.cc | 1190 ++++++++ webrtc/p2p/client/basicportallocator.h | 241 ++ webrtc/p2p/client/connectivitychecker.cc | 573 ++++ webrtc/p2p/client/connectivitychecker.h | 281 ++ .../client/connectivitychecker_unittest.cc | 375 +++ webrtc/p2p/client/fakeportallocator.h | 121 + webrtc/p2p/client/httpportallocator.cc | 326 +++ webrtc/p2p/client/httpportallocator.h | 173 ++ webrtc/p2p/client/portallocator_unittest.cc | 1066 +++++++ webrtc/p2p/client/sessionmanagertask.h | 76 + webrtc/p2p/client/sessionsendtask.h | 128 + webrtc/p2p/client/socketmonitor.cc | 97 + webrtc/p2p/client/socketmonitor.h | 54 + webrtc/p2p/p2p.gyp | 130 + webrtc/p2p/p2p_tests.gypi | 44 + webrtc/webrtc.gyp | 4 + webrtc/webrtc_tests.gypi | 4 + 465 files changed, 61160 insertions(+), 1005 deletions(-) create mode 100644 webrtc/libjingle/xmpp/OWNERS create mode 100644 webrtc/libjingle/xmpp/asyncsocket.h create mode 100644 webrtc/libjingle/xmpp/chatroommodule.h create mode 100644 webrtc/libjingle/xmpp/chatroommodule_unittest.cc create mode 100644 webrtc/libjingle/xmpp/chatroommoduleimpl.cc create mode 100644 webrtc/libjingle/xmpp/constants.cc create mode 100644 webrtc/libjingle/xmpp/constants.h create mode 100644 webrtc/libjingle/xmpp/discoitemsquerytask.cc create mode 100644 webrtc/libjingle/xmpp/discoitemsquerytask.h create mode 100644 webrtc/libjingle/xmpp/fakexmppclient.h create mode 100644 webrtc/libjingle/xmpp/hangoutpubsubclient.cc create mode 100644 webrtc/libjingle/xmpp/hangoutpubsubclient.h create mode 100644 webrtc/libjingle/xmpp/hangoutpubsubclient_unittest.cc create mode 100644 webrtc/libjingle/xmpp/iqtask.cc create mode 100644 webrtc/libjingle/xmpp/iqtask.h create mode 100644 webrtc/libjingle/xmpp/jid.cc create mode 100644 webrtc/libjingle/xmpp/jid.h create mode 100644 webrtc/libjingle/xmpp/jid_unittest.cc create mode 100644 webrtc/libjingle/xmpp/jingleinfotask.cc create mode 100644 webrtc/libjingle/xmpp/jingleinfotask.h create mode 100644 webrtc/libjingle/xmpp/module.h create mode 100644 webrtc/libjingle/xmpp/moduleimpl.cc create mode 100644 webrtc/libjingle/xmpp/moduleimpl.h create mode 100644 webrtc/libjingle/xmpp/mucroomconfigtask.cc create mode 100644 webrtc/libjingle/xmpp/mucroomconfigtask.h create mode 100644 webrtc/libjingle/xmpp/mucroomconfigtask_unittest.cc create mode 100644 webrtc/libjingle/xmpp/mucroomdiscoverytask.cc create mode 100644 webrtc/libjingle/xmpp/mucroomdiscoverytask.h create mode 100644 webrtc/libjingle/xmpp/mucroomdiscoverytask_unittest.cc create mode 100644 webrtc/libjingle/xmpp/mucroomlookuptask.cc create mode 100644 webrtc/libjingle/xmpp/mucroomlookuptask.h create mode 100644 webrtc/libjingle/xmpp/mucroomlookuptask_unittest.cc create mode 100644 webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.cc create mode 100644 webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.h create mode 100644 webrtc/libjingle/xmpp/mucroomuniquehangoutidtask_unittest.cc create mode 100644 webrtc/libjingle/xmpp/pingtask.cc create mode 100644 webrtc/libjingle/xmpp/pingtask.h create mode 100644 webrtc/libjingle/xmpp/pingtask_unittest.cc create mode 100644 webrtc/libjingle/xmpp/plainsaslhandler.h create mode 100644 webrtc/libjingle/xmpp/presenceouttask.cc create mode 100644 webrtc/libjingle/xmpp/presenceouttask.h create mode 100644 webrtc/libjingle/xmpp/presencereceivetask.cc create mode 100644 webrtc/libjingle/xmpp/presencereceivetask.h create mode 100644 webrtc/libjingle/xmpp/presencestatus.cc create mode 100644 webrtc/libjingle/xmpp/presencestatus.h create mode 100644 webrtc/libjingle/xmpp/prexmppauth.h create mode 100644 webrtc/libjingle/xmpp/pubsub_task.cc create mode 100644 webrtc/libjingle/xmpp/pubsub_task.h create mode 100644 webrtc/libjingle/xmpp/pubsubclient.cc create mode 100644 webrtc/libjingle/xmpp/pubsubclient.h create mode 100644 webrtc/libjingle/xmpp/pubsubclient_unittest.cc create mode 100644 webrtc/libjingle/xmpp/pubsubstateclient.cc create mode 100644 webrtc/libjingle/xmpp/pubsubstateclient.h create mode 100644 webrtc/libjingle/xmpp/pubsubtasks.cc create mode 100644 webrtc/libjingle/xmpp/pubsubtasks.h create mode 100644 webrtc/libjingle/xmpp/pubsubtasks_unittest.cc create mode 100644 webrtc/libjingle/xmpp/receivetask.cc create mode 100644 webrtc/libjingle/xmpp/receivetask.h create mode 100644 webrtc/libjingle/xmpp/rostermodule.h create mode 100644 webrtc/libjingle/xmpp/rostermodule_unittest.cc create mode 100644 webrtc/libjingle/xmpp/rostermoduleimpl.cc create mode 100644 webrtc/libjingle/xmpp/rostermoduleimpl.h create mode 100644 webrtc/libjingle/xmpp/saslcookiemechanism.h create mode 100644 webrtc/libjingle/xmpp/saslhandler.h create mode 100644 webrtc/libjingle/xmpp/saslmechanism.cc create mode 100644 webrtc/libjingle/xmpp/saslmechanism.h create mode 100644 webrtc/libjingle/xmpp/saslplainmechanism.h create mode 100644 webrtc/libjingle/xmpp/util_unittest.cc create mode 100644 webrtc/libjingle/xmpp/util_unittest.h create mode 100644 webrtc/libjingle/xmpp/xmpp.gyp create mode 100644 webrtc/libjingle/xmpp/xmpp_tests.gypi create mode 100644 webrtc/libjingle/xmpp/xmppauth.cc create mode 100644 webrtc/libjingle/xmpp/xmppauth.h create mode 100644 webrtc/libjingle/xmpp/xmppclient.cc create mode 100644 webrtc/libjingle/xmpp/xmppclient.h create mode 100644 webrtc/libjingle/xmpp/xmppclientsettings.h create mode 100644 webrtc/libjingle/xmpp/xmppengine.h create mode 100644 webrtc/libjingle/xmpp/xmppengine_unittest.cc create mode 100644 webrtc/libjingle/xmpp/xmppengineimpl.cc create mode 100644 webrtc/libjingle/xmpp/xmppengineimpl.h create mode 100644 webrtc/libjingle/xmpp/xmppengineimpl_iq.cc create mode 100644 webrtc/libjingle/xmpp/xmpplogintask.cc create mode 100644 webrtc/libjingle/xmpp/xmpplogintask.h create mode 100644 webrtc/libjingle/xmpp/xmpplogintask_unittest.cc create mode 100644 webrtc/libjingle/xmpp/xmpppump.cc create mode 100644 webrtc/libjingle/xmpp/xmpppump.h create mode 100644 webrtc/libjingle/xmpp/xmppsocket.cc create mode 100644 webrtc/libjingle/xmpp/xmppsocket.h create mode 100644 webrtc/libjingle/xmpp/xmppstanzaparser.cc create mode 100644 webrtc/libjingle/xmpp/xmppstanzaparser.h create mode 100644 webrtc/libjingle/xmpp/xmppstanzaparser_unittest.cc create mode 100644 webrtc/libjingle/xmpp/xmpptask.cc create mode 100644 webrtc/libjingle/xmpp/xmpptask.h create mode 100644 webrtc/libjingle/xmpp/xmppthread.cc create mode 100644 webrtc/libjingle/xmpp/xmppthread.h create mode 100644 webrtc/p2p/OWNERS create mode 100644 webrtc/p2p/base/asyncstuntcpsocket.cc create mode 100644 webrtc/p2p/base/asyncstuntcpsocket.h create mode 100644 webrtc/p2p/base/asyncstuntcpsocket_unittest.cc create mode 100644 webrtc/p2p/base/basicpacketsocketfactory.cc create mode 100644 webrtc/p2p/base/basicpacketsocketfactory.h create mode 100644 webrtc/p2p/base/candidate.h create mode 100644 webrtc/p2p/base/common.h create mode 100644 webrtc/p2p/base/constants.cc create mode 100644 webrtc/p2p/base/constants.h create mode 100644 webrtc/p2p/base/dtlstransport.h create mode 100644 webrtc/p2p/base/dtlstransportchannel.cc create mode 100644 webrtc/p2p/base/dtlstransportchannel.h create mode 100644 webrtc/p2p/base/dtlstransportchannel_unittest.cc create mode 100644 webrtc/p2p/base/fakesession.h create mode 100644 webrtc/p2p/base/p2ptransport.cc create mode 100644 webrtc/p2p/base/p2ptransport.h create mode 100644 webrtc/p2p/base/p2ptransportchannel.cc create mode 100644 webrtc/p2p/base/p2ptransportchannel.h create mode 100644 webrtc/p2p/base/p2ptransportchannel_unittest.cc create mode 100644 webrtc/p2p/base/packetsocketfactory.h create mode 100644 webrtc/p2p/base/parsing.cc create mode 100644 webrtc/p2p/base/parsing.h create mode 100644 webrtc/p2p/base/port.cc create mode 100644 webrtc/p2p/base/port.h create mode 100644 webrtc/p2p/base/port_unittest.cc create mode 100644 webrtc/p2p/base/portallocator.cc create mode 100644 webrtc/p2p/base/portallocator.h create mode 100644 webrtc/p2p/base/portallocatorsessionproxy.cc create mode 100644 webrtc/p2p/base/portallocatorsessionproxy.h create mode 100644 webrtc/p2p/base/portallocatorsessionproxy_unittest.cc create mode 100644 webrtc/p2p/base/portinterface.h create mode 100644 webrtc/p2p/base/portproxy.cc create mode 100644 webrtc/p2p/base/portproxy.h create mode 100644 webrtc/p2p/base/pseudotcp.cc create mode 100644 webrtc/p2p/base/pseudotcp.h create mode 100644 webrtc/p2p/base/pseudotcp_unittest.cc create mode 100644 webrtc/p2p/base/rawtransport.cc create mode 100644 webrtc/p2p/base/rawtransport.h create mode 100644 webrtc/p2p/base/rawtransportchannel.cc create mode 100644 webrtc/p2p/base/rawtransportchannel.h create mode 100644 webrtc/p2p/base/relayport.cc create mode 100644 webrtc/p2p/base/relayport.h create mode 100644 webrtc/p2p/base/relayport_unittest.cc create mode 100644 webrtc/p2p/base/relayserver.cc create mode 100644 webrtc/p2p/base/relayserver.h create mode 100644 webrtc/p2p/base/relayserver_unittest.cc create mode 100644 webrtc/p2p/base/session.cc create mode 100644 webrtc/p2p/base/session.h create mode 100644 webrtc/p2p/base/session_unittest.cc create mode 100644 webrtc/p2p/base/sessionclient.h create mode 100644 webrtc/p2p/base/sessiondescription.cc create mode 100644 webrtc/p2p/base/sessiondescription.h create mode 100644 webrtc/p2p/base/sessionid.h create mode 100644 webrtc/p2p/base/sessionmanager.cc create mode 100644 webrtc/p2p/base/sessionmanager.h create mode 100644 webrtc/p2p/base/sessionmessages.cc create mode 100644 webrtc/p2p/base/sessionmessages.h create mode 100644 webrtc/p2p/base/stun.cc create mode 100644 webrtc/p2p/base/stun.h create mode 100644 webrtc/p2p/base/stun_unittest.cc create mode 100644 webrtc/p2p/base/stunport.cc create mode 100644 webrtc/p2p/base/stunport.h create mode 100644 webrtc/p2p/base/stunport_unittest.cc create mode 100644 webrtc/p2p/base/stunrequest.cc create mode 100644 webrtc/p2p/base/stunrequest.h create mode 100644 webrtc/p2p/base/stunrequest_unittest.cc create mode 100644 webrtc/p2p/base/stunserver.cc create mode 100644 webrtc/p2p/base/stunserver.h create mode 100644 webrtc/p2p/base/stunserver_unittest.cc create mode 100644 webrtc/p2p/base/tcpport.cc create mode 100644 webrtc/p2p/base/tcpport.h create mode 100644 webrtc/p2p/base/testrelayserver.h create mode 100644 webrtc/p2p/base/teststunserver.h create mode 100644 webrtc/p2p/base/testturnserver.h create mode 100644 webrtc/p2p/base/transport.cc create mode 100644 webrtc/p2p/base/transport.h create mode 100644 webrtc/p2p/base/transport_unittest.cc create mode 100644 webrtc/p2p/base/transportchannel.cc create mode 100644 webrtc/p2p/base/transportchannel.h create mode 100644 webrtc/p2p/base/transportchannelimpl.h create mode 100644 webrtc/p2p/base/transportchannelproxy.cc create mode 100644 webrtc/p2p/base/transportchannelproxy.h create mode 100644 webrtc/p2p/base/transportdescription.cc create mode 100644 webrtc/p2p/base/transportdescription.h create mode 100644 webrtc/p2p/base/transportdescriptionfactory.cc create mode 100644 webrtc/p2p/base/transportdescriptionfactory.h create mode 100644 webrtc/p2p/base/transportdescriptionfactory_unittest.cc create mode 100644 webrtc/p2p/base/transportinfo.h create mode 100644 webrtc/p2p/base/turnport.cc create mode 100644 webrtc/p2p/base/turnport.h create mode 100644 webrtc/p2p/base/turnport_unittest.cc create mode 100644 webrtc/p2p/base/turnserver.cc create mode 100644 webrtc/p2p/base/turnserver.h create mode 100644 webrtc/p2p/base/udpport.h create mode 100644 webrtc/p2p/client/autoportallocator.h create mode 100644 webrtc/p2p/client/basicportallocator.cc create mode 100644 webrtc/p2p/client/basicportallocator.h create mode 100644 webrtc/p2p/client/connectivitychecker.cc create mode 100644 webrtc/p2p/client/connectivitychecker.h create mode 100644 webrtc/p2p/client/connectivitychecker_unittest.cc create mode 100644 webrtc/p2p/client/fakeportallocator.h create mode 100644 webrtc/p2p/client/httpportallocator.cc create mode 100644 webrtc/p2p/client/httpportallocator.h create mode 100644 webrtc/p2p/client/portallocator_unittest.cc create mode 100644 webrtc/p2p/client/sessionmanagertask.h create mode 100644 webrtc/p2p/client/sessionsendtask.h create mode 100644 webrtc/p2p/client/socketmonitor.cc create mode 100644 webrtc/p2p/client/socketmonitor.h create mode 100644 webrtc/p2p/p2p.gyp create mode 100644 webrtc/p2p/p2p_tests.gypi diff --git a/talk/app/webrtc/fakeportallocatorfactory.h b/talk/app/webrtc/fakeportallocatorfactory.h index eee98b06f..bfdc56ba5 100644 --- a/talk/app/webrtc/fakeportallocatorfactory.h +++ b/talk/app/webrtc/fakeportallocatorfactory.h @@ -32,7 +32,7 @@ #define TALK_APP_WEBRTC_FAKEPORTALLOCATORFACTORY_H_ #include "talk/app/webrtc/peerconnectioninterface.h" -#include "talk/p2p/client/fakeportallocator.h" +#include "webrtc/p2p/client/fakeportallocator.h" namespace webrtc { diff --git a/talk/app/webrtc/jsepicecandidate.h b/talk/app/webrtc/jsepicecandidate.h index 7be420c3a..71ed2c302 100644 --- a/talk/app/webrtc/jsepicecandidate.h +++ b/talk/app/webrtc/jsepicecandidate.h @@ -33,7 +33,7 @@ #include #include "talk/app/webrtc/jsep.h" -#include "talk/p2p/base/candidate.h" +#include "webrtc/p2p/base/candidate.h" #include "webrtc/base/constructormagic.h" namespace webrtc { diff --git a/talk/app/webrtc/jsepsessiondescription_unittest.cc b/talk/app/webrtc/jsepsessiondescription_unittest.cc index 769e34a9a..cf992c07f 100644 --- a/talk/app/webrtc/jsepsessiondescription_unittest.cc +++ b/talk/app/webrtc/jsepsessiondescription_unittest.cc @@ -29,9 +29,9 @@ #include "talk/app/webrtc/jsepicecandidate.h" #include "talk/app/webrtc/jsepsessiondescription.h" -#include "talk/p2p/base/candidate.h" -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/sessiondescription.h" +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/sessiondescription.h" #include "talk/session/media/mediasession.h" #include "webrtc/base/gunit.h" #include "webrtc/base/helpers.h" diff --git a/talk/app/webrtc/mediastreamsignaling_unittest.cc b/talk/app/webrtc/mediastreamsignaling_unittest.cc index 038c67d4a..d7b973486 100644 --- a/talk/app/webrtc/mediastreamsignaling_unittest.cc +++ b/talk/app/webrtc/mediastreamsignaling_unittest.cc @@ -38,8 +38,8 @@ #include "talk/app/webrtc/videotrack.h" #include "talk/media/base/fakemediaengine.h" #include "talk/media/devices/fakedevicemanager.h" -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/sessiondescription.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/sessiondescription.h" #include "talk/session/media/channelmanager.h" #include "webrtc/base/gunit.h" #include "webrtc/base/scoped_ptr.h" diff --git a/talk/app/webrtc/peerconnection.cc b/talk/app/webrtc/peerconnection.cc index f45ce0bf6..479948940 100644 --- a/talk/app/webrtc/peerconnection.cc +++ b/talk/app/webrtc/peerconnection.cc @@ -35,7 +35,7 @@ #include "talk/app/webrtc/mediaconstraintsinterface.h" #include "talk/app/webrtc/mediastreamhandler.h" #include "talk/app/webrtc/streamcollection.h" -#include "talk/p2p/client/basicportallocator.h" +#include "webrtc/p2p/client/basicportallocator.h" #include "talk/session/media/channelmanager.h" #include "webrtc/base/logging.h" #include "webrtc/base/stringencode.h" diff --git a/talk/app/webrtc/peerconnection_unittest.cc b/talk/app/webrtc/peerconnection_unittest.cc index da75e9d1b..ce745de25 100644 --- a/talk/app/webrtc/peerconnection_unittest.cc +++ b/talk/app/webrtc/peerconnection_unittest.cc @@ -46,8 +46,8 @@ #include "talk/app/webrtc/test/mockpeerconnectionobservers.h" #include "talk/app/webrtc/videosourceinterface.h" #include "talk/media/webrtc/fakewebrtcvideoengine.h" -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/sessiondescription.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/sessiondescription.h" #include "talk/session/media/mediasession.h" #include "webrtc/base/gunit.h" #include "webrtc/base/scoped_ptr.h" diff --git a/talk/app/webrtc/portallocatorfactory.cc b/talk/app/webrtc/portallocatorfactory.cc index 946f8ade5..da4c706cf 100644 --- a/talk/app/webrtc/portallocatorfactory.cc +++ b/talk/app/webrtc/portallocatorfactory.cc @@ -27,8 +27,8 @@ #include "talk/app/webrtc/portallocatorfactory.h" -#include "talk/p2p/base/basicpacketsocketfactory.h" -#include "talk/p2p/client/basicportallocator.h" +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/client/basicportallocator.h" #include "webrtc/base/logging.h" #include "webrtc/base/network.h" #include "webrtc/base/thread.h" diff --git a/talk/app/webrtc/statscollector_unittest.cc b/talk/app/webrtc/statscollector_unittest.cc index 2e55af9b5..c573a880f 100644 --- a/talk/app/webrtc/statscollector_unittest.cc +++ b/talk/app/webrtc/statscollector_unittest.cc @@ -35,7 +35,7 @@ #include "talk/app/webrtc/videotrack.h" #include "talk/media/base/fakemediaengine.h" #include "talk/media/devices/fakedevicemanager.h" -#include "talk/p2p/base/fakesession.h" +#include "webrtc/p2p/base/fakesession.h" #include "talk/session/media/channelmanager.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" diff --git a/talk/app/webrtc/webrtcsdp.cc b/talk/app/webrtc/webrtcsdp.cc index daba5d8d1..23b8f3d3a 100644 --- a/talk/app/webrtc/webrtcsdp.cc +++ b/talk/app/webrtc/webrtcsdp.cc @@ -39,9 +39,9 @@ #include "talk/media/base/constants.h" #include "talk/media/base/cryptoparams.h" #include "talk/media/sctp/sctpdataengine.h" -#include "talk/p2p/base/candidate.h" -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/port.h" +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/port.h" #include "talk/session/media/mediasession.h" #include "talk/session/media/mediasessionclient.h" #include "webrtc/base/common.h" diff --git a/talk/app/webrtc/webrtcsdp_unittest.cc b/talk/app/webrtc/webrtcsdp_unittest.cc index 17701a16e..ea590db47 100644 --- a/talk/app/webrtc/webrtcsdp_unittest.cc +++ b/talk/app/webrtc/webrtcsdp_unittest.cc @@ -32,7 +32,7 @@ #include "talk/app/webrtc/jsepsessiondescription.h" #include "talk/app/webrtc/webrtcsdp.h" #include "talk/media/base/constants.h" -#include "talk/p2p/base/constants.h" +#include "webrtc/p2p/base/constants.h" #include "talk/session/media/mediasession.h" #include "webrtc/base/gunit.h" #include "webrtc/base/logging.h" diff --git a/talk/app/webrtc/webrtcsession.h b/talk/app/webrtc/webrtcsession.h index 86ae43599..25e96461b 100644 --- a/talk/app/webrtc/webrtcsession.h +++ b/talk/app/webrtc/webrtcsession.h @@ -36,7 +36,7 @@ #include "talk/app/webrtc/peerconnectioninterface.h" #include "talk/app/webrtc/statstypes.h" #include "talk/media/base/mediachannel.h" -#include "talk/p2p/base/session.h" +#include "webrtc/p2p/base/session.h" #include "talk/session/media/mediasession.h" #include "webrtc/base/sigslot.h" #include "webrtc/base/thread.h" diff --git a/talk/app/webrtc/webrtcsession_unittest.cc b/talk/app/webrtc/webrtcsession_unittest.cc index d3480a09b..e79001a37 100644 --- a/talk/app/webrtc/webrtcsession_unittest.cc +++ b/talk/app/webrtc/webrtcsession_unittest.cc @@ -40,10 +40,10 @@ #include "talk/media/base/fakevideorenderer.h" #include "talk/media/base/mediachannel.h" #include "talk/media/devices/fakedevicemanager.h" -#include "talk/p2p/base/stunserver.h" -#include "talk/p2p/base/teststunserver.h" -#include "talk/p2p/base/testturnserver.h" -#include "talk/p2p/client/basicportallocator.h" +#include "webrtc/p2p/base/stunserver.h" +#include "webrtc/p2p/base/teststunserver.h" +#include "webrtc/p2p/base/testturnserver.h" +#include "webrtc/p2p/client/basicportallocator.h" #include "talk/session/media/channelmanager.h" #include "talk/session/media/mediasession.h" #include "webrtc/base/fakenetwork.h" diff --git a/talk/app/webrtc/webrtcsessiondescriptionfactory.h b/talk/app/webrtc/webrtcsessiondescriptionfactory.h index b8708561f..31f72feb8 100644 --- a/talk/app/webrtc/webrtcsessiondescriptionfactory.h +++ b/talk/app/webrtc/webrtcsessiondescriptionfactory.h @@ -29,7 +29,7 @@ #define TALK_APP_WEBRTC_WEBRTCSESSIONDESCRIPTIONFACTORY_H_ #include "talk/app/webrtc/peerconnectioninterface.h" -#include "talk/p2p/base/transportdescriptionfactory.h" +#include "webrtc/p2p/base/transportdescriptionfactory.h" #include "talk/session/media/mediasession.h" #include "webrtc/base/messagehandler.h" diff --git a/talk/examples/call/call_main.cc b/talk/examples/call/call_main.cc index cb309dc70..638f6f94c 100644 --- a/talk/examples/call/call_main.cc +++ b/talk/examples/call/call_main.cc @@ -41,13 +41,13 @@ #include "talk/examples/call/callclient.h" #include "talk/examples/call/console.h" #include "talk/examples/call/mediaenginefactory.h" -#include "talk/p2p/base/constants.h" +#include "webrtc/p2p/base/constants.h" #include "talk/session/media/mediasessionclient.h" #include "talk/session/media/srtpfilter.h" -#include "talk/xmpp/xmppauth.h" -#include "talk/xmpp/xmppclientsettings.h" -#include "talk/xmpp/xmpppump.h" -#include "talk/xmpp/xmppsocket.h" +#include "webrtc/libjingle/xmpp/xmppauth.h" +#include "webrtc/libjingle/xmpp/xmppclientsettings.h" +#include "webrtc/libjingle/xmpp/xmpppump.h" +#include "webrtc/libjingle/xmpp/xmppsocket.h" #include "webrtc/base/pathutils.h" #include "webrtc/base/ssladapter.h" #include "webrtc/base/stream.h" diff --git a/talk/examples/call/callclient.cc b/talk/examples/call/callclient.cc index b31e24a4e..866133e07 100644 --- a/talk/examples/call/callclient.cc +++ b/talk/examples/call/callclient.cc @@ -53,17 +53,17 @@ #include "talk/media/base/videorenderer.h" #include "talk/media/devices/devicemanager.h" #include "talk/media/devices/videorendererfactory.h" -#include "talk/p2p/base/sessionmanager.h" -#include "talk/p2p/client/basicportallocator.h" -#include "talk/p2p/client/sessionmanagertask.h" +#include "webrtc/p2p/base/sessionmanager.h" +#include "webrtc/p2p/client/basicportallocator.h" +#include "webrtc/p2p/client/sessionmanagertask.h" #include "talk/session/media/mediamessages.h" #include "talk/session/media/mediasessionclient.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/hangoutpubsubclient.h" -#include "talk/xmpp/mucroomconfigtask.h" -#include "talk/xmpp/mucroomlookuptask.h" -#include "talk/xmpp/pingtask.h" -#include "talk/xmpp/presenceouttask.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/hangoutpubsubclient.h" +#include "webrtc/libjingle/xmpp/mucroomconfigtask.h" +#include "webrtc/libjingle/xmpp/mucroomlookuptask.h" +#include "webrtc/libjingle/xmpp/pingtask.h" +#include "webrtc/libjingle/xmpp/presenceouttask.h" namespace { diff --git a/talk/examples/call/callclient.h b/talk/examples/call/callclient.h index 50c584b1b..075dc6efd 100644 --- a/talk/examples/call/callclient.h +++ b/talk/examples/call/callclient.h @@ -34,12 +34,12 @@ #include "talk/examples/call/console.h" #include "talk/media/base/mediachannel.h" -#include "talk/p2p/base/session.h" +#include "webrtc/p2p/base/session.h" #include "talk/session/media/mediamessages.h" #include "talk/session/media/mediasessionclient.h" -#include "talk/xmpp/hangoutpubsubclient.h" -#include "talk/xmpp/presencestatus.h" -#include "talk/xmpp/xmppclient.h" +#include "webrtc/libjingle/xmpp/hangoutpubsubclient.h" +#include "webrtc/libjingle/xmpp/presencestatus.h" +#include "webrtc/libjingle/xmpp/xmppclient.h" #include "webrtc/base/scoped_ptr.h" #include "webrtc/base/sslidentity.h" diff --git a/talk/examples/call/callclient_unittest.cc b/talk/examples/call/callclient_unittest.cc index d11580e13..256853a43 100644 --- a/talk/examples/call/callclient_unittest.cc +++ b/talk/examples/call/callclient_unittest.cc @@ -30,7 +30,7 @@ #include "talk/examples/call/callclient.h" #include "talk/media/base/filemediaengine.h" #include "talk/media/base/mediaengine.h" -#include "talk/xmpp/xmppthread.h" +#include "webrtc/libjingle/xmpp/xmppthread.h" #include "webrtc/base/gunit.h" TEST(CallClientTest, CreateCallClientWithDefaultMediaEngine) { diff --git a/talk/examples/call/friendinvitesendtask.cc b/talk/examples/call/friendinvitesendtask.cc index dae10062e..e2b8ddebd 100644 --- a/talk/examples/call/friendinvitesendtask.cc +++ b/talk/examples/call/friendinvitesendtask.cc @@ -26,7 +26,7 @@ */ #include "talk/examples/call/friendinvitesendtask.h" -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" namespace buzz { diff --git a/talk/examples/call/friendinvitesendtask.h b/talk/examples/call/friendinvitesendtask.h index 625f077f4..292845212 100644 --- a/talk/examples/call/friendinvitesendtask.h +++ b/talk/examples/call/friendinvitesendtask.h @@ -28,8 +28,8 @@ #ifndef _FRIENDINVITESENDTASK_H_ #define _FRIENDINVITESENDTASK_H_ -#include "talk/xmpp/xmppengine.h" -#include "talk/xmpp/xmpptask.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" namespace buzz { diff --git a/talk/examples/call/muc.h b/talk/examples/call/muc.h index 0e937cabb..9be192f5c 100644 --- a/talk/examples/call/muc.h +++ b/talk/examples/call/muc.h @@ -29,8 +29,8 @@ #define _MUC_H_ #include -#include "talk/xmpp/jid.h" -#include "talk/xmpp/presencestatus.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/presencestatus.h" namespace buzz { diff --git a/talk/examples/call/mucinviterecvtask.cc b/talk/examples/call/mucinviterecvtask.cc index e32456fb6..32de8fe8d 100644 --- a/talk/examples/call/mucinviterecvtask.cc +++ b/talk/examples/call/mucinviterecvtask.cc @@ -26,7 +26,7 @@ */ #include "talk/examples/call/mucinviterecvtask.h" -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" namespace buzz { diff --git a/talk/examples/call/mucinviterecvtask.h b/talk/examples/call/mucinviterecvtask.h index ddfd6be85..0466c9485 100644 --- a/talk/examples/call/mucinviterecvtask.h +++ b/talk/examples/call/mucinviterecvtask.h @@ -30,8 +30,8 @@ #include -#include "talk/xmpp/xmppengine.h" -#include "talk/xmpp/xmpptask.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" #include "webrtc/base/sigslot.h" namespace buzz { diff --git a/talk/examples/call/mucinvitesendtask.cc b/talk/examples/call/mucinvitesendtask.cc index d648fef08..2299b78aa 100644 --- a/talk/examples/call/mucinvitesendtask.cc +++ b/talk/examples/call/mucinvitesendtask.cc @@ -26,8 +26,8 @@ */ #include "talk/examples/call/mucinvitesendtask.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/xmppclient.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/xmppclient.h" namespace buzz { diff --git a/talk/examples/call/mucinvitesendtask.h b/talk/examples/call/mucinvitesendtask.h index 3ae74c16f..673efb082 100644 --- a/talk/examples/call/mucinvitesendtask.h +++ b/talk/examples/call/mucinvitesendtask.h @@ -29,8 +29,8 @@ #define _MUCINVITESENDTASK_H_ #include "talk/examples/call/muc.h" -#include "talk/xmpp/xmppengine.h" -#include "talk/xmpp/xmpptask.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" namespace buzz { diff --git a/talk/examples/call/presencepushtask.cc b/talk/examples/call/presencepushtask.cc index b598eb653..8d8dca80e 100644 --- a/talk/examples/call/presencepushtask.cc +++ b/talk/examples/call/presencepushtask.cc @@ -28,7 +28,7 @@ #include "talk/examples/call/presencepushtask.h" #include "talk/examples/call/muc.h" -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" #include "webrtc/base/stringencode.h" diff --git a/talk/examples/call/presencepushtask.h b/talk/examples/call/presencepushtask.h index c13a4f506..e3d3268e2 100644 --- a/talk/examples/call/presencepushtask.h +++ b/talk/examples/call/presencepushtask.h @@ -31,9 +31,9 @@ #include #include "talk/examples/call/callclient.h" -#include "talk/xmpp/presencestatus.h" -#include "talk/xmpp/xmppengine.h" -#include "talk/xmpp/xmpptask.h" +#include "webrtc/libjingle/xmpp/presencestatus.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" #include "webrtc/base/sigslot.h" namespace buzz { diff --git a/talk/examples/login/login_main.cc b/talk/examples/login/login_main.cc index bfe12af68..581c98690 100644 --- a/talk/examples/login/login_main.cc +++ b/talk/examples/login/login_main.cc @@ -29,10 +29,10 @@ #include -#include "talk/xmpp/constants.h" -#include "talk/xmpp/xmppclientsettings.h" -#include "talk/xmpp/xmppengine.h" -#include "talk/xmpp/xmppthread.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/xmppclientsettings.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmppthread.h" #include "webrtc/base/thread.h" int main(int argc, char **argv) { diff --git a/talk/examples/relayserver/relayserver_main.cc b/talk/examples/relayserver/relayserver_main.cc index 1a4ab78f5..5a8bec32c 100644 --- a/talk/examples/relayserver/relayserver_main.cc +++ b/talk/examples/relayserver/relayserver_main.cc @@ -27,7 +27,7 @@ #include // NOLINT -#include "talk/p2p/base/relayserver.h" +#include "webrtc/p2p/base/relayserver.h" #include "webrtc/base/scoped_ptr.h" #include "webrtc/base/thread.h" diff --git a/talk/examples/stunserver/stunserver_main.cc b/talk/examples/stunserver/stunserver_main.cc index f800f316e..3cbed9111 100644 --- a/talk/examples/stunserver/stunserver_main.cc +++ b/talk/examples/stunserver/stunserver_main.cc @@ -31,7 +31,7 @@ #include -#include "talk/p2p/base/stunserver.h" +#include "webrtc/p2p/base/stunserver.h" #include "webrtc/base/thread.h" using namespace cricket; diff --git a/talk/examples/turnserver/turnserver_main.cc b/talk/examples/turnserver/turnserver_main.cc index 607b8cf73..692b8a2c1 100644 --- a/talk/examples/turnserver/turnserver_main.cc +++ b/talk/examples/turnserver/turnserver_main.cc @@ -27,8 +27,8 @@ #include // NOLINT -#include "talk/p2p/base/basicpacketsocketfactory.h" -#include "talk/p2p/base/turnserver.h" +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/turnserver.h" #include "webrtc/base/asyncudpsocket.h" #include "webrtc/base/optionsfile.h" #include "webrtc/base/stringencode.h" diff --git a/talk/libjingle.gyp b/talk/libjingle.gyp index c552b2244..803eaa30f 100755 --- a/talk/libjingle.gyp +++ b/talk/libjingle.gyp @@ -299,7 +299,7 @@ 'targets': [ { 'target_name': 'libjingle', - 'type': 'static_library', + 'type': 'none', 'dependencies': [ '<(DEPTH)/third_party/expat/expat.gyp:expat', '<(DEPTH)/third_party/jsoncpp/jsoncpp.gyp:jsoncpp', @@ -309,81 +309,6 @@ '<(DEPTH)/third_party/expat/expat.gyp:expat', '<(DEPTH)/third_party/jsoncpp/jsoncpp.gyp:jsoncpp', ], - 'sources': [ - 'xmpp/asyncsocket.h', - 'xmpp/chatroommodule.h', - 'xmpp/chatroommoduleimpl.cc', - 'xmpp/constants.cc', - 'xmpp/constants.h', - 'xmpp/discoitemsquerytask.cc', - 'xmpp/discoitemsquerytask.h', - 'xmpp/hangoutpubsubclient.cc', - 'xmpp/hangoutpubsubclient.h', - 'xmpp/iqtask.cc', - 'xmpp/iqtask.h', - 'xmpp/jid.cc', - 'xmpp/jid.h', - 'xmpp/module.h', - 'xmpp/moduleimpl.cc', - 'xmpp/moduleimpl.h', - 'xmpp/mucroomconfigtask.cc', - 'xmpp/mucroomconfigtask.h', - 'xmpp/mucroomdiscoverytask.cc', - 'xmpp/mucroomdiscoverytask.h', - 'xmpp/mucroomlookuptask.cc', - 'xmpp/mucroomlookuptask.h', - 'xmpp/mucroomuniquehangoutidtask.cc', - 'xmpp/mucroomuniquehangoutidtask.h', - 'xmpp/pingtask.cc', - 'xmpp/pingtask.h', - 'xmpp/plainsaslhandler.h', - 'xmpp/presenceouttask.cc', - 'xmpp/presenceouttask.h', - 'xmpp/presencereceivetask.cc', - 'xmpp/presencereceivetask.h', - 'xmpp/presencestatus.cc', - 'xmpp/presencestatus.h', - 'xmpp/prexmppauth.h', - 'xmpp/pubsub_task.cc', - 'xmpp/pubsub_task.h', - 'xmpp/pubsubclient.cc', - 'xmpp/pubsubclient.h', - 'xmpp/pubsubstateclient.cc', - 'xmpp/pubsubstateclient.h', - 'xmpp/pubsubtasks.cc', - 'xmpp/pubsubtasks.h', - 'xmpp/receivetask.cc', - 'xmpp/receivetask.h', - 'xmpp/rostermodule.h', - 'xmpp/rostermoduleimpl.cc', - 'xmpp/rostermoduleimpl.h', - 'xmpp/saslcookiemechanism.h', - 'xmpp/saslhandler.h', - 'xmpp/saslmechanism.cc', - 'xmpp/saslmechanism.h', - 'xmpp/saslplainmechanism.h', - 'xmpp/xmppauth.cc', - 'xmpp/xmppauth.h', - 'xmpp/xmppclient.cc', - 'xmpp/xmppclient.h', - 'xmpp/xmppclientsettings.h', - 'xmpp/xmppengine.h', - 'xmpp/xmppengineimpl.cc', - 'xmpp/xmppengineimpl.h', - 'xmpp/xmppengineimpl_iq.cc', - 'xmpp/xmpplogintask.cc', - 'xmpp/xmpplogintask.h', - 'xmpp/xmpppump.cc', - 'xmpp/xmpppump.h', - 'xmpp/xmppsocket.cc', - 'xmpp/xmppsocket.h', - 'xmpp/xmppstanzaparser.cc', - 'xmpp/xmppstanzaparser.h', - 'xmpp/xmpptask.cc', - 'xmpp/xmpptask.h', - 'xmpp/xmppthread.cc', - 'xmpp/xmppthread.h', - ], }, # target libjingle { 'target_name': 'libjingle_media', @@ -403,6 +328,8 @@ '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers', '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers_default', '<(webrtc_root)/libjingle/xmllite/xmllite.gyp:rtc_xmllite', + '<(webrtc_root)/libjingle/xmpp/xmpp.gyp:rtc_xmpp', + '<(webrtc_root)/p2p/p2p.gyp:rtc_p2p', 'libjingle', ], 'direct_dependent_settings': { @@ -628,90 +555,6 @@ ], }, 'sources': [ - 'p2p/base/asyncstuntcpsocket.cc', - 'p2p/base/asyncstuntcpsocket.h', - 'p2p/base/basicpacketsocketfactory.cc', - 'p2p/base/basicpacketsocketfactory.h', - 'p2p/base/candidate.h', - 'p2p/base/common.h', - 'p2p/base/constants.cc', - 'p2p/base/constants.h', - 'p2p/base/dtlstransportchannel.cc', - 'p2p/base/dtlstransportchannel.h', - 'p2p/base/p2ptransport.cc', - 'p2p/base/p2ptransport.h', - 'p2p/base/p2ptransportchannel.cc', - 'p2p/base/p2ptransportchannel.h', - 'p2p/base/packetsocketfactory.h', - 'p2p/base/parsing.cc', - 'p2p/base/parsing.h', - 'p2p/base/port.cc', - 'p2p/base/port.h', - 'p2p/base/portallocator.cc', - 'p2p/base/portallocator.h', - 'p2p/base/portallocatorsessionproxy.cc', - 'p2p/base/portallocatorsessionproxy.h', - 'p2p/base/portinterface.h', - 'p2p/base/portproxy.cc', - 'p2p/base/portproxy.h', - 'p2p/base/pseudotcp.cc', - 'p2p/base/pseudotcp.h', - 'p2p/base/rawtransport.cc', - 'p2p/base/rawtransport.h', - 'p2p/base/rawtransportchannel.cc', - 'p2p/base/rawtransportchannel.h', - 'p2p/base/relayport.cc', - 'p2p/base/relayport.h', - 'p2p/base/relayserver.cc', - 'p2p/base/relayserver.h', - 'p2p/base/session.cc', - 'p2p/base/session.h', - 'p2p/base/sessionclient.h', - 'p2p/base/sessiondescription.cc', - 'p2p/base/sessiondescription.h', - 'p2p/base/sessionid.h', - 'p2p/base/sessionmanager.cc', - 'p2p/base/sessionmanager.h', - 'p2p/base/sessionmessages.cc', - 'p2p/base/sessionmessages.h', - 'p2p/base/stun.cc', - 'p2p/base/stun.h', - 'p2p/base/stunport.cc', - 'p2p/base/stunport.h', - 'p2p/base/stunrequest.cc', - 'p2p/base/stunrequest.h', - 'p2p/base/stunserver.cc', - 'p2p/base/stunserver.h', - 'p2p/base/tcpport.cc', - 'p2p/base/tcpport.h', - 'p2p/base/transport.cc', - 'p2p/base/transport.h', - 'p2p/base/transportchannel.cc', - 'p2p/base/transportchannel.h', - 'p2p/base/transportchannelimpl.h', - 'p2p/base/transportchannelproxy.cc', - 'p2p/base/transportchannelproxy.h', - 'p2p/base/transportdescription.cc', - 'p2p/base/transportdescription.h', - 'p2p/base/transportdescriptionfactory.cc', - 'p2p/base/transportdescriptionfactory.h', - 'p2p/base/transportinfo.h', - 'p2p/base/turnport.cc', - 'p2p/base/turnport.h', - 'p2p/base/turnserver.cc', - 'p2p/base/turnserver.h', - 'p2p/base/udpport.h', - 'p2p/client/autoportallocator.h', - 'p2p/client/basicportallocator.cc', - 'p2p/client/basicportallocator.h', - 'p2p/client/connectivitychecker.cc', - 'p2p/client/connectivitychecker.h', - 'p2p/client/httpportallocator.cc', - 'p2p/client/httpportallocator.h', - 'p2p/client/sessionmanagertask.h', - 'p2p/client/sessionsendtask.h', - 'p2p/client/socketmonitor.cc', - 'p2p/client/socketmonitor.h', 'session/tunnel/pseudotcpchannel.cc', 'session/tunnel/pseudotcpchannel.h', 'session/tunnel/tunnelsessionclient.cc', diff --git a/talk/libjingle_examples.gyp b/talk/libjingle_examples.gyp index ae1597583..a610146ee 100755 --- a/talk/libjingle_examples.gyp +++ b/talk/libjingle_examples.gyp @@ -39,8 +39,8 @@ 'libjingle.gyp:libjingle_p2p', ], 'sources': [ - 'xmpp/jingleinfotask.cc', - 'xmpp/jingleinfotask.h', + '<(webrtc_root)/libjingle/xmpp/jingleinfotask.cc', + '<(webrtc_root)/libjingle/xmpp/jingleinfotask.h', ], }, # target libjingle_xmpphelp { diff --git a/talk/libjingle_tests.gyp b/talk/libjingle_tests.gyp index 44adec35d..4443f40e1 100755 --- a/talk/libjingle_tests.gyp +++ b/talk/libjingle_tests.gyp @@ -81,21 +81,7 @@ 'libjingle_unittest_main', ], 'sources': [ - 'xmpp/fakexmppclient.h', - 'xmpp/hangoutpubsubclient_unittest.cc', - 'xmpp/jid_unittest.cc', - 'xmpp/mucroomconfigtask_unittest.cc', - 'xmpp/mucroomdiscoverytask_unittest.cc', - 'xmpp/mucroomlookuptask_unittest.cc', - 'xmpp/mucroomuniquehangoutidtask_unittest.cc', - 'xmpp/pingtask_unittest.cc', - 'xmpp/pubsubclient_unittest.cc', - 'xmpp/pubsubtasks_unittest.cc', - 'xmpp/util_unittest.cc', - 'xmpp/util_unittest.h', - 'xmpp/xmppengine_unittest.cc', - 'xmpp/xmpplogintask_unittest.cc', - 'xmpp/xmppstanzaparser_unittest.cc', + '<(DEPTH)/webrtc/test/testsupport/always_passing_unittest.cc', ], # sources }, # target libjingle_unittest { @@ -190,28 +176,6 @@ '<(DEPTH)/third_party/libsrtp/srtp', ], 'sources': [ - 'p2p/base/dtlstransportchannel_unittest.cc', - 'p2p/base/fakesession.h', - 'p2p/base/p2ptransportchannel_unittest.cc', - 'p2p/base/port_unittest.cc', - 'p2p/base/portallocatorsessionproxy_unittest.cc', - 'p2p/base/pseudotcp_unittest.cc', - 'p2p/base/relayport_unittest.cc', - 'p2p/base/relayserver_unittest.cc', - 'p2p/base/session_unittest.cc', - 'p2p/base/stun_unittest.cc', - 'p2p/base/stunport_unittest.cc', - 'p2p/base/stunrequest_unittest.cc', - 'p2p/base/stunserver_unittest.cc', - 'p2p/base/testrelayserver.h', - 'p2p/base/teststunserver.h', - 'p2p/base/testturnserver.h', - 'p2p/base/transport_unittest.cc', - 'p2p/base/transportdescriptionfactory_unittest.cc', - 'p2p/base/turnport_unittest.cc', - 'p2p/client/connectivitychecker_unittest.cc', - 'p2p/client/fakeportallocator.h', - 'p2p/client/portallocator_unittest.cc', 'session/media/bundlefilter_unittest.cc', 'session/media/channel_unittest.cc', 'session/media/channelmanager_unittest.cc', @@ -472,20 +436,6 @@ 'libjingle_peerconnection_unittest.isolate', ], }, - { - 'target_name': 'libjingle_unittest_run', - 'type': 'none', - 'dependencies': [ - 'libjingle_unittest', - ], - 'includes': [ - 'build/isolate.gypi', - 'libjingle_unittest.isolate', - ], - 'sources': [ - 'libjingle_unittest.isolate', - ], - }, ], }], ], diff --git a/talk/media/base/fakemediaengine.h b/talk/media/base/fakemediaengine.h index 87bc8a3ee..db5e2e4e8 100644 --- a/talk/media/base/fakemediaengine.h +++ b/talk/media/base/fakemediaengine.h @@ -38,7 +38,7 @@ #include "talk/media/base/mediaengine.h" #include "talk/media/base/rtputils.h" #include "talk/media/base/streamparams.h" -#include "talk/p2p/base/sessiondescription.h" +#include "webrtc/p2p/base/sessiondescription.h" #include "webrtc/base/buffer.h" #include "webrtc/base/stringutils.h" diff --git a/talk/media/webrtc/webrtcvoiceengine_unittest.cc b/talk/media/webrtc/webrtcvoiceengine_unittest.cc index c5bb20b38..7e38183c8 100644 --- a/talk/media/webrtc/webrtcvoiceengine_unittest.cc +++ b/talk/media/webrtc/webrtcvoiceengine_unittest.cc @@ -40,7 +40,7 @@ #include "talk/media/webrtc/fakewebrtcvoiceengine.h" #include "talk/media/webrtc/webrtcvie.h" #include "talk/media/webrtc/webrtcvoiceengine.h" -#include "talk/p2p/base/fakesession.h" +#include "webrtc/p2p/base/fakesession.h" #include "talk/session/media/channel.h" // Tests for the WebRtcVoiceEngine/VoiceChannel code. diff --git a/talk/p2p/base/asyncstuntcpsocket.cc b/talk/p2p/base/asyncstuntcpsocket.cc index c1aeead26..73bb1520c 100644 --- a/talk/p2p/base/asyncstuntcpsocket.cc +++ b/talk/p2p/base/asyncstuntcpsocket.cc @@ -25,11 +25,11 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/asyncstuntcpsocket.h" +#include "webrtc/p2p/base/asyncstuntcpsocket.h" #include -#include "talk/p2p/base/stun.h" +#include "webrtc/p2p/base/stun.h" #include "webrtc/base/common.h" #include "webrtc/base/logging.h" diff --git a/talk/p2p/base/asyncstuntcpsocket.h b/talk/p2p/base/asyncstuntcpsocket.h index 136b4df4f..3675c9ec1 100644 --- a/talk/p2p/base/asyncstuntcpsocket.h +++ b/talk/p2p/base/asyncstuntcpsocket.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_ASYNCSTUNTCPSOCKET_H_ -#define TALK_P2P_BASE_ASYNCSTUNTCPSOCKET_H_ +#ifndef WEBRTC_P2P_BASE_ASYNCSTUNTCPSOCKET_H_ +#define WEBRTC_P2P_BASE_ASYNCSTUNTCPSOCKET_H_ #include "webrtc/base/asynctcpsocket.h" #include "webrtc/base/scoped_ptr.h" @@ -64,4 +64,4 @@ class AsyncStunTCPSocket : public rtc::AsyncTCPSocketBase { } // namespace cricket -#endif // TALK_P2P_BASE_ASYNCSTUNTCPSOCKET_H_ +#endif // WEBRTC_P2P_BASE_ASYNCSTUNTCPSOCKET_H_ diff --git a/talk/p2p/base/asyncstuntcpsocket_unittest.cc b/talk/p2p/base/asyncstuntcpsocket_unittest.cc index 46e5d63e3..ba407e712 100644 --- a/talk/p2p/base/asyncstuntcpsocket_unittest.cc +++ b/talk/p2p/base/asyncstuntcpsocket_unittest.cc @@ -25,7 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/asyncstuntcpsocket.h" +#include "webrtc/p2p/base/asyncstuntcpsocket.h" #include "webrtc/base/asyncsocket.h" #include "webrtc/base/gunit.h" #include "webrtc/base/physicalsocketserver.h" diff --git a/talk/p2p/base/basicpacketsocketfactory.cc b/talk/p2p/base/basicpacketsocketfactory.cc index 2b3b06f49..1e0ddf8fa 100644 --- a/talk/p2p/base/basicpacketsocketfactory.cc +++ b/talk/p2p/base/basicpacketsocketfactory.cc @@ -25,10 +25,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/basicpacketsocketfactory.h" -#include "talk/p2p/base/asyncstuntcpsocket.h" -#include "talk/p2p/base/stun.h" +#include "webrtc/p2p/base/asyncstuntcpsocket.h" +#include "webrtc/p2p/base/stun.h" #include "webrtc/base/asynctcpsocket.h" #include "webrtc/base/asyncudpsocket.h" #include "webrtc/base/logging.h" diff --git a/talk/p2p/base/basicpacketsocketfactory.h b/talk/p2p/base/basicpacketsocketfactory.h index 77b16524c..f895efad7 100644 --- a/talk/p2p/base/basicpacketsocketfactory.h +++ b/talk/p2p/base/basicpacketsocketfactory.h @@ -25,10 +25,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_BASICPACKETSOCKETFACTORY_H_ -#define TALK_P2P_BASE_BASICPACKETSOCKETFACTORY_H_ +#ifndef WEBRTC_P2P_BASE_BASICPACKETSOCKETFACTORY_H_ +#define WEBRTC_P2P_BASE_BASICPACKETSOCKETFACTORY_H_ -#include "talk/p2p/base/packetsocketfactory.h" +#include "webrtc/p2p/base/packetsocketfactory.h" namespace rtc { @@ -65,4 +65,4 @@ class BasicPacketSocketFactory : public PacketSocketFactory { } // namespace rtc -#endif // TALK_P2P_BASE_BASICPACKETSOCKETFACTORY_H_ +#endif // WEBRTC_P2P_BASE_BASICPACKETSOCKETFACTORY_H_ diff --git a/talk/p2p/base/candidate.h b/talk/p2p/base/candidate.h index cb0b1bcfd..64872f3ce 100644 --- a/talk/p2p/base/candidate.h +++ b/talk/p2p/base/candidate.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_CANDIDATE_H_ -#define TALK_P2P_BASE_CANDIDATE_H_ +#ifndef WEBRTC_P2P_BASE_CANDIDATE_H_ +#define WEBRTC_P2P_BASE_CANDIDATE_H_ #include #include @@ -35,7 +35,7 @@ #include #include -#include "talk/p2p/base/constants.h" +#include "webrtc/p2p/base/constants.h" #include "webrtc/base/basictypes.h" #include "webrtc/base/socketaddress.h" @@ -226,4 +226,4 @@ class Candidate { } // namespace cricket -#endif // TALK_P2P_BASE_CANDIDATE_H_ +#endif // WEBRTC_P2P_BASE_CANDIDATE_H_ diff --git a/talk/p2p/base/common.h b/talk/p2p/base/common.h index a33e9e0bf..0aeb77ed3 100644 --- a/talk/p2p/base/common.h +++ b/talk/p2p/base/common.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_COMMON_H_ -#define TALK_P2P_BASE_COMMON_H_ +#ifndef WEBRTC_P2P_BASE_COMMON_H_ +#define WEBRTC_P2P_BASE_COMMON_H_ #include "webrtc/base/logging.h" @@ -34,4 +34,4 @@ #define LOG_J(sev, obj) LOG(sev) << "Jingle:" << obj->ToString() << ": " #define LOG_JV(sev, obj) LOG_V(sev) << "Jingle:" << obj->ToString() << ": " -#endif // TALK_P2P_BASE_COMMON_H_ +#endif // WEBRTC_P2P_BASE_COMMON_H_ diff --git a/talk/p2p/base/constants.cc b/talk/p2p/base/constants.cc index 278a615bb..54cfb75bf 100644 --- a/talk/p2p/base/constants.cc +++ b/talk/p2p/base/constants.cc @@ -25,7 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/constants.h" +#include "webrtc/p2p/base/constants.h" #include diff --git a/talk/p2p/base/constants.h b/talk/p2p/base/constants.h index 4cd116630..9c53b7d8c 100644 --- a/talk/p2p/base/constants.h +++ b/talk/p2p/base/constants.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_CONSTANTS_H_ -#define TALK_P2P_BASE_CONSTANTS_H_ +#ifndef WEBRTC_P2P_BASE_CONSTANTS_H_ +#define WEBRTC_P2P_BASE_CONSTANTS_H_ #include #include "webrtc/libjingle/xmllite/qname.h" @@ -273,4 +273,4 @@ extern const char CONNECTIONROLE_HOLDCONN_STR[]; } // namespace cricket -#endif // TALK_P2P_BASE_CONSTANTS_H_ +#endif // WEBRTC_P2P_BASE_CONSTANTS_H_ diff --git a/talk/p2p/base/dtlstransport.h b/talk/p2p/base/dtlstransport.h index 318c14af9..66b08b4f9 100644 --- a/talk/p2p/base/dtlstransport.h +++ b/talk/p2p/base/dtlstransport.h @@ -25,11 +25,11 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_DTLSTRANSPORT_H_ -#define TALK_P2P_BASE_DTLSTRANSPORT_H_ +#ifndef WEBRTC_P2P_BASE_DTLSTRANSPORT_H_ +#define WEBRTC_P2P_BASE_DTLSTRANSPORT_H_ -#include "talk/p2p/base/dtlstransportchannel.h" -#include "talk/p2p/base/transport.h" +#include "webrtc/p2p/base/dtlstransportchannel.h" +#include "webrtc/p2p/base/transport.h" namespace rtc { class SSLIdentity; @@ -254,4 +254,4 @@ class DtlsTransport : public Base { } // namespace cricket -#endif // TALK_P2P_BASE_DTLSTRANSPORT_H_ +#endif // WEBRTC_P2P_BASE_DTLSTRANSPORT_H_ diff --git a/talk/p2p/base/dtlstransportchannel.cc b/talk/p2p/base/dtlstransportchannel.cc index e0f414170..78d462202 100644 --- a/talk/p2p/base/dtlstransportchannel.cc +++ b/talk/p2p/base/dtlstransportchannel.cc @@ -26,9 +26,9 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/dtlstransportchannel.h" +#include "webrtc/p2p/base/dtlstransportchannel.h" -#include "talk/p2p/base/common.h" +#include "webrtc/p2p/base/common.h" #include "webrtc/base/buffer.h" #include "webrtc/base/dscp.h" #include "webrtc/base/messagequeue.h" diff --git a/talk/p2p/base/dtlstransportchannel.h b/talk/p2p/base/dtlstransportchannel.h index 44769c9b8..670601328 100644 --- a/talk/p2p/base/dtlstransportchannel.h +++ b/talk/p2p/base/dtlstransportchannel.h @@ -26,13 +26,13 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_DTLSTRANSPORTCHANNEL_H_ -#define TALK_P2P_BASE_DTLSTRANSPORTCHANNEL_H_ +#ifndef WEBRTC_P2P_BASE_DTLSTRANSPORTCHANNEL_H_ +#define WEBRTC_P2P_BASE_DTLSTRANSPORTCHANNEL_H_ #include #include -#include "talk/p2p/base/transportchannelimpl.h" +#include "webrtc/p2p/base/transportchannelimpl.h" #include "webrtc/base/buffer.h" #include "webrtc/base/scoped_ptr.h" #include "webrtc/base/sslstreamadapter.h" @@ -261,4 +261,4 @@ class DtlsTransportChannelWrapper : public TransportChannelImpl { } // namespace cricket -#endif // TALK_P2P_BASE_DTLSTRANSPORTCHANNEL_H_ +#endif // WEBRTC_P2P_BASE_DTLSTRANSPORTCHANNEL_H_ diff --git a/talk/p2p/base/dtlstransportchannel_unittest.cc b/talk/p2p/base/dtlstransportchannel_unittest.cc index 64825bc15..44854c71b 100644 --- a/talk/p2p/base/dtlstransportchannel_unittest.cc +++ b/talk/p2p/base/dtlstransportchannel_unittest.cc @@ -28,8 +28,8 @@ #include -#include "talk/p2p/base/dtlstransport.h" -#include "talk/p2p/base/fakesession.h" +#include "webrtc/p2p/base/dtlstransport.h" +#include "webrtc/p2p/base/fakesession.h" #include "webrtc/base/common.h" #include "webrtc/base/dscp.h" #include "webrtc/base/gunit.h" diff --git a/talk/p2p/base/fakesession.h b/talk/p2p/base/fakesession.h index 7c9971900..cb9111f03 100644 --- a/talk/p2p/base/fakesession.h +++ b/talk/p2p/base/fakesession.h @@ -25,17 +25,17 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_FAKESESSION_H_ -#define TALK_P2P_BASE_FAKESESSION_H_ +#ifndef WEBRTC_P2P_BASE_FAKESESSION_H_ +#define WEBRTC_P2P_BASE_FAKESESSION_H_ #include #include #include -#include "talk/p2p/base/session.h" -#include "talk/p2p/base/transport.h" -#include "talk/p2p/base/transportchannel.h" -#include "talk/p2p/base/transportchannelimpl.h" +#include "webrtc/p2p/base/session.h" +#include "webrtc/p2p/base/transport.h" +#include "webrtc/p2p/base/transportchannel.h" +#include "webrtc/p2p/base/transportchannelimpl.h" #include "webrtc/base/buffer.h" #include "webrtc/base/fakesslidentity.h" #include "webrtc/base/messagequeue.h" @@ -506,4 +506,4 @@ class FakeSession : public BaseSession { } // namespace cricket -#endif // TALK_P2P_BASE_FAKESESSION_H_ +#endif // WEBRTC_P2P_BASE_FAKESESSION_H_ diff --git a/talk/p2p/base/p2ptransport.cc b/talk/p2p/base/p2ptransport.cc index 06941ac6c..bc316cfe9 100644 --- a/talk/p2p/base/p2ptransport.cc +++ b/talk/p2p/base/p2ptransport.cc @@ -25,19 +25,19 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/p2ptransport.h" +#include "webrtc/p2p/base/p2ptransport.h" #include #include -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/p2ptransportchannel.h" -#include "talk/p2p/base/parsing.h" -#include "talk/p2p/base/sessionmanager.h" -#include "talk/p2p/base/sessionmessages.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/p2ptransportchannel.h" +#include "webrtc/p2p/base/parsing.h" +#include "webrtc/p2p/base/sessionmanager.h" +#include "webrtc/p2p/base/sessionmessages.h" #include "webrtc/libjingle/xmllite/qname.h" #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" #include "webrtc/base/base64.h" #include "webrtc/base/common.h" #include "webrtc/base/stringencode.h" diff --git a/talk/p2p/base/p2ptransport.h b/talk/p2p/base/p2ptransport.h index 500bb9bd9..fc65babdf 100644 --- a/talk/p2p/base/p2ptransport.h +++ b/talk/p2p/base/p2ptransport.h @@ -25,12 +25,12 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_P2PTRANSPORT_H_ -#define TALK_P2P_BASE_P2PTRANSPORT_H_ +#ifndef WEBRTC_P2P_BASE_P2PTRANSPORT_H_ +#define WEBRTC_P2P_BASE_P2PTRANSPORT_H_ #include #include -#include "talk/p2p/base/transport.h" +#include "webrtc/p2p/base/transport.h" namespace cricket { @@ -100,4 +100,4 @@ class P2PTransportParser : public TransportParser { } // namespace cricket -#endif // TALK_P2P_BASE_P2PTRANSPORT_H_ +#endif // WEBRTC_P2P_BASE_P2PTRANSPORT_H_ diff --git a/talk/p2p/base/p2ptransportchannel.cc b/talk/p2p/base/p2ptransportchannel.cc index b1dc22349..daedbeb3c 100644 --- a/talk/p2p/base/p2ptransportchannel.cc +++ b/talk/p2p/base/p2ptransportchannel.cc @@ -25,12 +25,12 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/p2ptransportchannel.h" +#include "webrtc/p2p/base/p2ptransportchannel.h" #include -#include "talk/p2p/base/common.h" -#include "talk/p2p/base/relayport.h" // For RELAY_PORT_TYPE. -#include "talk/p2p/base/stunport.h" // For STUN_PORT_TYPE. +#include "webrtc/p2p/base/common.h" +#include "webrtc/p2p/base/relayport.h" // For RELAY_PORT_TYPE. +#include "webrtc/p2p/base/stunport.h" // For STUN_PORT_TYPE. #include "webrtc/base/common.h" #include "webrtc/base/crc32.h" #include "webrtc/base/logging.h" diff --git a/talk/p2p/base/p2ptransportchannel.h b/talk/p2p/base/p2ptransportchannel.h index 229e512ea..6d459996b 100644 --- a/talk/p2p/base/p2ptransportchannel.h +++ b/talk/p2p/base/p2ptransportchannel.h @@ -34,18 +34,18 @@ // When all of the available connections become invalid (non-writable), we // kick off a process of determining more candidates and more connections. // -#ifndef TALK_P2P_BASE_P2PTRANSPORTCHANNEL_H_ -#define TALK_P2P_BASE_P2PTRANSPORTCHANNEL_H_ +#ifndef WEBRTC_P2P_BASE_P2PTRANSPORTCHANNEL_H_ +#define WEBRTC_P2P_BASE_P2PTRANSPORTCHANNEL_H_ #include #include #include -#include "talk/p2p/base/candidate.h" -#include "talk/p2p/base/p2ptransport.h" -#include "talk/p2p/base/portallocator.h" -#include "talk/p2p/base/portinterface.h" -#include "talk/p2p/base/transport.h" -#include "talk/p2p/base/transportchannelimpl.h" +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/p2ptransport.h" +#include "webrtc/p2p/base/portallocator.h" +#include "webrtc/p2p/base/portinterface.h" +#include "webrtc/p2p/base/transport.h" +#include "webrtc/p2p/base/transportchannelimpl.h" #include "webrtc/base/asyncpacketsocket.h" #include "webrtc/base/sigslot.h" @@ -256,4 +256,4 @@ class P2PTransportChannel : public TransportChannelImpl, } // namespace cricket -#endif // TALK_P2P_BASE_P2PTRANSPORTCHANNEL_H_ +#endif // WEBRTC_P2P_BASE_P2PTRANSPORTCHANNEL_H_ diff --git a/talk/p2p/base/p2ptransportchannel_unittest.cc b/talk/p2p/base/p2ptransportchannel_unittest.cc index 44c76ee1a..d7c0b9ffe 100644 --- a/talk/p2p/base/p2ptransportchannel_unittest.cc +++ b/talk/p2p/base/p2ptransportchannel_unittest.cc @@ -25,11 +25,11 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/p2ptransportchannel.h" -#include "talk/p2p/base/testrelayserver.h" -#include "talk/p2p/base/teststunserver.h" -#include "talk/p2p/base/testturnserver.h" -#include "talk/p2p/client/basicportallocator.h" +#include "webrtc/p2p/base/p2ptransportchannel.h" +#include "webrtc/p2p/base/testrelayserver.h" +#include "webrtc/p2p/base/teststunserver.h" +#include "webrtc/p2p/base/testturnserver.h" +#include "webrtc/p2p/client/basicportallocator.h" #include "webrtc/base/dscp.h" #include "webrtc/base/fakenetwork.h" #include "webrtc/base/firewallsocketserver.h" diff --git a/talk/p2p/base/packetsocketfactory.h b/talk/p2p/base/packetsocketfactory.h index 46767c250..0160cacdf 100644 --- a/talk/p2p/base/packetsocketfactory.h +++ b/talk/p2p/base/packetsocketfactory.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_PACKETSOCKETFACTORY_H_ -#define TALK_P2P_BASE_PACKETSOCKETFACTORY_H_ +#ifndef WEBRTC_P2P_BASE_PACKETSOCKETFACTORY_H_ +#define WEBRTC_P2P_BASE_PACKETSOCKETFACTORY_H_ #include "webrtc/base/proxyinfo.h" @@ -66,4 +66,4 @@ class PacketSocketFactory { } // namespace rtc -#endif // TALK_P2P_BASE_PACKETSOCKETFACTORY_H_ +#endif // WEBRTC_P2P_BASE_PACKETSOCKETFACTORY_H_ diff --git a/talk/p2p/base/parsing.cc b/talk/p2p/base/parsing.cc index 146509662..eace88bca 100644 --- a/talk/p2p/base/parsing.cc +++ b/talk/p2p/base/parsing.cc @@ -25,7 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/parsing.h" +#include "webrtc/p2p/base/parsing.h" #include #include diff --git a/talk/p2p/base/parsing.h b/talk/p2p/base/parsing.h index 2aab2f816..f150747aa 100644 --- a/talk/p2p/base/parsing.h +++ b/talk/p2p/base/parsing.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_PARSING_H_ -#define TALK_P2P_BASE_PARSING_H_ +#ifndef WEBRTC_P2P_BASE_PARSING_H_ +#define WEBRTC_P2P_BASE_PARSING_H_ #include #include @@ -154,4 +154,4 @@ std::vector CopyOfXmlChildren(const buzz::XmlElement* elem); } // namespace cricket -#endif // TALK_P2P_BASE_PARSING_H_ +#endif // WEBRTC_P2P_BASE_PARSING_H_ diff --git a/talk/p2p/base/port.cc b/talk/p2p/base/port.cc index 670e1de1f..7ce01b52a 100644 --- a/talk/p2p/base/port.cc +++ b/talk/p2p/base/port.cc @@ -25,13 +25,13 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/port.h" +#include "webrtc/p2p/base/port.h" #include #include -#include "talk/p2p/base/common.h" -#include "talk/p2p/base/portallocator.h" +#include "webrtc/p2p/base/common.h" +#include "webrtc/p2p/base/portallocator.h" #include "webrtc/base/base64.h" #include "webrtc/base/crc32.h" #include "webrtc/base/helpers.h" diff --git a/talk/p2p/base/port.h b/talk/p2p/base/port.h index f0fcb2e92..490aee537 100644 --- a/talk/p2p/base/port.h +++ b/talk/p2p/base/port.h @@ -25,20 +25,20 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_PORT_H_ -#define TALK_P2P_BASE_PORT_H_ +#ifndef WEBRTC_P2P_BASE_PORT_H_ +#define WEBRTC_P2P_BASE_PORT_H_ #include #include #include #include -#include "talk/p2p/base/candidate.h" -#include "talk/p2p/base/packetsocketfactory.h" -#include "talk/p2p/base/portinterface.h" -#include "talk/p2p/base/stun.h" -#include "talk/p2p/base/stunrequest.h" -#include "talk/p2p/base/transport.h" +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/packetsocketfactory.h" +#include "webrtc/p2p/base/portinterface.h" +#include "webrtc/p2p/base/stun.h" +#include "webrtc/p2p/base/stunrequest.h" +#include "webrtc/p2p/base/transport.h" #include "webrtc/base/asyncpacketsocket.h" #include "webrtc/base/network.h" #include "webrtc/base/proxyinfo.h" @@ -616,4 +616,4 @@ class ProxyConnection : public Connection { } // namespace cricket -#endif // TALK_P2P_BASE_PORT_H_ +#endif // WEBRTC_P2P_BASE_PORT_H_ diff --git a/talk/p2p/base/port_unittest.cc b/talk/p2p/base/port_unittest.cc index efa3f658c..bd4520f6a 100644 --- a/talk/p2p/base/port_unittest.cc +++ b/talk/p2p/base/port_unittest.cc @@ -25,16 +25,16 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/basicpacketsocketfactory.h" -#include "talk/p2p/base/portproxy.h" -#include "talk/p2p/base/relayport.h" -#include "talk/p2p/base/stunport.h" -#include "talk/p2p/base/tcpport.h" -#include "talk/p2p/base/testrelayserver.h" -#include "talk/p2p/base/teststunserver.h" -#include "talk/p2p/base/testturnserver.h" -#include "talk/p2p/base/transport.h" -#include "talk/p2p/base/turnport.h" +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/portproxy.h" +#include "webrtc/p2p/base/relayport.h" +#include "webrtc/p2p/base/stunport.h" +#include "webrtc/p2p/base/tcpport.h" +#include "webrtc/p2p/base/testrelayserver.h" +#include "webrtc/p2p/base/teststunserver.h" +#include "webrtc/p2p/base/testturnserver.h" +#include "webrtc/p2p/base/transport.h" +#include "webrtc/p2p/base/turnport.h" #include "webrtc/base/crc32.h" #include "webrtc/base/gunit.h" #include "webrtc/base/helpers.h" diff --git a/talk/p2p/base/portallocator.cc b/talk/p2p/base/portallocator.cc index 369af863d..84fb34480 100644 --- a/talk/p2p/base/portallocator.cc +++ b/talk/p2p/base/portallocator.cc @@ -25,9 +25,9 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/portallocator.h" +#include "webrtc/p2p/base/portallocator.h" -#include "talk/p2p/base/portallocatorsessionproxy.h" +#include "webrtc/p2p/base/portallocatorsessionproxy.h" namespace cricket { diff --git a/talk/p2p/base/portallocator.h b/talk/p2p/base/portallocator.h index 5bc389ed7..29a1b0e09 100644 --- a/talk/p2p/base/portallocator.h +++ b/talk/p2p/base/portallocator.h @@ -25,13 +25,13 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_PORTALLOCATOR_H_ -#define TALK_P2P_BASE_PORTALLOCATOR_H_ +#ifndef WEBRTC_P2P_BASE_PORTALLOCATOR_H_ +#define WEBRTC_P2P_BASE_PORTALLOCATOR_H_ #include #include -#include "talk/p2p/base/portinterface.h" +#include "webrtc/p2p/base/portinterface.h" #include "webrtc/base/helpers.h" #include "webrtc/base/proxyinfo.h" #include "webrtc/base/sigslot.h" @@ -206,4 +206,4 @@ class PortAllocator : public sigslot::has_slots<> { } // namespace cricket -#endif // TALK_P2P_BASE_PORTALLOCATOR_H_ +#endif // WEBRTC_P2P_BASE_PORTALLOCATOR_H_ diff --git a/talk/p2p/base/portallocatorsessionproxy.cc b/talk/p2p/base/portallocatorsessionproxy.cc index a6c80658f..2801f26f0 100644 --- a/talk/p2p/base/portallocatorsessionproxy.cc +++ b/talk/p2p/base/portallocatorsessionproxy.cc @@ -25,10 +25,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/portallocatorsessionproxy.h" +#include "webrtc/p2p/base/portallocatorsessionproxy.h" -#include "talk/p2p/base/portallocator.h" -#include "talk/p2p/base/portproxy.h" +#include "webrtc/p2p/base/portallocator.h" +#include "webrtc/p2p/base/portproxy.h" #include "webrtc/base/thread.h" namespace cricket { diff --git a/talk/p2p/base/portallocatorsessionproxy.h b/talk/p2p/base/portallocatorsessionproxy.h index 659c7301b..fdd5d02b6 100644 --- a/talk/p2p/base/portallocatorsessionproxy.h +++ b/talk/p2p/base/portallocatorsessionproxy.h @@ -25,13 +25,13 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_PORTALLOCATORSESSIONPROXY_H_ -#define TALK_P2P_BASE_PORTALLOCATORSESSIONPROXY_H_ +#ifndef WEBRTC_P2P_BASE_PORTALLOCATORSESSIONPROXY_H_ +#define WEBRTC_P2P_BASE_PORTALLOCATORSESSIONPROXY_H_ #include -#include "talk/p2p/base/candidate.h" -#include "talk/p2p/base/portallocator.h" +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/portallocator.h" namespace cricket { class PortAllocator; @@ -120,4 +120,4 @@ class PortAllocatorSessionProxy : public PortAllocatorSession { } // namespace cricket -#endif // TALK_P2P_BASE_PORTALLOCATORSESSIONPROXY_H_ +#endif // WEBRTC_P2P_BASE_PORTALLOCATORSESSIONPROXY_H_ diff --git a/talk/p2p/base/portallocatorsessionproxy_unittest.cc b/talk/p2p/base/portallocatorsessionproxy_unittest.cc index 3a69b4666..30353ee70 100644 --- a/talk/p2p/base/portallocatorsessionproxy_unittest.cc +++ b/talk/p2p/base/portallocatorsessionproxy_unittest.cc @@ -27,10 +27,10 @@ #include -#include "talk/p2p/base/basicpacketsocketfactory.h" -#include "talk/p2p/base/portallocatorsessionproxy.h" -#include "talk/p2p/client/basicportallocator.h" -#include "talk/p2p/client/fakeportallocator.h" +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/portallocatorsessionproxy.h" +#include "webrtc/p2p/client/basicportallocator.h" +#include "webrtc/p2p/client/fakeportallocator.h" #include "webrtc/base/fakenetwork.h" #include "webrtc/base/gunit.h" #include "webrtc/base/thread.h" diff --git a/talk/p2p/base/portinterface.h b/talk/p2p/base/portinterface.h index 8cab5b41b..d53dc2e8e 100644 --- a/talk/p2p/base/portinterface.h +++ b/talk/p2p/base/portinterface.h @@ -25,12 +25,12 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_PORTINTERFACE_H_ -#define TALK_P2P_BASE_PORTINTERFACE_H_ +#ifndef WEBRTC_P2P_BASE_PORTINTERFACE_H_ +#define WEBRTC_P2P_BASE_PORTINTERFACE_H_ #include -#include "talk/p2p/base/transport.h" +#include "webrtc/p2p/base/transport.h" #include "webrtc/base/socketaddress.h" namespace rtc { @@ -140,4 +140,4 @@ class PortInterface { } // namespace cricket -#endif // TALK_P2P_BASE_PORTINTERFACE_H_ +#endif // WEBRTC_P2P_BASE_PORTINTERFACE_H_ diff --git a/talk/p2p/base/portproxy.cc b/talk/p2p/base/portproxy.cc index 841cd858b..5a5d1ff8a 100644 --- a/talk/p2p/base/portproxy.cc +++ b/talk/p2p/base/portproxy.cc @@ -25,7 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/portproxy.h" +#include "webrtc/p2p/base/portproxy.h" namespace cricket { diff --git a/talk/p2p/base/portproxy.h b/talk/p2p/base/portproxy.h index 8c28223d3..6f79318f1 100644 --- a/talk/p2p/base/portproxy.h +++ b/talk/p2p/base/portproxy.h @@ -25,10 +25,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_PORTPROXY_H_ -#define TALK_P2P_BASE_PORTPROXY_H_ +#ifndef WEBRTC_P2P_BASE_PORTPROXY_H_ +#define WEBRTC_P2P_BASE_PORTPROXY_H_ -#include "talk/p2p/base/portinterface.h" +#include "webrtc/p2p/base/portinterface.h" #include "webrtc/base/sigslot.h" namespace rtc { @@ -101,4 +101,4 @@ class PortProxy : public PortInterface, public sigslot::has_slots<> { } // namespace cricket -#endif // TALK_P2P_BASE_PORTPROXY_H_ +#endif // WEBRTC_P2P_BASE_PORTPROXY_H_ diff --git a/talk/p2p/base/pseudotcp.cc b/talk/p2p/base/pseudotcp.cc index 9a944f06f..a82cd677d 100644 --- a/talk/p2p/base/pseudotcp.cc +++ b/talk/p2p/base/pseudotcp.cc @@ -25,7 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/pseudotcp.h" +#include "webrtc/p2p/base/pseudotcp.h" #include #include diff --git a/talk/p2p/base/pseudotcp.h b/talk/p2p/base/pseudotcp.h index 46e9d3b0f..e3455de71 100644 --- a/talk/p2p/base/pseudotcp.h +++ b/talk/p2p/base/pseudotcp.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_PSEUDOTCP_H_ -#define TALK_P2P_BASE_PSEUDOTCP_H_ +#ifndef WEBRTC_P2P_BASE_PSEUDOTCP_H_ +#define WEBRTC_P2P_BASE_PSEUDOTCP_H_ #include @@ -255,4 +255,4 @@ class PseudoTcp { } // namespace cricket -#endif // TALK_P2P_BASE_PSEUDOTCP_H_ +#endif // WEBRTC_P2P_BASE_PSEUDOTCP_H_ diff --git a/talk/p2p/base/pseudotcp_unittest.cc b/talk/p2p/base/pseudotcp_unittest.cc index d9435cff5..522eb1889 100644 --- a/talk/p2p/base/pseudotcp_unittest.cc +++ b/talk/p2p/base/pseudotcp_unittest.cc @@ -27,7 +27,7 @@ #include -#include "talk/p2p/base/pseudotcp.h" +#include "webrtc/p2p/base/pseudotcp.h" #include "webrtc/base/gunit.h" #include "webrtc/base/helpers.h" #include "webrtc/base/messagehandler.h" diff --git a/talk/p2p/base/rawtransport.cc b/talk/p2p/base/rawtransport.cc index 2af18644c..f363dd104 100644 --- a/talk/p2p/base/rawtransport.cc +++ b/talk/p2p/base/rawtransport.cc @@ -27,14 +27,14 @@ #include #include -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/parsing.h" -#include "talk/p2p/base/rawtransport.h" -#include "talk/p2p/base/rawtransportchannel.h" -#include "talk/p2p/base/sessionmanager.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/parsing.h" +#include "webrtc/p2p/base/rawtransport.h" +#include "webrtc/p2p/base/rawtransportchannel.h" +#include "webrtc/p2p/base/sessionmanager.h" #include "webrtc/libjingle/xmllite/qname.h" #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" #include "webrtc/base/common.h" #if defined(FEATURE_ENABLE_PSTN) diff --git a/talk/p2p/base/rawtransport.h b/talk/p2p/base/rawtransport.h index 3a20ef56c..7458191b6 100644 --- a/talk/p2p/base/rawtransport.h +++ b/talk/p2p/base/rawtransport.h @@ -25,11 +25,11 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_RAWTRANSPORT_H_ -#define TALK_P2P_BASE_RAWTRANSPORT_H_ +#ifndef WEBRTC_P2P_BASE_RAWTRANSPORT_H_ +#define WEBRTC_P2P_BASE_RAWTRANSPORT_H_ #include -#include "talk/p2p/base/transport.h" +#include "webrtc/p2p/base/transport.h" #if defined(FEATURE_ENABLE_PSTN) namespace cricket { @@ -78,4 +78,4 @@ class RawTransport : public Transport, public TransportParser { #endif // defined(FEATURE_ENABLE_PSTN) -#endif // TALK_P2P_BASE_RAWTRANSPORT_H_ +#endif // WEBRTC_P2P_BASE_RAWTRANSPORT_H_ diff --git a/talk/p2p/base/rawtransportchannel.cc b/talk/p2p/base/rawtransportchannel.cc index ae268e35e..ef2286b6a 100644 --- a/talk/p2p/base/rawtransportchannel.cc +++ b/talk/p2p/base/rawtransportchannel.cc @@ -25,20 +25,20 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/rawtransportchannel.h" +#include "webrtc/p2p/base/rawtransportchannel.h" #include #include -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/portallocator.h" -#include "talk/p2p/base/portinterface.h" -#include "talk/p2p/base/rawtransport.h" -#include "talk/p2p/base/relayport.h" -#include "talk/p2p/base/sessionmanager.h" -#include "talk/p2p/base/stunport.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/portallocator.h" +#include "webrtc/p2p/base/portinterface.h" +#include "webrtc/p2p/base/rawtransport.h" +#include "webrtc/p2p/base/relayport.h" +#include "webrtc/p2p/base/sessionmanager.h" +#include "webrtc/p2p/base/stunport.h" #include "webrtc/libjingle/xmllite/qname.h" #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" #include "webrtc/base/common.h" #if defined(FEATURE_ENABLE_PSTN) diff --git a/talk/p2p/base/rawtransportchannel.h b/talk/p2p/base/rawtransportchannel.h index 8fbf073a8..25f6e4f3d 100644 --- a/talk/p2p/base/rawtransportchannel.h +++ b/talk/p2p/base/rawtransportchannel.h @@ -25,14 +25,14 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_RAWTRANSPORTCHANNEL_H_ -#define TALK_P2P_BASE_RAWTRANSPORTCHANNEL_H_ +#ifndef WEBRTC_P2P_BASE_RAWTRANSPORTCHANNEL_H_ +#define WEBRTC_P2P_BASE_RAWTRANSPORTCHANNEL_H_ #include #include -#include "talk/p2p/base/candidate.h" -#include "talk/p2p/base/rawtransport.h" -#include "talk/p2p/base/transportchannelimpl.h" +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/rawtransport.h" +#include "webrtc/p2p/base/transportchannelimpl.h" #include "webrtc/base/messagequeue.h" #if defined(FEATURE_ENABLE_PSTN) @@ -203,4 +203,4 @@ class RawTransportChannel : public TransportChannelImpl, } // namespace cricket #endif // defined(FEATURE_ENABLE_PSTN) -#endif // TALK_P2P_BASE_RAWTRANSPORTCHANNEL_H_ +#endif // WEBRTC_P2P_BASE_RAWTRANSPORTCHANNEL_H_ diff --git a/talk/p2p/base/relayport.cc b/talk/p2p/base/relayport.cc index 55052a589..2db42c97e 100644 --- a/talk/p2p/base/relayport.cc +++ b/talk/p2p/base/relayport.cc @@ -25,7 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/relayport.h" +#include "webrtc/p2p/base/relayport.h" #include "webrtc/base/asyncpacketsocket.h" #include "webrtc/base/helpers.h" #include "webrtc/base/logging.h" diff --git a/talk/p2p/base/relayport.h b/talk/p2p/base/relayport.h index f22d04585..bc0c5d98c 100644 --- a/talk/p2p/base/relayport.h +++ b/talk/p2p/base/relayport.h @@ -25,16 +25,16 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_RELAYPORT_H_ -#define TALK_P2P_BASE_RELAYPORT_H_ +#ifndef WEBRTC_P2P_BASE_RELAYPORT_H_ +#define WEBRTC_P2P_BASE_RELAYPORT_H_ #include #include #include #include -#include "talk/p2p/base/port.h" -#include "talk/p2p/base/stunrequest.h" +#include "webrtc/p2p/base/port.h" +#include "webrtc/p2p/base/stunrequest.h" namespace cricket { @@ -115,4 +115,4 @@ class RelayPort : public Port { } // namespace cricket -#endif // TALK_P2P_BASE_RELAYPORT_H_ +#endif // WEBRTC_P2P_BASE_RELAYPORT_H_ diff --git a/talk/p2p/base/relayport_unittest.cc b/talk/p2p/base/relayport_unittest.cc index ebb16c5db..2f4515e44 100644 --- a/talk/p2p/base/relayport_unittest.cc +++ b/talk/p2p/base/relayport_unittest.cc @@ -25,9 +25,9 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/basicpacketsocketfactory.h" -#include "talk/p2p/base/relayport.h" -#include "talk/p2p/base/relayserver.h" +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/relayport.h" +#include "webrtc/p2p/base/relayserver.h" #include "webrtc/base/gunit.h" #include "webrtc/base/helpers.h" #include "webrtc/base/logging.h" diff --git a/talk/p2p/base/relayserver.cc b/talk/p2p/base/relayserver.cc index b5d1ac6e0..ebf165bff 100644 --- a/talk/p2p/base/relayserver.cc +++ b/talk/p2p/base/relayserver.cc @@ -25,7 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/relayserver.h" +#include "webrtc/p2p/base/relayserver.h" #ifdef POSIX #include diff --git a/talk/p2p/base/relayserver.h b/talk/p2p/base/relayserver.h index 248d0e7c1..8065174ac 100644 --- a/talk/p2p/base/relayserver.h +++ b/talk/p2p/base/relayserver.h @@ -25,15 +25,15 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_RELAYSERVER_H_ -#define TALK_P2P_BASE_RELAYSERVER_H_ +#ifndef WEBRTC_P2P_BASE_RELAYSERVER_H_ +#define WEBRTC_P2P_BASE_RELAYSERVER_H_ #include #include #include -#include "talk/p2p/base/port.h" -#include "talk/p2p/base/stun.h" +#include "webrtc/p2p/base/port.h" +#include "webrtc/p2p/base/stun.h" #include "webrtc/base/asyncudpsocket.h" #include "webrtc/base/socketaddresspair.h" #include "webrtc/base/thread.h" @@ -249,4 +249,4 @@ class RelayServerBinding : public rtc::MessageHandler { } // namespace cricket -#endif // TALK_P2P_BASE_RELAYSERVER_H_ +#endif // WEBRTC_P2P_BASE_RELAYSERVER_H_ diff --git a/talk/p2p/base/relayserver_unittest.cc b/talk/p2p/base/relayserver_unittest.cc index 5d77ca6a9..5ea5e100c 100644 --- a/talk/p2p/base/relayserver_unittest.cc +++ b/talk/p2p/base/relayserver_unittest.cc @@ -27,7 +27,7 @@ #include -#include "talk/p2p/base/relayserver.h" +#include "webrtc/p2p/base/relayserver.h" #include "webrtc/base/gunit.h" #include "webrtc/base/helpers.h" #include "webrtc/base/logging.h" diff --git a/talk/p2p/base/session.cc b/talk/p2p/base/session.cc index a9bf81579..c4db1cf02 100644 --- a/talk/p2p/base/session.cc +++ b/talk/p2p/base/session.cc @@ -25,16 +25,16 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/session.h" +#include "webrtc/p2p/base/session.h" -#include "talk/p2p/base/dtlstransport.h" -#include "talk/p2p/base/p2ptransport.h" -#include "talk/p2p/base/sessionclient.h" -#include "talk/p2p/base/transport.h" -#include "talk/p2p/base/transportchannelproxy.h" -#include "talk/p2p/base/transportinfo.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/jid.h" +#include "webrtc/p2p/base/dtlstransport.h" +#include "webrtc/p2p/base/p2ptransport.h" +#include "webrtc/p2p/base/sessionclient.h" +#include "webrtc/p2p/base/transport.h" +#include "webrtc/p2p/base/transportchannelproxy.h" +#include "webrtc/p2p/base/transportinfo.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/jid.h" #include "webrtc/base/bind.h" #include "webrtc/base/common.h" #include "webrtc/base/helpers.h" @@ -42,7 +42,7 @@ #include "webrtc/base/scoped_ptr.h" #include "webrtc/base/sslstreamadapter.h" -#include "talk/p2p/base/constants.h" +#include "webrtc/p2p/base/constants.h" namespace cricket { diff --git a/talk/p2p/base/session.h b/talk/p2p/base/session.h index e06cf00c1..cbe350f01 100644 --- a/talk/p2p/base/session.h +++ b/talk/p2p/base/session.h @@ -25,22 +25,22 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_SESSION_H_ -#define TALK_P2P_BASE_SESSION_H_ +#ifndef WEBRTC_P2P_BASE_SESSION_H_ +#define WEBRTC_P2P_BASE_SESSION_H_ #include #include #include #include -#include "talk/p2p/base/parsing.h" -#include "talk/p2p/base/port.h" -#include "talk/p2p/base/sessionclient.h" -#include "talk/p2p/base/sessionmanager.h" -#include "talk/p2p/base/sessionmessages.h" -#include "talk/p2p/base/transport.h" +#include "webrtc/p2p/base/parsing.h" +#include "webrtc/p2p/base/port.h" +#include "webrtc/p2p/base/sessionclient.h" +#include "webrtc/p2p/base/sessionmanager.h" +#include "webrtc/p2p/base/sessionmessages.h" +#include "webrtc/p2p/base/transport.h" #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" #include "webrtc/base/refcount.h" #include "webrtc/base/scoped_ptr.h" #include "webrtc/base/scoped_ref_ptr.h" @@ -744,4 +744,4 @@ class Session : public BaseSession { } // namespace cricket -#endif // TALK_P2P_BASE_SESSION_H_ +#endif // WEBRTC_P2P_BASE_SESSION_H_ diff --git a/talk/p2p/base/session_unittest.cc b/talk/p2p/base/session_unittest.cc index 4674d2c23..c7a3db9cd 100644 --- a/talk/p2p/base/session_unittest.cc +++ b/talk/p2p/base/session_unittest.cc @@ -31,22 +31,22 @@ #include #include -#include "talk/p2p/base/basicpacketsocketfactory.h" -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/p2ptransport.h" -#include "talk/p2p/base/parsing.h" -#include "talk/p2p/base/portallocator.h" -#include "talk/p2p/base/relayport.h" -#include "talk/p2p/base/relayserver.h" -#include "talk/p2p/base/session.h" -#include "talk/p2p/base/sessionclient.h" -#include "talk/p2p/base/sessionmanager.h" -#include "talk/p2p/base/stunport.h" -#include "talk/p2p/base/stunserver.h" -#include "talk/p2p/base/transportchannel.h" -#include "talk/p2p/base/transportchannelproxy.h" -#include "talk/p2p/base/udpport.h" -#include "talk/xmpp/constants.h" +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/p2ptransport.h" +#include "webrtc/p2p/base/parsing.h" +#include "webrtc/p2p/base/portallocator.h" +#include "webrtc/p2p/base/relayport.h" +#include "webrtc/p2p/base/relayserver.h" +#include "webrtc/p2p/base/session.h" +#include "webrtc/p2p/base/sessionclient.h" +#include "webrtc/p2p/base/sessionmanager.h" +#include "webrtc/p2p/base/stunport.h" +#include "webrtc/p2p/base/stunserver.h" +#include "webrtc/p2p/base/transportchannel.h" +#include "webrtc/p2p/base/transportchannelproxy.h" +#include "webrtc/p2p/base/udpport.h" +#include "webrtc/libjingle/xmpp/constants.h" #include "webrtc/base/base64.h" #include "webrtc/base/common.h" #include "webrtc/base/gunit.h" diff --git a/talk/p2p/base/sessionclient.h b/talk/p2p/base/sessionclient.h index 10b0c9236..f7952f052 100644 --- a/talk/p2p/base/sessionclient.h +++ b/talk/p2p/base/sessionclient.h @@ -25,10 +25,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_SESSIONCLIENT_H_ -#define TALK_P2P_BASE_SESSIONCLIENT_H_ +#ifndef WEBRTC_P2P_BASE_SESSIONCLIENT_H_ +#define WEBRTC_P2P_BASE_SESSIONCLIENT_H_ -#include "talk/p2p/base/constants.h" +#include "webrtc/p2p/base/constants.h" namespace buzz { class XmlElement; @@ -92,4 +92,4 @@ class SessionClient : public ContentParser { } // namespace cricket -#endif // TALK_P2P_BASE_SESSIONCLIENT_H_ +#endif // WEBRTC_P2P_BASE_SESSIONCLIENT_H_ diff --git a/talk/p2p/base/sessiondescription.cc b/talk/p2p/base/sessiondescription.cc index 7ad3d4890..57f08c9c1 100644 --- a/talk/p2p/base/sessiondescription.cc +++ b/talk/p2p/base/sessiondescription.cc @@ -25,7 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/sessiondescription.h" +#include "webrtc/p2p/base/sessiondescription.h" #include "webrtc/libjingle/xmllite/xmlelement.h" diff --git a/talk/p2p/base/sessiondescription.h b/talk/p2p/base/sessiondescription.h index 56dd412b7..bc76761e0 100644 --- a/talk/p2p/base/sessiondescription.h +++ b/talk/p2p/base/sessiondescription.h @@ -25,13 +25,13 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_SESSIONDESCRIPTION_H_ -#define TALK_P2P_BASE_SESSIONDESCRIPTION_H_ +#ifndef WEBRTC_P2P_BASE_SESSIONDESCRIPTION_H_ +#define WEBRTC_P2P_BASE_SESSIONDESCRIPTION_H_ #include #include -#include "talk/p2p/base/transportinfo.h" +#include "webrtc/p2p/base/transportinfo.h" #include "webrtc/base/constructormagic.h" namespace cricket { @@ -199,4 +199,4 @@ enum ContentSource { } // namespace cricket -#endif // TALK_P2P_BASE_SESSIONDESCRIPTION_H_ +#endif // WEBRTC_P2P_BASE_SESSIONDESCRIPTION_H_ diff --git a/talk/p2p/base/sessionid.h b/talk/p2p/base/sessionid.h index 6942942fb..52ee99061 100644 --- a/talk/p2p/base/sessionid.h +++ b/talk/p2p/base/sessionid.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_SESSIONID_H_ -#define TALK_P2P_BASE_SESSIONID_H_ +#ifndef WEBRTC_P2P_BASE_SESSIONID_H_ +#define WEBRTC_P2P_BASE_SESSIONID_H_ // TODO: Remove this file. @@ -34,4 +34,4 @@ namespace cricket { } // namespace cricket -#endif // TALK_P2P_BASE_SESSIONID_H_ +#endif // WEBRTC_P2P_BASE_SESSIONID_H_ diff --git a/talk/p2p/base/sessionmanager.cc b/talk/p2p/base/sessionmanager.cc index d17351785..7f27f6cd1 100644 --- a/talk/p2p/base/sessionmanager.cc +++ b/talk/p2p/base/sessionmanager.cc @@ -25,13 +25,13 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/sessionmanager.h" +#include "webrtc/p2p/base/sessionmanager.h" -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/session.h" -#include "talk/p2p/base/sessionmessages.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/jid.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/session.h" +#include "webrtc/p2p/base/sessionmessages.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/jid.h" #include "webrtc/base/common.h" #include "webrtc/base/helpers.h" #include "webrtc/base/logging.h" diff --git a/talk/p2p/base/sessionmanager.h b/talk/p2p/base/sessionmanager.h index 92aeb3d06..0e0e9a43a 100644 --- a/talk/p2p/base/sessionmanager.h +++ b/talk/p2p/base/sessionmanager.h @@ -25,16 +25,16 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_SESSIONMANAGER_H_ -#define TALK_P2P_BASE_SESSIONMANAGER_H_ +#ifndef WEBRTC_P2P_BASE_SESSIONMANAGER_H_ +#define WEBRTC_P2P_BASE_SESSIONMANAGER_H_ #include #include #include #include -#include "talk/p2p/base/portallocator.h" -#include "talk/p2p/base/transportdescriptionfactory.h" +#include "webrtc/p2p/base/portallocator.h" +#include "webrtc/p2p/base/transportdescriptionfactory.h" #include "webrtc/base/sigslot.h" #include "webrtc/base/thread.h" @@ -208,4 +208,4 @@ class SessionManager : public sigslot::has_slots<> { } // namespace cricket -#endif // TALK_P2P_BASE_SESSIONMANAGER_H_ +#endif // WEBRTC_P2P_BASE_SESSIONMANAGER_H_ diff --git a/talk/p2p/base/sessionmessages.cc b/talk/p2p/base/sessionmessages.cc index b2fd9d60d..0534271d7 100644 --- a/talk/p2p/base/sessionmessages.cc +++ b/talk/p2p/base/sessionmessages.cc @@ -25,19 +25,19 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/sessionmessages.h" +#include "webrtc/p2p/base/sessionmessages.h" #include #include -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/p2ptransport.h" -#include "talk/p2p/base/parsing.h" -#include "talk/p2p/base/sessionclient.h" -#include "talk/p2p/base/sessiondescription.h" -#include "talk/p2p/base/transport.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/p2ptransport.h" +#include "webrtc/p2p/base/parsing.h" +#include "webrtc/p2p/base/sessionclient.h" +#include "webrtc/p2p/base/sessiondescription.h" +#include "webrtc/p2p/base/transport.h" #include "webrtc/libjingle/xmllite/xmlconstants.h" -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" #include "webrtc/base/logging.h" #include "webrtc/base/scoped_ptr.h" #include "webrtc/base/stringutils.h" diff --git a/talk/p2p/base/sessionmessages.h b/talk/p2p/base/sessionmessages.h index 7e1f8ac3a..0ab56ce3d 100644 --- a/talk/p2p/base/sessionmessages.h +++ b/talk/p2p/base/sessionmessages.h @@ -25,17 +25,17 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_SESSIONMESSAGES_H_ -#define TALK_P2P_BASE_SESSIONMESSAGES_H_ +#ifndef WEBRTC_P2P_BASE_SESSIONMESSAGES_H_ +#define WEBRTC_P2P_BASE_SESSIONMESSAGES_H_ #include #include #include -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/parsing.h" -#include "talk/p2p/base/sessiondescription.h" // Needed to delete contents. -#include "talk/p2p/base/transportinfo.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/parsing.h" +#include "webrtc/p2p/base/sessiondescription.h" // Needed to delete contents. +#include "webrtc/p2p/base/transportinfo.h" #include "webrtc/libjingle/xmllite/xmlelement.h" #include "webrtc/base/basictypes.h" @@ -240,4 +240,4 @@ bool FindSessionRedirect(const buzz::XmlElement* stanza, SessionRedirect* redirect); } // namespace cricket -#endif // TALK_P2P_BASE_SESSIONMESSAGES_H_ +#endif // WEBRTC_P2P_BASE_SESSIONMESSAGES_H_ diff --git a/talk/p2p/base/stun.cc b/talk/p2p/base/stun.cc index 061fd9a60..6bdf77e04 100644 --- a/talk/p2p/base/stun.cc +++ b/talk/p2p/base/stun.cc @@ -25,7 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/stun.h" +#include "webrtc/p2p/base/stun.h" #include diff --git a/talk/p2p/base/stun.h b/talk/p2p/base/stun.h index c4f522b90..5a43a3ccc 100644 --- a/talk/p2p/base/stun.h +++ b/talk/p2p/base/stun.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_STUN_H_ -#define TALK_P2P_BASE_STUN_H_ +#ifndef WEBRTC_P2P_BASE_STUN_H_ +#define WEBRTC_P2P_BASE_STUN_H_ // This file contains classes for dealing with the STUN protocol, as specified // in RFC 5389, and its descendants. @@ -646,4 +646,4 @@ class IceMessage : public StunMessage { } // namespace cricket -#endif // TALK_P2P_BASE_STUN_H_ +#endif // WEBRTC_P2P_BASE_STUN_H_ diff --git a/talk/p2p/base/stun_unittest.cc b/talk/p2p/base/stun_unittest.cc index 00dffed31..34a47cb11 100644 --- a/talk/p2p/base/stun_unittest.cc +++ b/talk/p2p/base/stun_unittest.cc @@ -27,7 +27,7 @@ #include -#include "talk/p2p/base/stun.h" +#include "webrtc/p2p/base/stun.h" #include "webrtc/base/bytebuffer.h" #include "webrtc/base/gunit.h" #include "webrtc/base/logging.h" diff --git a/talk/p2p/base/stunport.cc b/talk/p2p/base/stunport.cc index ce2d828a9..0602c5a5d 100644 --- a/talk/p2p/base/stunport.cc +++ b/talk/p2p/base/stunport.cc @@ -25,11 +25,11 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/stunport.h" +#include "webrtc/p2p/base/stunport.h" -#include "talk/p2p/base/common.h" -#include "talk/p2p/base/portallocator.h" -#include "talk/p2p/base/stun.h" +#include "webrtc/p2p/base/common.h" +#include "webrtc/p2p/base/portallocator.h" +#include "webrtc/p2p/base/stun.h" #include "webrtc/base/common.h" #include "webrtc/base/helpers.h" #include "webrtc/base/logging.h" diff --git a/talk/p2p/base/stunport.h b/talk/p2p/base/stunport.h index 1fc1f6491..976754603 100644 --- a/talk/p2p/base/stunport.h +++ b/talk/p2p/base/stunport.h @@ -25,13 +25,13 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_STUNPORT_H_ -#define TALK_P2P_BASE_STUNPORT_H_ +#ifndef WEBRTC_P2P_BASE_STUNPORT_H_ +#define WEBRTC_P2P_BASE_STUNPORT_H_ #include -#include "talk/p2p/base/port.h" -#include "talk/p2p/base/stunrequest.h" +#include "webrtc/p2p/base/port.h" +#include "webrtc/p2p/base/stunrequest.h" #include "webrtc/base/asyncpacketsocket.h" // TODO(mallinath) - Rename stunport.cc|h to udpport.cc|h. @@ -252,4 +252,4 @@ class StunPort : public UDPPort { } // namespace cricket -#endif // TALK_P2P_BASE_STUNPORT_H_ +#endif // WEBRTC_P2P_BASE_STUNPORT_H_ diff --git a/talk/p2p/base/stunport_unittest.cc b/talk/p2p/base/stunport_unittest.cc index 5c97ecc0d..7b289ea08 100644 --- a/talk/p2p/base/stunport_unittest.cc +++ b/talk/p2p/base/stunport_unittest.cc @@ -25,9 +25,9 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/basicpacketsocketfactory.h" -#include "talk/p2p/base/stunport.h" -#include "talk/p2p/base/teststunserver.h" +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/stunport.h" +#include "webrtc/p2p/base/teststunserver.h" #include "webrtc/base/gunit.h" #include "webrtc/base/helpers.h" #include "webrtc/base/physicalsocketserver.h" diff --git a/talk/p2p/base/stunrequest.cc b/talk/p2p/base/stunrequest.cc index 148718f25..55d270e2b 100644 --- a/talk/p2p/base/stunrequest.cc +++ b/talk/p2p/base/stunrequest.cc @@ -25,7 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/stunrequest.h" +#include "webrtc/p2p/base/stunrequest.h" #include "webrtc/base/common.h" #include "webrtc/base/helpers.h" diff --git a/talk/p2p/base/stunrequest.h b/talk/p2p/base/stunrequest.h index 374f7e7b3..be7344576 100644 --- a/talk/p2p/base/stunrequest.h +++ b/talk/p2p/base/stunrequest.h @@ -25,12 +25,12 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_STUNREQUEST_H_ -#define TALK_P2P_BASE_STUNREQUEST_H_ +#ifndef WEBRTC_P2P_BASE_STUNREQUEST_H_ +#define WEBRTC_P2P_BASE_STUNREQUEST_H_ #include #include -#include "talk/p2p/base/stun.h" +#include "webrtc/p2p/base/stun.h" #include "webrtc/base/sigslot.h" #include "webrtc/base/thread.h" @@ -130,4 +130,4 @@ private: } // namespace cricket -#endif // TALK_P2P_BASE_STUNREQUEST_H_ +#endif // WEBRTC_P2P_BASE_STUNREQUEST_H_ diff --git a/talk/p2p/base/stunrequest_unittest.cc b/talk/p2p/base/stunrequest_unittest.cc index 2783aa32b..5c7b97bae 100644 --- a/talk/p2p/base/stunrequest_unittest.cc +++ b/talk/p2p/base/stunrequest_unittest.cc @@ -25,7 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/stunrequest.h" +#include "webrtc/p2p/base/stunrequest.h" #include "webrtc/base/gunit.h" #include "webrtc/base/helpers.h" #include "webrtc/base/logging.h" diff --git a/talk/p2p/base/stunserver.cc b/talk/p2p/base/stunserver.cc index df428f9d4..a4beca433 100644 --- a/talk/p2p/base/stunserver.cc +++ b/talk/p2p/base/stunserver.cc @@ -25,7 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/stunserver.h" +#include "webrtc/p2p/base/stunserver.h" #include "webrtc/base/bytebuffer.h" #include "webrtc/base/logging.h" diff --git a/talk/p2p/base/stunserver.h b/talk/p2p/base/stunserver.h index a141e5b28..5bbe258c4 100644 --- a/talk/p2p/base/stunserver.h +++ b/talk/p2p/base/stunserver.h @@ -25,10 +25,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_STUNSERVER_H_ -#define TALK_P2P_BASE_STUNSERVER_H_ +#ifndef WEBRTC_P2P_BASE_STUNSERVER_H_ +#define WEBRTC_P2P_BASE_STUNSERVER_H_ -#include "talk/p2p/base/stun.h" +#include "webrtc/p2p/base/stun.h" #include "webrtc/base/asyncudpsocket.h" #include "webrtc/base/scoped_ptr.h" @@ -80,4 +80,4 @@ class StunServer : public sigslot::has_slots<> { } // namespace cricket -#endif // TALK_P2P_BASE_STUNSERVER_H_ +#endif // WEBRTC_P2P_BASE_STUNSERVER_H_ diff --git a/talk/p2p/base/stunserver_unittest.cc b/talk/p2p/base/stunserver_unittest.cc index 405d8aeab..4cf3c3303 100644 --- a/talk/p2p/base/stunserver_unittest.cc +++ b/talk/p2p/base/stunserver_unittest.cc @@ -27,7 +27,7 @@ #include -#include "talk/p2p/base/stunserver.h" +#include "webrtc/p2p/base/stunserver.h" #include "webrtc/base/gunit.h" #include "webrtc/base/logging.h" #include "webrtc/base/physicalsocketserver.h" diff --git a/talk/p2p/base/tcpport.cc b/talk/p2p/base/tcpport.cc index cb5b7266d..30c28c93a 100644 --- a/talk/p2p/base/tcpport.cc +++ b/talk/p2p/base/tcpport.cc @@ -25,9 +25,9 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/tcpport.h" +#include "webrtc/p2p/base/tcpport.h" -#include "talk/p2p/base/common.h" +#include "webrtc/p2p/base/common.h" #include "webrtc/base/common.h" #include "webrtc/base/logging.h" diff --git a/talk/p2p/base/tcpport.h b/talk/p2p/base/tcpport.h index 97bd97731..c331f6be3 100644 --- a/talk/p2p/base/tcpport.h +++ b/talk/p2p/base/tcpport.h @@ -25,12 +25,12 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_TCPPORT_H_ -#define TALK_P2P_BASE_TCPPORT_H_ +#ifndef WEBRTC_P2P_BASE_TCPPORT_H_ +#define WEBRTC_P2P_BASE_TCPPORT_H_ #include #include -#include "talk/p2p/base/port.h" +#include "webrtc/p2p/base/port.h" #include "webrtc/base/asyncpacketsocket.h" namespace cricket { @@ -150,4 +150,4 @@ class TCPConnection : public Connection { } // namespace cricket -#endif // TALK_P2P_BASE_TCPPORT_H_ +#endif // WEBRTC_P2P_BASE_TCPPORT_H_ diff --git a/talk/p2p/base/testrelayserver.h b/talk/p2p/base/testrelayserver.h index eaeefc801..003b42c0f 100644 --- a/talk/p2p/base/testrelayserver.h +++ b/talk/p2p/base/testrelayserver.h @@ -25,10 +25,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_TESTRELAYSERVER_H_ -#define TALK_P2P_BASE_TESTRELAYSERVER_H_ +#ifndef WEBRTC_P2P_BASE_TESTRELAYSERVER_H_ +#define WEBRTC_P2P_BASE_TESTRELAYSERVER_H_ -#include "talk/p2p/base/relayserver.h" +#include "webrtc/p2p/base/relayserver.h" #include "webrtc/base/asynctcpsocket.h" #include "webrtc/base/scoped_ptr.h" #include "webrtc/base/sigslot.h" @@ -115,4 +115,4 @@ class TestRelayServer : public sigslot::has_slots<> { } // namespace cricket -#endif // TALK_P2P_BASE_TESTRELAYSERVER_H_ +#endif // WEBRTC_P2P_BASE_TESTRELAYSERVER_H_ diff --git a/talk/p2p/base/teststunserver.h b/talk/p2p/base/teststunserver.h index 840150be8..eef15d798 100644 --- a/talk/p2p/base/teststunserver.h +++ b/talk/p2p/base/teststunserver.h @@ -25,10 +25,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_TESTSTUNSERVER_H_ -#define TALK_P2P_BASE_TESTSTUNSERVER_H_ +#ifndef WEBRTC_P2P_BASE_TESTSTUNSERVER_H_ +#define WEBRTC_P2P_BASE_TESTSTUNSERVER_H_ -#include "talk/p2p/base/stunserver.h" +#include "webrtc/p2p/base/stunserver.h" #include "webrtc/base/socketaddress.h" #include "webrtc/base/thread.h" @@ -72,4 +72,4 @@ class TestStunServer : StunServer { } // namespace cricket -#endif // TALK_P2P_BASE_TESTSTUNSERVER_H_ +#endif // WEBRTC_P2P_BASE_TESTSTUNSERVER_H_ diff --git a/talk/p2p/base/testturnserver.h b/talk/p2p/base/testturnserver.h index 6c30afe06..64da87783 100644 --- a/talk/p2p/base/testturnserver.h +++ b/talk/p2p/base/testturnserver.h @@ -25,15 +25,15 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_TESTTURNSERVER_H_ -#define TALK_P2P_BASE_TESTTURNSERVER_H_ +#ifndef WEBRTC_P2P_BASE_TESTTURNSERVER_H_ +#define WEBRTC_P2P_BASE_TESTTURNSERVER_H_ #include #include -#include "talk/p2p/base/basicpacketsocketfactory.h" -#include "talk/p2p/base/stun.h" -#include "talk/p2p/base/turnserver.h" +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/stun.h" +#include "webrtc/p2p/base/turnserver.h" #include "webrtc/base/asyncudpsocket.h" #include "webrtc/base/thread.h" @@ -117,4 +117,4 @@ class TestTurnServer : public TurnAuthInterface { } // namespace cricket -#endif // TALK_P2P_BASE_TESTTURNSERVER_H_ +#endif // WEBRTC_P2P_BASE_TESTTURNSERVER_H_ diff --git a/talk/p2p/base/transport.cc b/talk/p2p/base/transport.cc index d88f5e70b..f33f942c9 100644 --- a/talk/p2p/base/transport.cc +++ b/talk/p2p/base/transport.cc @@ -25,16 +25,16 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/transport.h" +#include "webrtc/p2p/base/transport.h" -#include "talk/p2p/base/candidate.h" -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/parsing.h" -#include "talk/p2p/base/port.h" -#include "talk/p2p/base/sessionmanager.h" -#include "talk/p2p/base/transportchannelimpl.h" +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/parsing.h" +#include "webrtc/p2p/base/port.h" +#include "webrtc/p2p/base/sessionmanager.h" +#include "webrtc/p2p/base/transportchannelimpl.h" #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" #include "webrtc/base/bind.h" #include "webrtc/base/common.h" #include "webrtc/base/logging.h" diff --git a/talk/p2p/base/transport.h b/talk/p2p/base/transport.h index 21661f6ea..fbc7f6f44 100644 --- a/talk/p2p/base/transport.h +++ b/talk/p2p/base/transport.h @@ -43,16 +43,16 @@ // It is not possible to do so here because the subclass constructor will // already have run. -#ifndef TALK_P2P_BASE_TRANSPORT_H_ -#define TALK_P2P_BASE_TRANSPORT_H_ +#ifndef WEBRTC_P2P_BASE_TRANSPORT_H_ +#define WEBRTC_P2P_BASE_TRANSPORT_H_ #include #include #include -#include "talk/p2p/base/candidate.h" -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/sessiondescription.h" -#include "talk/p2p/base/transportinfo.h" +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/sessiondescription.h" +#include "webrtc/p2p/base/transportinfo.h" #include "webrtc/base/criticalsection.h" #include "webrtc/base/messagequeue.h" #include "webrtc/base/sigslot.h" @@ -527,4 +527,4 @@ TransportProtocol TransportProtocolFromDescription( } // namespace cricket -#endif // TALK_P2P_BASE_TRANSPORT_H_ +#endif // WEBRTC_P2P_BASE_TRANSPORT_H_ diff --git a/talk/p2p/base/transport_unittest.cc b/talk/p2p/base/transport_unittest.cc index 8f7dae2a1..9ef7d60ef 100644 --- a/talk/p2p/base/transport_unittest.cc +++ b/talk/p2p/base/transport_unittest.cc @@ -25,14 +25,14 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/fakesession.h" -#include "talk/p2p/base/p2ptransport.h" -#include "talk/p2p/base/parsing.h" -#include "talk/p2p/base/rawtransport.h" -#include "talk/p2p/base/sessionmessages.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/fakesession.h" +#include "webrtc/p2p/base/p2ptransport.h" +#include "webrtc/p2p/base/parsing.h" +#include "webrtc/p2p/base/rawtransport.h" +#include "webrtc/p2p/base/sessionmessages.h" #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" #include "webrtc/base/fakesslidentity.h" #include "webrtc/base/gunit.h" #include "webrtc/base/thread.h" diff --git a/talk/p2p/base/transportchannel.cc b/talk/p2p/base/transportchannel.cc index 50ebfb991..13048ed08 100644 --- a/talk/p2p/base/transportchannel.cc +++ b/talk/p2p/base/transportchannel.cc @@ -26,7 +26,7 @@ */ #include -#include "talk/p2p/base/transportchannel.h" +#include "webrtc/p2p/base/transportchannel.h" namespace cricket { diff --git a/talk/p2p/base/transportchannel.h b/talk/p2p/base/transportchannel.h index b0beb044f..56d9467ff 100644 --- a/talk/p2p/base/transportchannel.h +++ b/talk/p2p/base/transportchannel.h @@ -25,15 +25,15 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_TRANSPORTCHANNEL_H_ -#define TALK_P2P_BASE_TRANSPORTCHANNEL_H_ +#ifndef WEBRTC_P2P_BASE_TRANSPORTCHANNEL_H_ +#define WEBRTC_P2P_BASE_TRANSPORTCHANNEL_H_ #include #include -#include "talk/p2p/base/candidate.h" -#include "talk/p2p/base/transport.h" -#include "talk/p2p/base/transportdescription.h" +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/transport.h" +#include "webrtc/p2p/base/transportdescription.h" #include "webrtc/base/asyncpacketsocket.h" #include "webrtc/base/basictypes.h" #include "webrtc/base/dscp.h" @@ -157,4 +157,4 @@ class TransportChannel : public sigslot::has_slots<> { } // namespace cricket -#endif // TALK_P2P_BASE_TRANSPORTCHANNEL_H_ +#endif // WEBRTC_P2P_BASE_TRANSPORTCHANNEL_H_ diff --git a/talk/p2p/base/transportchannelimpl.h b/talk/p2p/base/transportchannelimpl.h index fde980b6e..73afdee23 100644 --- a/talk/p2p/base/transportchannelimpl.h +++ b/talk/p2p/base/transportchannelimpl.h @@ -25,12 +25,12 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_TRANSPORTCHANNELIMPL_H_ -#define TALK_P2P_BASE_TRANSPORTCHANNELIMPL_H_ +#ifndef WEBRTC_P2P_BASE_TRANSPORTCHANNELIMPL_H_ +#define WEBRTC_P2P_BASE_TRANSPORTCHANNELIMPL_H_ #include -#include "talk/p2p/base/transport.h" -#include "talk/p2p/base/transportchannel.h" +#include "webrtc/p2p/base/transport.h" +#include "webrtc/p2p/base/transportchannel.h" namespace buzz { class XmlElement; } @@ -125,4 +125,4 @@ class TransportChannelImpl : public TransportChannel { } // namespace cricket -#endif // TALK_P2P_BASE_TRANSPORTCHANNELIMPL_H_ +#endif // WEBRTC_P2P_BASE_TRANSPORTCHANNELIMPL_H_ diff --git a/talk/p2p/base/transportchannelproxy.cc b/talk/p2p/base/transportchannelproxy.cc index 9fa0b6a51..259b8ddc6 100644 --- a/talk/p2p/base/transportchannelproxy.cc +++ b/talk/p2p/base/transportchannelproxy.cc @@ -25,9 +25,9 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/transport.h" -#include "talk/p2p/base/transportchannelimpl.h" -#include "talk/p2p/base/transportchannelproxy.h" +#include "webrtc/p2p/base/transport.h" +#include "webrtc/p2p/base/transportchannelimpl.h" +#include "webrtc/p2p/base/transportchannelproxy.h" #include "webrtc/base/common.h" #include "webrtc/base/logging.h" #include "webrtc/base/thread.h" diff --git a/talk/p2p/base/transportchannelproxy.h b/talk/p2p/base/transportchannelproxy.h index 07b90903e..dee4313c4 100644 --- a/talk/p2p/base/transportchannelproxy.h +++ b/talk/p2p/base/transportchannelproxy.h @@ -25,14 +25,14 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_TRANSPORTCHANNELPROXY_H_ -#define TALK_P2P_BASE_TRANSPORTCHANNELPROXY_H_ +#ifndef WEBRTC_P2P_BASE_TRANSPORTCHANNELPROXY_H_ +#define WEBRTC_P2P_BASE_TRANSPORTCHANNELPROXY_H_ #include #include #include -#include "talk/p2p/base/transportchannel.h" +#include "webrtc/p2p/base/transportchannel.h" #include "webrtc/base/messagehandler.h" namespace rtc { @@ -109,4 +109,4 @@ class TransportChannelProxy : public TransportChannel, } // namespace cricket -#endif // TALK_P2P_BASE_TRANSPORTCHANNELPROXY_H_ +#endif // WEBRTC_P2P_BASE_TRANSPORTCHANNELPROXY_H_ diff --git a/talk/p2p/base/transportdescription.cc b/talk/p2p/base/transportdescription.cc index 08460d795..8bdcbb368 100644 --- a/talk/p2p/base/transportdescription.cc +++ b/talk/p2p/base/transportdescription.cc @@ -25,9 +25,9 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/transportdescription.h" +#include "webrtc/p2p/base/transportdescription.h" -#include "talk/p2p/base/constants.h" +#include "webrtc/p2p/base/constants.h" #include "webrtc/base/stringutils.h" namespace cricket { diff --git a/talk/p2p/base/transportdescription.h b/talk/p2p/base/transportdescription.h index efd04ffe0..5763d23e6 100644 --- a/talk/p2p/base/transportdescription.h +++ b/talk/p2p/base/transportdescription.h @@ -25,15 +25,15 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_TRANSPORTDESCRIPTION_H_ -#define TALK_P2P_BASE_TRANSPORTDESCRIPTION_H_ +#ifndef WEBRTC_P2P_BASE_TRANSPORTDESCRIPTION_H_ +#define WEBRTC_P2P_BASE_TRANSPORTDESCRIPTION_H_ #include #include #include -#include "talk/p2p/base/candidate.h" -#include "talk/p2p/base/constants.h" +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/constants.h" #include "webrtc/base/scoped_ptr.h" #include "webrtc/base/sslfingerprint.h" @@ -185,4 +185,4 @@ struct TransportDescription { } // namespace cricket -#endif // TALK_P2P_BASE_TRANSPORTDESCRIPTION_H_ +#endif // WEBRTC_P2P_BASE_TRANSPORTDESCRIPTION_H_ diff --git a/talk/p2p/base/transportdescriptionfactory.cc b/talk/p2p/base/transportdescriptionfactory.cc index 4eb603c3e..7260b9c8e 100644 --- a/talk/p2p/base/transportdescriptionfactory.cc +++ b/talk/p2p/base/transportdescriptionfactory.cc @@ -25,9 +25,9 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/transportdescriptionfactory.h" +#include "webrtc/p2p/base/transportdescriptionfactory.h" -#include "talk/p2p/base/transportdescription.h" +#include "webrtc/p2p/base/transportdescription.h" #include "webrtc/base/helpers.h" #include "webrtc/base/logging.h" #include "webrtc/base/messagedigest.h" diff --git a/talk/p2p/base/transportdescriptionfactory.h b/talk/p2p/base/transportdescriptionfactory.h index 84f25ac45..bcd14c648 100644 --- a/talk/p2p/base/transportdescriptionfactory.h +++ b/talk/p2p/base/transportdescriptionfactory.h @@ -25,10 +25,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_TRANSPORTDESCRIPTIONFACTORY_H_ -#define TALK_P2P_BASE_TRANSPORTDESCRIPTIONFACTORY_H_ +#ifndef WEBRTC_P2P_BASE_TRANSPORTDESCRIPTIONFACTORY_H_ +#define WEBRTC_P2P_BASE_TRANSPORTDESCRIPTIONFACTORY_H_ -#include "talk/p2p/base/transportdescription.h" +#include "webrtc/p2p/base/transportdescription.h" namespace rtc { class SSLIdentity; @@ -80,4 +80,4 @@ class TransportDescriptionFactory { } // namespace cricket -#endif // TALK_P2P_BASE_TRANSPORTDESCRIPTIONFACTORY_H_ +#endif // WEBRTC_P2P_BASE_TRANSPORTDESCRIPTIONFACTORY_H_ diff --git a/talk/p2p/base/transportdescriptionfactory_unittest.cc b/talk/p2p/base/transportdescriptionfactory_unittest.cc index 5c17f963c..a7938532f 100644 --- a/talk/p2p/base/transportdescriptionfactory_unittest.cc +++ b/talk/p2p/base/transportdescriptionfactory_unittest.cc @@ -28,9 +28,9 @@ #include #include -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/transportdescription.h" -#include "talk/p2p/base/transportdescriptionfactory.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/transportdescription.h" +#include "webrtc/p2p/base/transportdescriptionfactory.h" #include "webrtc/base/fakesslidentity.h" #include "webrtc/base/gunit.h" #include "webrtc/base/ssladapter.h" diff --git a/talk/p2p/base/transportinfo.h b/talk/p2p/base/transportinfo.h index 5ffb10acc..414b6caf6 100644 --- a/talk/p2p/base/transportinfo.h +++ b/talk/p2p/base/transportinfo.h @@ -25,15 +25,15 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_TRANSPORTINFO_H_ -#define TALK_P2P_BASE_TRANSPORTINFO_H_ +#ifndef WEBRTC_P2P_BASE_TRANSPORTINFO_H_ +#define WEBRTC_P2P_BASE_TRANSPORTINFO_H_ #include #include -#include "talk/p2p/base/candidate.h" -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/transportdescription.h" +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/transportdescription.h" #include "webrtc/base/helpers.h" namespace cricket { @@ -57,4 +57,4 @@ typedef std::vector TransportInfos; } // namespace cricket -#endif // TALK_P2P_BASE_TRANSPORTINFO_H_ +#endif // WEBRTC_P2P_BASE_TRANSPORTINFO_H_ diff --git a/talk/p2p/base/turnport.cc b/talk/p2p/base/turnport.cc index 64bfed457..b38317b81 100644 --- a/talk/p2p/base/turnport.cc +++ b/talk/p2p/base/turnport.cc @@ -25,12 +25,12 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/turnport.h" +#include "webrtc/p2p/base/turnport.h" #include -#include "talk/p2p/base/common.h" -#include "talk/p2p/base/stun.h" +#include "webrtc/p2p/base/common.h" +#include "webrtc/p2p/base/stun.h" #include "webrtc/base/asyncpacketsocket.h" #include "webrtc/base/byteorder.h" #include "webrtc/base/common.h" diff --git a/talk/p2p/base/turnport.h b/talk/p2p/base/turnport.h index ab7d4e714..6345aea16 100644 --- a/talk/p2p/base/turnport.h +++ b/talk/p2p/base/turnport.h @@ -25,16 +25,16 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_TURNPORT_H_ -#define TALK_P2P_BASE_TURNPORT_H_ +#ifndef WEBRTC_P2P_BASE_TURNPORT_H_ +#define WEBRTC_P2P_BASE_TURNPORT_H_ #include #include #include #include -#include "talk/p2p/base/port.h" -#include "talk/p2p/client/basicportallocator.h" +#include "webrtc/p2p/base/port.h" +#include "webrtc/p2p/client/basicportallocator.h" #include "webrtc/base/asyncpacketsocket.h" namespace rtc { @@ -251,4 +251,4 @@ class TurnPort : public Port { } // namespace cricket -#endif // TALK_P2P_BASE_TURNPORT_H_ +#endif // WEBRTC_P2P_BASE_TURNPORT_H_ diff --git a/talk/p2p/base/turnport_unittest.cc b/talk/p2p/base/turnport_unittest.cc index c5261e4a3..95615ff1f 100644 --- a/talk/p2p/base/turnport_unittest.cc +++ b/talk/p2p/base/turnport_unittest.cc @@ -28,12 +28,12 @@ #include #endif -#include "talk/p2p/base/basicpacketsocketfactory.h" -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/tcpport.h" -#include "talk/p2p/base/testturnserver.h" -#include "talk/p2p/base/turnport.h" -#include "talk/p2p/base/udpport.h" +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/tcpport.h" +#include "webrtc/p2p/base/testturnserver.h" +#include "webrtc/p2p/base/turnport.h" +#include "webrtc/p2p/base/udpport.h" #include "webrtc/base/asynctcpsocket.h" #include "webrtc/base/buffer.h" #include "webrtc/base/dscp.h" diff --git a/talk/p2p/base/turnserver.cc b/talk/p2p/base/turnserver.cc index dbcbcd493..28096aebc 100644 --- a/talk/p2p/base/turnserver.cc +++ b/talk/p2p/base/turnserver.cc @@ -25,12 +25,12 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/turnserver.h" +#include "webrtc/p2p/base/turnserver.h" -#include "talk/p2p/base/asyncstuntcpsocket.h" -#include "talk/p2p/base/common.h" -#include "talk/p2p/base/packetsocketfactory.h" -#include "talk/p2p/base/stun.h" +#include "webrtc/p2p/base/asyncstuntcpsocket.h" +#include "webrtc/p2p/base/common.h" +#include "webrtc/p2p/base/packetsocketfactory.h" +#include "webrtc/p2p/base/stun.h" #include "webrtc/base/bytebuffer.h" #include "webrtc/base/helpers.h" #include "webrtc/base/logging.h" diff --git a/talk/p2p/base/turnserver.h b/talk/p2p/base/turnserver.h index 553d00ca8..44878fdc8 100644 --- a/talk/p2p/base/turnserver.h +++ b/talk/p2p/base/turnserver.h @@ -25,15 +25,15 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_TURNSERVER_H_ -#define TALK_P2P_BASE_TURNSERVER_H_ +#ifndef WEBRTC_P2P_BASE_TURNSERVER_H_ +#define WEBRTC_P2P_BASE_TURNSERVER_H_ #include #include #include #include -#include "talk/p2p/base/portinterface.h" +#include "webrtc/p2p/base/portinterface.h" #include "webrtc/base/asyncpacketsocket.h" #include "webrtc/base/messagequeue.h" #include "webrtc/base/sigslot.h" @@ -204,4 +204,4 @@ class TurnServer : public sigslot::has_slots<> { } // namespace cricket -#endif // TALK_P2P_BASE_TURNSERVER_H_ +#endif // WEBRTC_P2P_BASE_TURNSERVER_H_ diff --git a/talk/p2p/base/udpport.h b/talk/p2p/base/udpport.h index fc981fdad..35be1a16b 100644 --- a/talk/p2p/base/udpport.h +++ b/talk/p2p/base/udpport.h @@ -25,10 +25,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_BASE_UDPPORT_H_ -#define TALK_P2P_BASE_UDPPORT_H_ +#ifndef WEBRTC_P2P_BASE_UDPPORT_H_ +#define WEBRTC_P2P_BASE_UDPPORT_H_ // StunPort will be handling UDPPort functionality. -#include "talk/p2p/base/stunport.h" +#include "webrtc/p2p/base/stunport.h" -#endif // TALK_P2P_BASE_UDPPORT_H_ +#endif // WEBRTC_P2P_BASE_UDPPORT_H_ diff --git a/talk/p2p/client/autoportallocator.h b/talk/p2p/client/autoportallocator.h index ed87162e4..e7f216994 100644 --- a/talk/p2p/client/autoportallocator.h +++ b/talk/p2p/client/autoportallocator.h @@ -25,15 +25,15 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_CLIENT_AUTOPORTALLOCATOR_H_ -#define TALK_P2P_CLIENT_AUTOPORTALLOCATOR_H_ +#ifndef WEBRTC_P2P_CLIENT_AUTOPORTALLOCATOR_H_ +#define WEBRTC_P2P_CLIENT_AUTOPORTALLOCATOR_H_ #include #include -#include "talk/p2p/client/httpportallocator.h" -#include "talk/xmpp/jingleinfotask.h" -#include "talk/xmpp/xmppclient.h" +#include "webrtc/p2p/client/httpportallocator.h" +#include "webrtc/libjingle/xmpp/jingleinfotask.h" +#include "webrtc/libjingle/xmpp/xmppclient.h" #include "webrtc/base/sigslot.h" // This class sets the relay and stun servers using XmppClient. @@ -66,4 +66,4 @@ class AutoPortAllocator : public cricket::HttpPortAllocator { } }; -#endif // TALK_P2P_CLIENT_AUTOPORTALLOCATOR_H_ +#endif // WEBRTC_P2P_CLIENT_AUTOPORTALLOCATOR_H_ diff --git a/talk/p2p/client/basicportallocator.cc b/talk/p2p/client/basicportallocator.cc index 0b35bddd4..27d0db2d1 100644 --- a/talk/p2p/client/basicportallocator.cc +++ b/talk/p2p/client/basicportallocator.cc @@ -25,19 +25,19 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/client/basicportallocator.h" +#include "webrtc/p2p/client/basicportallocator.h" #include #include -#include "talk/p2p/base/basicpacketsocketfactory.h" -#include "talk/p2p/base/common.h" -#include "talk/p2p/base/port.h" -#include "talk/p2p/base/relayport.h" -#include "talk/p2p/base/stunport.h" -#include "talk/p2p/base/tcpport.h" -#include "talk/p2p/base/turnport.h" -#include "talk/p2p/base/udpport.h" +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/common.h" +#include "webrtc/p2p/base/port.h" +#include "webrtc/p2p/base/relayport.h" +#include "webrtc/p2p/base/stunport.h" +#include "webrtc/p2p/base/tcpport.h" +#include "webrtc/p2p/base/turnport.h" +#include "webrtc/p2p/base/udpport.h" #include "webrtc/base/common.h" #include "webrtc/base/helpers.h" #include "webrtc/base/logging.h" diff --git a/talk/p2p/client/basicportallocator.h b/talk/p2p/client/basicportallocator.h index d4247722d..e2389d3e6 100644 --- a/talk/p2p/client/basicportallocator.h +++ b/talk/p2p/client/basicportallocator.h @@ -25,14 +25,14 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_CLIENT_BASICPORTALLOCATOR_H_ -#define TALK_P2P_CLIENT_BASICPORTALLOCATOR_H_ +#ifndef WEBRTC_P2P_CLIENT_BASICPORTALLOCATOR_H_ +#define WEBRTC_P2P_CLIENT_BASICPORTALLOCATOR_H_ #include #include -#include "talk/p2p/base/port.h" -#include "talk/p2p/base/portallocator.h" +#include "webrtc/p2p/base/port.h" +#include "webrtc/p2p/base/portallocator.h" #include "webrtc/base/messagequeue.h" #include "webrtc/base/network.h" #include "webrtc/base/scoped_ptr.h" @@ -255,4 +255,4 @@ struct PortConfiguration : public rtc::MessageData { } // namespace cricket -#endif // TALK_P2P_CLIENT_BASICPORTALLOCATOR_H_ +#endif // WEBRTC_P2P_CLIENT_BASICPORTALLOCATOR_H_ diff --git a/talk/p2p/client/connectivitychecker.cc b/talk/p2p/client/connectivitychecker.cc index 723c5a10b..38a7dd2f9 100644 --- a/talk/p2p/client/connectivitychecker.cc +++ b/talk/p2p/client/connectivitychecker.cc @@ -27,14 +27,14 @@ #include -#include "talk/p2p/client/connectivitychecker.h" +#include "webrtc/p2p/client/connectivitychecker.h" -#include "talk/p2p/base/candidate.h" -#include "talk/p2p/base/common.h" -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/port.h" -#include "talk/p2p/base/relayport.h" -#include "talk/p2p/base/stunport.h" +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/common.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/port.h" +#include "webrtc/p2p/base/relayport.h" +#include "webrtc/p2p/base/stunport.h" #include "webrtc/base/asynchttprequest.h" #include "webrtc/base/autodetectproxy.h" #include "webrtc/base/helpers.h" diff --git a/talk/p2p/client/connectivitychecker.h b/talk/p2p/client/connectivitychecker.h index d4cda1eee..ba3d00374 100644 --- a/talk/p2p/client/connectivitychecker.h +++ b/talk/p2p/client/connectivitychecker.h @@ -1,14 +1,14 @@ // Copyright 2011 Google Inc. All Rights Reserved. -#ifndef TALK_P2P_CLIENT_CONNECTIVITYCHECKER_H_ -#define TALK_P2P_CLIENT_CONNECTIVITYCHECKER_H_ +#ifndef WEBRTC_P2P_CLIENT_CONNECTIVITYCHECKER_H_ +#define WEBRTC_P2P_CLIENT_CONNECTIVITYCHECKER_H_ #include #include -#include "talk/p2p/base/basicpacketsocketfactory.h" -#include "talk/p2p/client/httpportallocator.h" +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/client/httpportallocator.h" #include "webrtc/base/basictypes.h" #include "webrtc/base/messagehandler.h" #include "webrtc/base/network.h" @@ -271,4 +271,4 @@ class ConnectivityChecker } // namespace cricket -#endif // TALK_P2P_CLIENT_CONNECTIVITYCHECKER_H_ +#endif // WEBRTC_P2P_CLIENT_CONNECTIVITYCHECKER_H_ diff --git a/talk/p2p/client/connectivitychecker_unittest.cc b/talk/p2p/client/connectivitychecker_unittest.cc index 187505a4d..5a2a60825 100644 --- a/talk/p2p/client/connectivitychecker_unittest.cc +++ b/talk/p2p/client/connectivitychecker_unittest.cc @@ -27,11 +27,11 @@ #include -#include "talk/p2p/base/basicpacketsocketfactory.h" -#include "talk/p2p/base/relayport.h" -#include "talk/p2p/base/stunport.h" -#include "talk/p2p/client/connectivitychecker.h" -#include "talk/p2p/client/httpportallocator.h" +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/relayport.h" +#include "webrtc/p2p/base/stunport.h" +#include "webrtc/p2p/client/connectivitychecker.h" +#include "webrtc/p2p/client/httpportallocator.h" #include "webrtc/base/asynchttprequest.h" #include "webrtc/base/fakenetwork.h" #include "webrtc/base/gunit.h" diff --git a/talk/p2p/client/fakeportallocator.h b/talk/p2p/client/fakeportallocator.h index e1a04dd18..04c5f47b8 100644 --- a/talk/p2p/client/fakeportallocator.h +++ b/talk/p2p/client/fakeportallocator.h @@ -25,13 +25,13 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_CLIENT_FAKEPORTALLOCATOR_H_ -#define TALK_P2P_CLIENT_FAKEPORTALLOCATOR_H_ +#ifndef WEBRTC_P2P_CLIENT_FAKEPORTALLOCATOR_H_ +#define WEBRTC_P2P_CLIENT_FAKEPORTALLOCATOR_H_ #include -#include "talk/p2p/base/basicpacketsocketfactory.h" -#include "talk/p2p/base/portallocator.h" -#include "talk/p2p/base/udpport.h" +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/portallocator.h" +#include "webrtc/p2p/base/udpport.h" #include "webrtc/base/scoped_ptr.h" namespace rtc { @@ -135,4 +135,4 @@ class FakePortAllocator : public cricket::PortAllocator { } // namespace cricket -#endif // TALK_P2P_CLIENT_FAKEPORTALLOCATOR_H_ +#endif // WEBRTC_P2P_CLIENT_FAKEPORTALLOCATOR_H_ diff --git a/talk/p2p/client/httpportallocator.cc b/talk/p2p/client/httpportallocator.cc index 31c9b5195..ce525936e 100644 --- a/talk/p2p/client/httpportallocator.cc +++ b/talk/p2p/client/httpportallocator.cc @@ -25,7 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/client/httpportallocator.h" +#include "webrtc/p2p/client/httpportallocator.h" #include #include diff --git a/talk/p2p/client/httpportallocator.h b/talk/p2p/client/httpportallocator.h index 7ace94385..cab8e7e72 100644 --- a/talk/p2p/client/httpportallocator.h +++ b/talk/p2p/client/httpportallocator.h @@ -25,14 +25,14 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_CLIENT_HTTPPORTALLOCATOR_H_ -#define TALK_P2P_CLIENT_HTTPPORTALLOCATOR_H_ +#ifndef WEBRTC_P2P_CLIENT_HTTPPORTALLOCATOR_H_ +#define WEBRTC_P2P_CLIENT_HTTPPORTALLOCATOR_H_ #include #include #include -#include "talk/p2p/client/basicportallocator.h" +#include "webrtc/p2p/client/basicportallocator.h" class HttpPortAllocatorTest_TestSessionRequestUrl_Test; @@ -187,4 +187,4 @@ class HttpPortAllocatorSession : public HttpPortAllocatorSessionBase { } // namespace cricket -#endif // TALK_P2P_CLIENT_HTTPPORTALLOCATOR_H_ +#endif // WEBRTC_P2P_CLIENT_HTTPPORTALLOCATOR_H_ diff --git a/talk/p2p/client/portallocator_unittest.cc b/talk/p2p/client/portallocator_unittest.cc index 3357fe0be..5b1a86927 100644 --- a/talk/p2p/client/portallocator_unittest.cc +++ b/talk/p2p/client/portallocator_unittest.cc @@ -25,15 +25,15 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/base/basicpacketsocketfactory.h" -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/p2ptransportchannel.h" -#include "talk/p2p/base/portallocatorsessionproxy.h" -#include "talk/p2p/base/testrelayserver.h" -#include "talk/p2p/base/teststunserver.h" -#include "talk/p2p/base/testturnserver.h" -#include "talk/p2p/client/basicportallocator.h" -#include "talk/p2p/client/httpportallocator.h" +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/p2ptransportchannel.h" +#include "webrtc/p2p/base/portallocatorsessionproxy.h" +#include "webrtc/p2p/base/testrelayserver.h" +#include "webrtc/p2p/base/teststunserver.h" +#include "webrtc/p2p/base/testturnserver.h" +#include "webrtc/p2p/client/basicportallocator.h" +#include "webrtc/p2p/client/httpportallocator.h" #include "webrtc/base/fakenetwork.h" #include "webrtc/base/firewallsocketserver.h" #include "webrtc/base/gunit.h" diff --git a/talk/p2p/client/sessionmanagertask.h b/talk/p2p/client/sessionmanagertask.h index e16d9d6f7..753d93889 100644 --- a/talk/p2p/client/sessionmanagertask.h +++ b/talk/p2p/client/sessionmanagertask.h @@ -25,13 +25,13 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_CLIENT_SESSIONMANAGERTASK_H_ -#define TALK_P2P_CLIENT_SESSIONMANAGERTASK_H_ +#ifndef WEBRTC_P2P_CLIENT_SESSIONMANAGERTASK_H_ +#define WEBRTC_P2P_CLIENT_SESSIONMANAGERTASK_H_ -#include "talk/p2p/base/sessionmanager.h" -#include "talk/p2p/client/sessionsendtask.h" -#include "talk/xmpp/xmppengine.h" -#include "talk/xmpp/xmpptask.h" +#include "webrtc/p2p/base/sessionmanager.h" +#include "webrtc/p2p/client/sessionsendtask.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" namespace cricket { @@ -90,4 +90,4 @@ class SessionManagerTask : public buzz::XmppTask { } // namespace cricket -#endif // TALK_P2P_CLIENT_SESSIONMANAGERTASK_H_ +#endif // WEBRTC_P2P_CLIENT_SESSIONMANAGERTASK_H_ diff --git a/talk/p2p/client/sessionsendtask.h b/talk/p2p/client/sessionsendtask.h index c8734d29b..7dafc1e1f 100644 --- a/talk/p2p/client/sessionsendtask.h +++ b/talk/p2p/client/sessionsendtask.h @@ -25,14 +25,14 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_CLIENT_SESSIONSENDTASK_H_ -#define TALK_P2P_CLIENT_SESSIONSENDTASK_H_ +#ifndef WEBRTC_P2P_CLIENT_SESSIONSENDTASK_H_ +#define WEBRTC_P2P_CLIENT_SESSIONSENDTASK_H_ -#include "talk/p2p/base/sessionmanager.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/xmppclient.h" -#include "talk/xmpp/xmppengine.h" -#include "talk/xmpp/xmpptask.h" +#include "webrtc/p2p/base/sessionmanager.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/xmppclient.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" #include "webrtc/base/common.h" namespace cricket { @@ -142,4 +142,4 @@ class SessionSendTask : public buzz::XmppTask { } -#endif // TALK_P2P_CLIENT_SESSIONSENDTASK_H_ +#endif // WEBRTC_P2P_CLIENT_SESSIONSENDTASK_H_ diff --git a/talk/p2p/client/socketmonitor.cc b/talk/p2p/client/socketmonitor.cc index 1924c7079..a3a33e311 100644 --- a/talk/p2p/client/socketmonitor.cc +++ b/talk/p2p/client/socketmonitor.cc @@ -25,7 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/p2p/client/socketmonitor.h" +#include "webrtc/p2p/client/socketmonitor.h" #include "webrtc/base/common.h" diff --git a/talk/p2p/client/socketmonitor.h b/talk/p2p/client/socketmonitor.h index 87b11cf40..77241fed0 100644 --- a/talk/p2p/client/socketmonitor.h +++ b/talk/p2p/client/socketmonitor.h @@ -25,12 +25,12 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_P2P_CLIENT_SOCKETMONITOR_H_ -#define TALK_P2P_CLIENT_SOCKETMONITOR_H_ +#ifndef WEBRTC_P2P_CLIENT_SOCKETMONITOR_H_ +#define WEBRTC_P2P_CLIENT_SOCKETMONITOR_H_ #include -#include "talk/p2p/base/transportchannel.h" +#include "webrtc/p2p/base/transportchannel.h" #include "webrtc/base/criticalsection.h" #include "webrtc/base/sigslot.h" #include "webrtc/base/thread.h" @@ -68,4 +68,4 @@ class SocketMonitor : public rtc::MessageHandler, } // namespace cricket -#endif // TALK_P2P_CLIENT_SOCKETMONITOR_H_ +#endif // WEBRTC_P2P_CLIENT_SOCKETMONITOR_H_ diff --git a/talk/session/media/audiomonitor.h b/talk/session/media/audiomonitor.h index e5585a493..454f0fe01 100644 --- a/talk/session/media/audiomonitor.h +++ b/talk/session/media/audiomonitor.h @@ -29,7 +29,7 @@ #define TALK_SESSION_MEDIA_AUDIOMONITOR_H_ #include -#include "talk/p2p/base/port.h" +#include "webrtc/p2p/base/port.h" #include "webrtc/base/sigslot.h" #include "webrtc/base/thread.h" diff --git a/talk/session/media/call.cc b/talk/session/media/call.cc index ad2813032..7e59ac6a6 100644 --- a/talk/session/media/call.cc +++ b/talk/session/media/call.cc @@ -28,7 +28,7 @@ #include #include "talk/media/base/constants.h" #include "talk/media/base/screencastid.h" -#include "talk/p2p/base/parsing.h" +#include "webrtc/p2p/base/parsing.h" #include "talk/session/media/call.h" #include "talk/session/media/currentspeakermonitor.h" #include "talk/session/media/mediasessionclient.h" diff --git a/talk/session/media/call.h b/talk/session/media/call.h index be9397e0b..5a2d49b3d 100644 --- a/talk/session/media/call.h +++ b/talk/session/media/call.h @@ -37,13 +37,13 @@ #include "talk/media/base/screencastid.h" #include "talk/media/base/streamparams.h" #include "talk/media/base/videocommon.h" -#include "talk/p2p/base/session.h" -#include "talk/p2p/client/socketmonitor.h" +#include "webrtc/p2p/base/session.h" +#include "webrtc/p2p/client/socketmonitor.h" #include "talk/session/media/audiomonitor.h" #include "talk/session/media/currentspeakermonitor.h" #include "talk/session/media/mediamessages.h" #include "talk/session/media/mediasession.h" -#include "talk/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/jid.h" #include "webrtc/base/messagequeue.h" namespace cricket { diff --git a/talk/session/media/channel.cc b/talk/session/media/channel.cc index 3615da867..414a131e3 100644 --- a/talk/session/media/channel.cc +++ b/talk/session/media/channel.cc @@ -29,7 +29,7 @@ #include "talk/media/base/constants.h" #include "talk/media/base/rtputils.h" -#include "talk/p2p/base/transportchannel.h" +#include "webrtc/p2p/base/transportchannel.h" #include "talk/session/media/channelmanager.h" #include "talk/session/media/mediamessages.h" #include "talk/session/media/typingmonitor.h" diff --git a/talk/session/media/channel.h b/talk/session/media/channel.h index 6da29a4ae..29980bba2 100644 --- a/talk/session/media/channel.h +++ b/talk/session/media/channel.h @@ -35,8 +35,8 @@ #include "talk/media/base/mediaengine.h" #include "talk/media/base/streamparams.h" #include "talk/media/base/videocapturer.h" -#include "talk/p2p/base/session.h" -#include "talk/p2p/client/socketmonitor.h" +#include "webrtc/p2p/base/session.h" +#include "webrtc/p2p/client/socketmonitor.h" #include "talk/session/media/audiomonitor.h" #include "talk/session/media/bundlefilter.h" #include "talk/session/media/mediamonitor.h" diff --git a/talk/session/media/channel_unittest.cc b/talk/session/media/channel_unittest.cc index 698a34ae8..0fba14eb8 100644 --- a/talk/session/media/channel_unittest.cc +++ b/talk/session/media/channel_unittest.cc @@ -31,7 +31,7 @@ #include "talk/media/base/rtpdump.h" #include "talk/media/base/screencastid.h" #include "talk/media/base/testutils.h" -#include "talk/p2p/base/fakesession.h" +#include "webrtc/p2p/base/fakesession.h" #include "talk/session/media/channel.h" #include "talk/session/media/mediamessages.h" #include "talk/session/media/mediarecorder.h" diff --git a/talk/session/media/channelmanager.h b/talk/session/media/channelmanager.h index 98a3c96a8..764451d0f 100644 --- a/talk/session/media/channelmanager.h +++ b/talk/session/media/channelmanager.h @@ -33,7 +33,7 @@ #include "talk/media/base/capturemanager.h" #include "talk/media/base/mediaengine.h" -#include "talk/p2p/base/session.h" +#include "webrtc/p2p/base/session.h" #include "talk/session/media/voicechannel.h" #include "webrtc/base/criticalsection.h" #include "webrtc/base/fileutils.h" diff --git a/talk/session/media/channelmanager_unittest.cc b/talk/session/media/channelmanager_unittest.cc index 13633646a..4c6f4ab03 100644 --- a/talk/session/media/channelmanager_unittest.cc +++ b/talk/session/media/channelmanager_unittest.cc @@ -29,7 +29,7 @@ #include "talk/media/base/nullvideorenderer.h" #include "talk/media/base/testutils.h" #include "talk/media/devices/fakedevicemanager.h" -#include "talk/p2p/base/fakesession.h" +#include "webrtc/p2p/base/fakesession.h" #include "talk/session/media/channelmanager.h" #include "webrtc/base/gunit.h" #include "webrtc/base/logging.h" diff --git a/talk/session/media/mediamessages.cc b/talk/session/media/mediamessages.cc index 6c9f68112..011fd7ddb 100644 --- a/talk/session/media/mediamessages.cc +++ b/talk/session/media/mediamessages.cc @@ -31,8 +31,8 @@ #include "talk/session/media/mediamessages.h" -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/parsing.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/parsing.h" #include "talk/session/media/mediasessionclient.h" #include "webrtc/libjingle/xmllite/xmlelement.h" #include "webrtc/base/logging.h" diff --git a/talk/session/media/mediamessages.h b/talk/session/media/mediamessages.h index 4a56e4b75..d918e3ead 100644 --- a/talk/session/media/mediamessages.h +++ b/talk/session/media/mediamessages.h @@ -41,8 +41,8 @@ #include "talk/media/base/mediachannel.h" // For RtpHeaderExtension #include "talk/media/base/streamparams.h" -#include "talk/p2p/base/parsing.h" -#include "talk/p2p/base/sessiondescription.h" +#include "webrtc/p2p/base/parsing.h" +#include "webrtc/p2p/base/sessiondescription.h" #include "webrtc/base/basictypes.h" namespace cricket { diff --git a/talk/session/media/mediamessages_unittest.cc b/talk/session/media/mediamessages_unittest.cc index 9ebd38c82..a9f00e47d 100644 --- a/talk/session/media/mediamessages_unittest.cc +++ b/talk/session/media/mediamessages_unittest.cc @@ -30,7 +30,7 @@ #include #include -#include "talk/p2p/base/constants.h" +#include "webrtc/p2p/base/constants.h" #include "talk/session/media/mediasessionclient.h" #include "webrtc/libjingle/xmllite/xmlelement.h" #include "webrtc/base/gunit.h" diff --git a/talk/session/media/mediarecorder_unittest.cc b/talk/session/media/mediarecorder_unittest.cc index 9feb22aae..b71a9845d 100644 --- a/talk/session/media/mediarecorder_unittest.cc +++ b/talk/session/media/mediarecorder_unittest.cc @@ -28,7 +28,7 @@ #include "talk/media/base/fakemediaengine.h" #include "talk/media/base/rtpdump.h" #include "talk/media/base/testutils.h" -#include "talk/p2p/base/fakesession.h" +#include "webrtc/p2p/base/fakesession.h" #include "talk/session/media/channel.h" #include "talk/session/media/mediarecorder.h" #include "webrtc/base/bytebuffer.h" diff --git a/talk/session/media/mediasession.cc b/talk/session/media/mediasession.cc index 79fc15144..96e45a6fc 100644 --- a/talk/session/media/mediasession.cc +++ b/talk/session/media/mediasession.cc @@ -34,10 +34,10 @@ #include "talk/media/base/constants.h" #include "talk/media/base/cryptoparams.h" -#include "talk/p2p/base/constants.h" +#include "webrtc/p2p/base/constants.h" #include "talk/session/media/channelmanager.h" #include "talk/session/media/srtpfilter.h" -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" #include "webrtc/base/helpers.h" #include "webrtc/base/logging.h" #include "webrtc/base/scoped_ptr.h" diff --git a/talk/session/media/mediasession.h b/talk/session/media/mediasession.h index 992439ccb..462ddd22e 100644 --- a/talk/session/media/mediasession.h +++ b/talk/session/media/mediasession.h @@ -40,9 +40,9 @@ #include "talk/media/base/mediachannel.h" #include "talk/media/base/mediaengine.h" // For DataChannelType #include "talk/media/base/streamparams.h" -#include "talk/p2p/base/sessiondescription.h" -#include "talk/p2p/base/transport.h" -#include "talk/p2p/base/transportdescriptionfactory.h" +#include "webrtc/p2p/base/sessiondescription.h" +#include "webrtc/p2p/base/transport.h" +#include "webrtc/p2p/base/transportdescriptionfactory.h" #include "webrtc/base/scoped_ptr.h" namespace cricket { diff --git a/talk/session/media/mediasession_unittest.cc b/talk/session/media/mediasession_unittest.cc index e3c678ee1..9ee60652d 100644 --- a/talk/session/media/mediasession_unittest.cc +++ b/talk/session/media/mediasession_unittest.cc @@ -30,9 +30,9 @@ #include "talk/media/base/codec.h" #include "talk/media/base/testutils.h" -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/transportdescription.h" -#include "talk/p2p/base/transportinfo.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/transportdescription.h" +#include "webrtc/p2p/base/transportinfo.h" #include "talk/session/media/mediasession.h" #include "talk/session/media/srtpfilter.h" #include "webrtc/base/fakesslidentity.h" diff --git a/talk/session/media/mediasessionclient.cc b/talk/session/media/mediasessionclient.cc index 826909aa6..5cb79175d 100644 --- a/talk/session/media/mediasessionclient.cc +++ b/talk/session/media/mediasessionclient.cc @@ -32,13 +32,13 @@ #include "talk/media/base/capturemanager.h" #include "talk/media/base/cryptoparams.h" #include "talk/media/sctp/sctpdataengine.h" -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/parsing.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/parsing.h" #include "talk/session/media/mediamessages.h" #include "talk/session/media/srtpfilter.h" #include "webrtc/libjingle/xmllite/qname.h" #include "webrtc/libjingle/xmllite/xmlconstants.h" -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" #include "webrtc/base/helpers.h" #include "webrtc/base/logging.h" #include "webrtc/base/stringencode.h" diff --git a/talk/session/media/mediasessionclient.h b/talk/session/media/mediasessionclient.h index 61d1f1d13..c66c221e4 100644 --- a/talk/session/media/mediasessionclient.h +++ b/talk/session/media/mediasessionclient.h @@ -33,10 +33,10 @@ #include #include #include "talk/media/base/cryptoparams.h" -#include "talk/p2p/base/session.h" -#include "talk/p2p/base/sessionclient.h" -#include "talk/p2p/base/sessiondescription.h" -#include "talk/p2p/base/sessionmanager.h" +#include "webrtc/p2p/base/session.h" +#include "webrtc/p2p/base/sessionclient.h" +#include "webrtc/p2p/base/sessiondescription.h" +#include "webrtc/p2p/base/sessionmanager.h" #include "talk/session/media/call.h" #include "talk/session/media/channelmanager.h" #include "talk/session/media/mediasession.h" diff --git a/talk/session/media/mediasessionclient_unittest.cc b/talk/session/media/mediasessionclient_unittest.cc index bb3043d71..3e8a90f74 100644 --- a/talk/session/media/mediasessionclient_unittest.cc +++ b/talk/session/media/mediasessionclient_unittest.cc @@ -31,13 +31,13 @@ #include "talk/media/base/fakemediaengine.h" #include "talk/media/base/testutils.h" #include "talk/media/devices/fakedevicemanager.h" -#include "talk/p2p/base/constants.h" -#include "talk/p2p/client/basicportallocator.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/client/basicportallocator.h" #include "talk/session/media/mediasessionclient.h" #include "webrtc/libjingle/xmllite/xmlbuilder.h" #include "webrtc/libjingle/xmllite/xmlelement.h" #include "webrtc/libjingle/xmllite/xmlprinter.h" -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" #include "webrtc/base/gunit.h" #include "webrtc/base/logging.h" #include "webrtc/base/scoped_ptr.h" diff --git a/talk/session/media/rtcpmuxfilter.h b/talk/session/media/rtcpmuxfilter.h index 40951a243..948b3c33f 100644 --- a/talk/session/media/rtcpmuxfilter.h +++ b/talk/session/media/rtcpmuxfilter.h @@ -28,7 +28,7 @@ #ifndef TALK_SESSION_MEDIA_RTCPMUXFILTER_H_ #define TALK_SESSION_MEDIA_RTCPMUXFILTER_H_ -#include "talk/p2p/base/sessiondescription.h" +#include "webrtc/p2p/base/sessiondescription.h" #include "webrtc/base/basictypes.h" namespace cricket { diff --git a/talk/session/media/srtpfilter.h b/talk/session/media/srtpfilter.h index ce3368f55..313441880 100644 --- a/talk/session/media/srtpfilter.h +++ b/talk/session/media/srtpfilter.h @@ -34,7 +34,7 @@ #include #include "talk/media/base/cryptoparams.h" -#include "talk/p2p/base/sessiondescription.h" +#include "webrtc/p2p/base/sessiondescription.h" #include "webrtc/base/basictypes.h" #include "webrtc/base/scoped_ptr.h" #include "webrtc/base/sigslotrepeater.h" diff --git a/talk/session/media/srtpfilter_unittest.cc b/talk/session/media/srtpfilter_unittest.cc index 8b859f88f..51d701d21 100644 --- a/talk/session/media/srtpfilter_unittest.cc +++ b/talk/session/media/srtpfilter_unittest.cc @@ -27,7 +27,7 @@ #include "talk/media/base/cryptoparams.h" #include "talk/media/base/fakertp.h" -#include "talk/p2p/base/sessiondescription.h" +#include "webrtc/p2p/base/sessiondescription.h" #include "talk/session/media/srtpfilter.h" #include "webrtc/base/byteorder.h" #include "webrtc/base/gunit.h" diff --git a/talk/session/media/typingmonitor_unittest.cc b/talk/session/media/typingmonitor_unittest.cc index 95fa7a4fb..66e2401ea 100644 --- a/talk/session/media/typingmonitor_unittest.cc +++ b/talk/session/media/typingmonitor_unittest.cc @@ -26,7 +26,7 @@ */ #include "talk/media/base/fakemediaengine.h" -#include "talk/p2p/base/fakesession.h" +#include "webrtc/p2p/base/fakesession.h" #include "talk/session/media/channel.h" #include "talk/session/media/currentspeakermonitor.h" #include "talk/session/media/typingmonitor.h" diff --git a/talk/session/tunnel/pseudotcpchannel.cc b/talk/session/tunnel/pseudotcpchannel.cc index f2776142e..861e1784e 100644 --- a/talk/session/tunnel/pseudotcpchannel.cc +++ b/talk/session/tunnel/pseudotcpchannel.cc @@ -27,8 +27,8 @@ #include #include "pseudotcpchannel.h" -#include "talk/p2p/base/candidate.h" -#include "talk/p2p/base/transportchannel.h" +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/transportchannel.h" #include "webrtc/base/basictypes.h" #include "webrtc/base/common.h" #include "webrtc/base/logging.h" diff --git a/talk/session/tunnel/pseudotcpchannel.h b/talk/session/tunnel/pseudotcpchannel.h index 5ccfa4c3e..ad6d907f5 100644 --- a/talk/session/tunnel/pseudotcpchannel.h +++ b/talk/session/tunnel/pseudotcpchannel.h @@ -28,8 +28,8 @@ #ifndef TALK_SESSION_TUNNEL_PSEUDOTCPCHANNEL_H_ #define TALK_SESSION_TUNNEL_PSEUDOTCPCHANNEL_H_ -#include "talk/p2p/base/pseudotcp.h" -#include "talk/p2p/base/session.h" +#include "webrtc/p2p/base/pseudotcp.h" +#include "webrtc/p2p/base/session.h" #include "webrtc/base/criticalsection.h" #include "webrtc/base/messagequeue.h" #include "webrtc/base/stream.h" diff --git a/talk/session/tunnel/securetunnelsessionclient.cc b/talk/session/tunnel/securetunnelsessionclient.cc index cb41c3baf..4ed818564 100644 --- a/talk/session/tunnel/securetunnelsessionclient.cc +++ b/talk/session/tunnel/securetunnelsessionclient.cc @@ -27,7 +27,7 @@ // SecureTunnelSessionClient and SecureTunnelSession implementation. -#include "talk/p2p/base/transportchannel.h" +#include "webrtc/p2p/base/transportchannel.h" #include "talk/session/tunnel/pseudotcpchannel.h" #include "talk/session/tunnel/securetunnelsessionclient.h" #include "webrtc/libjingle/xmllite/xmlelement.h" diff --git a/talk/session/tunnel/tunnelsessionclient.cc b/talk/session/tunnel/tunnelsessionclient.cc index 7221db404..7d2a7d1d2 100644 --- a/talk/session/tunnel/tunnelsessionclient.cc +++ b/talk/session/tunnel/tunnelsessionclient.cc @@ -26,8 +26,8 @@ */ #include "pseudotcpchannel.h" -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/transportchannel.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/transportchannel.h" #include "webrtc/libjingle/xmllite/xmlelement.h" #include "tunnelsessionclient.h" #include "webrtc/base/basicdefs.h" diff --git a/talk/session/tunnel/tunnelsessionclient.h b/talk/session/tunnel/tunnelsessionclient.h index d3fa64aa7..87beaed8b 100644 --- a/talk/session/tunnel/tunnelsessionclient.h +++ b/talk/session/tunnel/tunnelsessionclient.h @@ -30,14 +30,14 @@ #include -#include "talk/p2p/base/constants.h" -#include "talk/p2p/base/pseudotcp.h" -#include "talk/p2p/base/session.h" -#include "talk/p2p/base/sessionclient.h" -#include "talk/p2p/base/sessiondescription.h" -#include "talk/p2p/base/sessionmanager.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/pseudotcp.h" +#include "webrtc/p2p/base/session.h" +#include "webrtc/p2p/base/sessionclient.h" +#include "webrtc/p2p/base/sessiondescription.h" +#include "webrtc/p2p/base/sessionmanager.h" #include "webrtc/libjingle/xmllite/qname.h" -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" #include "webrtc/base/criticalsection.h" #include "webrtc/base/stream.h" diff --git a/talk/session/tunnel/tunnelsessionclient_unittest.cc b/talk/session/tunnel/tunnelsessionclient_unittest.cc index 6d46918cc..6bd746a40 100644 --- a/talk/session/tunnel/tunnelsessionclient_unittest.cc +++ b/talk/session/tunnel/tunnelsessionclient_unittest.cc @@ -26,9 +26,9 @@ */ #include -#include "talk/p2p/base/sessionmanager.h" -#include "talk/p2p/base/transport.h" -#include "talk/p2p/client/fakeportallocator.h" +#include "webrtc/p2p/base/sessionmanager.h" +#include "webrtc/p2p/base/transport.h" +#include "webrtc/p2p/client/fakeportallocator.h" #include "talk/session/tunnel/tunnelsessionclient.h" #include "webrtc/base/gunit.h" #include "webrtc/base/messagehandler.h" diff --git a/talk/xmpp/chatroommodule.h b/talk/xmpp/chatroommodule.h index 8358fc1e9..145ab89c1 100644 --- a/talk/xmpp/chatroommodule.h +++ b/talk/xmpp/chatroommodule.h @@ -28,8 +28,8 @@ #ifndef TALK_XMPP_CHATROOMMODULE_H_ #define TALK_XMPP_CHATROOMMODULE_H_ -#include "talk/xmpp/module.h" -#include "talk/xmpp/rostermodule.h" +#include "webrtc/libjingle/xmpp/module.h" +#include "webrtc/libjingle/xmpp/rostermodule.h" namespace buzz { diff --git a/talk/xmpp/chatroommoduleimpl.cc b/talk/xmpp/chatroommoduleimpl.cc index 45db014c6..33c752e85 100644 --- a/talk/xmpp/chatroommoduleimpl.cc +++ b/talk/xmpp/chatroommoduleimpl.cc @@ -31,9 +31,9 @@ #include #include #include -#include "talk/xmpp/chatroommodule.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/moduleimpl.h" +#include "webrtc/libjingle/xmpp/chatroommodule.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/moduleimpl.h" #include "webrtc/base/common.h" namespace buzz { diff --git a/talk/xmpp/constants.cc b/talk/xmpp/constants.cc index 297eafdb3..fce9c75d0 100644 --- a/talk/xmpp/constants.cc +++ b/talk/xmpp/constants.cc @@ -25,14 +25,14 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" #include #include "webrtc/libjingle/xmllite/qname.h" #include "webrtc/libjingle/xmllite/xmlconstants.h" #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/jid.h" #include "webrtc/base/basicdefs.h" namespace buzz { diff --git a/talk/xmpp/constants.h b/talk/xmpp/constants.h index 6aa1a54c3..52088cae4 100644 --- a/talk/xmpp/constants.h +++ b/talk/xmpp/constants.h @@ -30,7 +30,7 @@ #include #include "webrtc/libjingle/xmllite/qname.h" -#include "talk/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/jid.h" namespace buzz { diff --git a/talk/xmpp/discoitemsquerytask.cc b/talk/xmpp/discoitemsquerytask.cc index d900b8503..b739ba35a 100644 --- a/talk/xmpp/discoitemsquerytask.cc +++ b/talk/xmpp/discoitemsquerytask.cc @@ -25,9 +25,9 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/constants.h" -#include "talk/xmpp/discoitemsquerytask.h" -#include "talk/xmpp/xmpptask.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/discoitemsquerytask.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" #include "webrtc/base/scoped_ptr.h" namespace buzz { diff --git a/talk/xmpp/discoitemsquerytask.h b/talk/xmpp/discoitemsquerytask.h index 409fc900b..c31d2616b 100644 --- a/talk/xmpp/discoitemsquerytask.h +++ b/talk/xmpp/discoitemsquerytask.h @@ -54,7 +54,7 @@ #include #include -#include "talk/xmpp/iqtask.h" +#include "webrtc/libjingle/xmpp/iqtask.h" namespace buzz { diff --git a/talk/xmpp/fakexmppclient.h b/talk/xmpp/fakexmppclient.h index 3522ba930..2e37dd194 100644 --- a/talk/xmpp/fakexmppclient.h +++ b/talk/xmpp/fakexmppclient.h @@ -33,7 +33,7 @@ #include #include -#include "talk/xmpp/xmpptask.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" namespace buzz { diff --git a/talk/xmpp/hangoutpubsubclient.cc b/talk/xmpp/hangoutpubsubclient.cc index 63f5bcfb3..dacccf1a0 100644 --- a/talk/xmpp/hangoutpubsubclient.cc +++ b/talk/xmpp/hangoutpubsubclient.cc @@ -25,12 +25,12 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/hangoutpubsubclient.h" +#include "webrtc/libjingle/xmpp/hangoutpubsubclient.h" #include "webrtc/libjingle/xmllite/qname.h" #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/jid.h" #include "webrtc/base/logging.h" diff --git a/talk/xmpp/hangoutpubsubclient.h b/talk/xmpp/hangoutpubsubclient.h index 5692fc667..03c96f2e9 100644 --- a/talk/xmpp/hangoutpubsubclient.h +++ b/talk/xmpp/hangoutpubsubclient.h @@ -32,9 +32,9 @@ #include #include -#include "talk/xmpp/jid.h" -#include "talk/xmpp/pubsubclient.h" -#include "talk/xmpp/pubsubstateclient.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/pubsubclient.h" +#include "webrtc/libjingle/xmpp/pubsubstateclient.h" #include "webrtc/base/scoped_ptr.h" #include "webrtc/base/sigslot.h" #include "webrtc/base/sigslotrepeater.h" diff --git a/talk/xmpp/hangoutpubsubclient_unittest.cc b/talk/xmpp/hangoutpubsubclient_unittest.cc index 555ee5c34..574e21a84 100644 --- a/talk/xmpp/hangoutpubsubclient_unittest.cc +++ b/talk/xmpp/hangoutpubsubclient_unittest.cc @@ -5,10 +5,10 @@ #include "webrtc/libjingle/xmllite/qname.h" #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/fakexmppclient.h" -#include "talk/xmpp/hangoutpubsubclient.h" -#include "talk/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/hangoutpubsubclient.h" +#include "webrtc/libjingle/xmpp/jid.h" #include "webrtc/base/faketaskrunner.h" #include "webrtc/base/gunit.h" #include "webrtc/base/sigslot.h" diff --git a/talk/xmpp/iqtask.cc b/talk/xmpp/iqtask.cc index f6a21d478..d4cf310c4 100644 --- a/talk/xmpp/iqtask.cc +++ b/talk/xmpp/iqtask.cc @@ -25,10 +25,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/iqtask.h" +#include "webrtc/libjingle/xmpp/iqtask.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/xmppclient.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/xmppclient.h" namespace buzz { diff --git a/talk/xmpp/iqtask.h b/talk/xmpp/iqtask.h index 312c4af5f..8b0c04b09 100644 --- a/talk/xmpp/iqtask.h +++ b/talk/xmpp/iqtask.h @@ -30,8 +30,8 @@ #include -#include "talk/xmpp/xmppengine.h" -#include "talk/xmpp/xmpptask.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" namespace buzz { diff --git a/talk/xmpp/jid.cc b/talk/xmpp/jid.cc index 702e477dd..0410458a4 100644 --- a/talk/xmpp/jid.cc +++ b/talk/xmpp/jid.cc @@ -25,14 +25,14 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/jid.h" #include #include #include -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" #include "webrtc/base/common.h" #include "webrtc/base/logging.h" diff --git a/talk/xmpp/jid_unittest.cc b/talk/xmpp/jid_unittest.cc index 05835cf83..9cae2f856 100644 --- a/talk/xmpp/jid_unittest.cc +++ b/talk/xmpp/jid_unittest.cc @@ -1,7 +1,7 @@ // Copyright 2004 Google Inc. All Rights Reserved -#include "talk/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/jid.h" #include "webrtc/base/gunit.h" using buzz::Jid; diff --git a/talk/xmpp/jingleinfotask.cc b/talk/xmpp/jingleinfotask.cc index 3ceae07ba..3b8fdde03 100644 --- a/talk/xmpp/jingleinfotask.cc +++ b/talk/xmpp/jingleinfotask.cc @@ -25,11 +25,11 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/jingleinfotask.h" +#include "webrtc/libjingle/xmpp/jingleinfotask.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/xmppclient.h" -#include "talk/xmpp/xmpptask.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/xmppclient.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" #include "webrtc/base/socketaddress.h" namespace buzz { diff --git a/talk/xmpp/jingleinfotask.h b/talk/xmpp/jingleinfotask.h index 18cb5a9f4..ac03ecfec 100644 --- a/talk/xmpp/jingleinfotask.h +++ b/talk/xmpp/jingleinfotask.h @@ -30,9 +30,9 @@ #include -#include "talk/p2p/client/httpportallocator.h" -#include "talk/xmpp/xmppengine.h" -#include "talk/xmpp/xmpptask.h" +#include "webrtc/p2p/client/httpportallocator.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" #include "webrtc/base/sigslot.h" namespace buzz { diff --git a/talk/xmpp/module.h b/talk/xmpp/module.h index a5d06877b..f1cff1191 100644 --- a/talk/xmpp/module.h +++ b/talk/xmpp/module.h @@ -28,7 +28,7 @@ #ifndef TALK_XMPP_MODULE_H_ #define TALK_XMPP_MODULE_H_ -#include "talk/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" namespace buzz { diff --git a/talk/xmpp/moduleimpl.cc b/talk/xmpp/moduleimpl.cc index 4635bee70..8ac755ded 100644 --- a/talk/xmpp/moduleimpl.cc +++ b/talk/xmpp/moduleimpl.cc @@ -25,7 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/moduleimpl.h" +#include "webrtc/libjingle/xmpp/moduleimpl.h" #include "webrtc/base/common.h" namespace buzz { diff --git a/talk/xmpp/moduleimpl.h b/talk/xmpp/moduleimpl.h index 897bfce72..f1986f3bb 100644 --- a/talk/xmpp/moduleimpl.h +++ b/talk/xmpp/moduleimpl.h @@ -28,8 +28,8 @@ #ifndef TALK_XMPP_MODULEIMPL_H_ #define TALK_XMPP_MODULEIMPL_H_ -#include "talk/xmpp/module.h" -#include "talk/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/module.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" namespace buzz { diff --git a/talk/xmpp/mucroomconfigtask.cc b/talk/xmpp/mucroomconfigtask.cc index 536eb9bf2..86ba5f6b8 100644 --- a/talk/xmpp/mucroomconfigtask.cc +++ b/talk/xmpp/mucroomconfigtask.cc @@ -28,9 +28,9 @@ #include #include -#include "talk/xmpp/mucroomconfigtask.h" +#include "webrtc/libjingle/xmpp/mucroomconfigtask.h" -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" #include "webrtc/base/scoped_ptr.h" namespace buzz { diff --git a/talk/xmpp/mucroomconfigtask.h b/talk/xmpp/mucroomconfigtask.h index ba0dbaa27..5626d3e9c 100644 --- a/talk/xmpp/mucroomconfigtask.h +++ b/talk/xmpp/mucroomconfigtask.h @@ -29,7 +29,7 @@ #define TALK_XMPP_MUCROOMCONFIGTASK_H_ #include -#include "talk/xmpp/iqtask.h" +#include "webrtc/libjingle/xmpp/iqtask.h" namespace buzz { diff --git a/talk/xmpp/mucroomconfigtask_unittest.cc b/talk/xmpp/mucroomconfigtask_unittest.cc index bf5e7b4fa..e9e1281b9 100644 --- a/talk/xmpp/mucroomconfigtask_unittest.cc +++ b/talk/xmpp/mucroomconfigtask_unittest.cc @@ -29,9 +29,9 @@ #include #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/fakexmppclient.h" -#include "talk/xmpp/mucroomconfigtask.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/mucroomconfigtask.h" #include "webrtc/base/faketaskrunner.h" #include "webrtc/base/gunit.h" #include "webrtc/base/sigslot.h" diff --git a/talk/xmpp/mucroomdiscoverytask.cc b/talk/xmpp/mucroomdiscoverytask.cc index c7477ae3e..0ec4fbe7c 100644 --- a/talk/xmpp/mucroomdiscoverytask.cc +++ b/talk/xmpp/mucroomdiscoverytask.cc @@ -25,9 +25,9 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/mucroomdiscoverytask.h" +#include "webrtc/libjingle/xmpp/mucroomdiscoverytask.h" -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" namespace buzz { diff --git a/talk/xmpp/mucroomdiscoverytask.h b/talk/xmpp/mucroomdiscoverytask.h index 4097cc624..dc5918d0c 100644 --- a/talk/xmpp/mucroomdiscoverytask.h +++ b/talk/xmpp/mucroomdiscoverytask.h @@ -30,7 +30,7 @@ #include #include -#include "talk/xmpp/iqtask.h" +#include "webrtc/libjingle/xmpp/iqtask.h" namespace buzz { diff --git a/talk/xmpp/mucroomdiscoverytask_unittest.cc b/talk/xmpp/mucroomdiscoverytask_unittest.cc index e1a633efb..77fdf86c6 100644 --- a/talk/xmpp/mucroomdiscoverytask_unittest.cc +++ b/talk/xmpp/mucroomdiscoverytask_unittest.cc @@ -29,9 +29,9 @@ #include #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/fakexmppclient.h" -#include "talk/xmpp/mucroomdiscoverytask.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/mucroomdiscoverytask.h" #include "webrtc/base/faketaskrunner.h" #include "webrtc/base/gunit.h" #include "webrtc/base/sigslot.h" diff --git a/talk/xmpp/mucroomlookuptask.cc b/talk/xmpp/mucroomlookuptask.cc index 8e1895c68..6832adeed 100644 --- a/talk/xmpp/mucroomlookuptask.cc +++ b/talk/xmpp/mucroomlookuptask.cc @@ -25,9 +25,9 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/mucroomlookuptask.h" +#include "webrtc/libjingle/xmpp/mucroomlookuptask.h" -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" #include "webrtc/base/logging.h" #include "webrtc/base/scoped_ptr.h" diff --git a/talk/xmpp/mucroomlookuptask.h b/talk/xmpp/mucroomlookuptask.h index 48f2484c7..9bcca2c93 100644 --- a/talk/xmpp/mucroomlookuptask.h +++ b/talk/xmpp/mucroomlookuptask.h @@ -29,7 +29,7 @@ #define TALK_XMPP_MUCROOMLOOKUPTASK_H_ #include -#include "talk/xmpp/iqtask.h" +#include "webrtc/libjingle/xmpp/iqtask.h" namespace buzz { diff --git a/talk/xmpp/mucroomlookuptask_unittest.cc b/talk/xmpp/mucroomlookuptask_unittest.cc index 03be29245..961e66495 100644 --- a/talk/xmpp/mucroomlookuptask_unittest.cc +++ b/talk/xmpp/mucroomlookuptask_unittest.cc @@ -29,9 +29,9 @@ #include #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/fakexmppclient.h" -#include "talk/xmpp/mucroomlookuptask.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/mucroomlookuptask.h" #include "webrtc/base/faketaskrunner.h" #include "webrtc/base/gunit.h" #include "webrtc/base/sigslot.h" diff --git a/talk/xmpp/mucroomuniquehangoutidtask.cc b/talk/xmpp/mucroomuniquehangoutidtask.cc index 78a8edf27..db94741d9 100644 --- a/talk/xmpp/mucroomuniquehangoutidtask.cc +++ b/talk/xmpp/mucroomuniquehangoutidtask.cc @@ -1,9 +1,9 @@ // Copyright 2012 Google Inc. All Rights Reserved. -#include "talk/xmpp/mucroomuniquehangoutidtask.h" +#include "webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.h" -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" namespace buzz { diff --git a/talk/xmpp/mucroomuniquehangoutidtask.h b/talk/xmpp/mucroomuniquehangoutidtask.h index d222bacdd..83de43480 100644 --- a/talk/xmpp/mucroomuniquehangoutidtask.h +++ b/talk/xmpp/mucroomuniquehangoutidtask.h @@ -4,7 +4,7 @@ #ifndef TALK_XMPP_MUCROOMUNIQUEHANGOUTIDTASK_H_ #define TALK_XMPP_MUCROOMUNIQUEHANGOUTIDTASK_H_ -#include "talk/xmpp/iqtask.h" +#include "webrtc/libjingle/xmpp/iqtask.h" namespace buzz { diff --git a/talk/xmpp/mucroomuniquehangoutidtask_unittest.cc b/talk/xmpp/mucroomuniquehangoutidtask_unittest.cc index 42bed132b..d8f7c2037 100644 --- a/talk/xmpp/mucroomuniquehangoutidtask_unittest.cc +++ b/talk/xmpp/mucroomuniquehangoutidtask_unittest.cc @@ -29,9 +29,9 @@ #include #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/fakexmppclient.h" -#include "talk/xmpp/mucroomuniquehangoutidtask.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.h" #include "webrtc/base/faketaskrunner.h" #include "webrtc/base/gunit.h" #include "webrtc/base/sigslot.h" diff --git a/talk/xmpp/pingtask.cc b/talk/xmpp/pingtask.cc index 7922ea04c..619c3a8d9 100644 --- a/talk/xmpp/pingtask.cc +++ b/talk/xmpp/pingtask.cc @@ -1,9 +1,9 @@ // Copyright 2011 Google Inc. All Rights Reserved. -#include "talk/xmpp/pingtask.h" +#include "webrtc/libjingle/xmpp/pingtask.h" -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" #include "webrtc/base/logging.h" #include "webrtc/base/scoped_ptr.h" diff --git a/talk/xmpp/pingtask.h b/talk/xmpp/pingtask.h index 2a869a3a7..87d74b4b3 100644 --- a/talk/xmpp/pingtask.h +++ b/talk/xmpp/pingtask.h @@ -28,7 +28,7 @@ #ifndef TALK_XMPP_PINGTASK_H_ #define TALK_XMPP_PINGTASK_H_ -#include "talk/xmpp/xmpptask.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" #include "webrtc/base/messagehandler.h" #include "webrtc/base/messagequeue.h" diff --git a/talk/xmpp/pingtask_unittest.cc b/talk/xmpp/pingtask_unittest.cc index fe88a5cc9..a0b6618c0 100644 --- a/talk/xmpp/pingtask_unittest.cc +++ b/talk/xmpp/pingtask_unittest.cc @@ -29,9 +29,9 @@ #include #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/fakexmppclient.h" -#include "talk/xmpp/pingtask.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/pingtask.h" #include "webrtc/base/faketaskrunner.h" #include "webrtc/base/gunit.h" #include "webrtc/base/sigslot.h" diff --git a/talk/xmpp/plainsaslhandler.h b/talk/xmpp/plainsaslhandler.h index 31032e40d..f23782d65 100644 --- a/talk/xmpp/plainsaslhandler.h +++ b/talk/xmpp/plainsaslhandler.h @@ -29,8 +29,8 @@ #define TALK_XMPP_PLAINSASLHANDLER_H_ #include -#include "talk/xmpp/saslhandler.h" -#include "talk/xmpp/saslplainmechanism.h" +#include "webrtc/libjingle/xmpp/saslhandler.h" +#include "webrtc/libjingle/xmpp/saslplainmechanism.h" #include "webrtc/base/cryptstring.h" namespace buzz { diff --git a/talk/xmpp/presenceouttask.cc b/talk/xmpp/presenceouttask.cc index a77d9d6c6..39ef6466c 100644 --- a/talk/xmpp/presenceouttask.cc +++ b/talk/xmpp/presenceouttask.cc @@ -27,9 +27,9 @@ #include #include -#include "talk/xmpp/constants.h" -#include "talk/xmpp/presenceouttask.h" -#include "talk/xmpp/xmppclient.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/presenceouttask.h" +#include "webrtc/libjingle/xmpp/xmppclient.h" #include "webrtc/base/stringencode.h" namespace buzz { diff --git a/talk/xmpp/presenceouttask.h b/talk/xmpp/presenceouttask.h index 53bbae5bf..f6b04afe5 100644 --- a/talk/xmpp/presenceouttask.h +++ b/talk/xmpp/presenceouttask.h @@ -28,9 +28,9 @@ #ifndef TALK_XMPP_PRESENCEOUTTASK_H_ #define TALK_XMPP_PRESENCEOUTTASK_H_ -#include "talk/xmpp/presencestatus.h" -#include "talk/xmpp/xmppengine.h" -#include "talk/xmpp/xmpptask.h" +#include "webrtc/libjingle/xmpp/presencestatus.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" namespace buzz { diff --git a/talk/xmpp/presencereceivetask.cc b/talk/xmpp/presencereceivetask.cc index 940c53b69..0a159ac7b 100644 --- a/talk/xmpp/presencereceivetask.cc +++ b/talk/xmpp/presencereceivetask.cc @@ -25,9 +25,9 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/presencereceivetask.h" +#include "webrtc/libjingle/xmpp/presencereceivetask.h" -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" #include "webrtc/base/stringencode.h" namespace buzz { diff --git a/talk/xmpp/presencereceivetask.h b/talk/xmpp/presencereceivetask.h index 6a090f3a4..80e11d8d3 100644 --- a/talk/xmpp/presencereceivetask.h +++ b/talk/xmpp/presencereceivetask.h @@ -30,8 +30,8 @@ #include "webrtc/base/sigslot.h" -#include "talk/xmpp/presencestatus.h" -#include "talk/xmpp/xmpptask.h" +#include "webrtc/libjingle/xmpp/presencestatus.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" namespace buzz { diff --git a/talk/xmpp/presencestatus.cc b/talk/xmpp/presencestatus.cc index c75b70546..2ab89b23b 100644 --- a/talk/xmpp/presencestatus.cc +++ b/talk/xmpp/presencestatus.cc @@ -25,7 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/presencestatus.h" +#include "webrtc/libjingle/xmpp/presencestatus.h" namespace buzz { PresenceStatus::PresenceStatus() diff --git a/talk/xmpp/presencestatus.h b/talk/xmpp/presencestatus.h index 45c547129..c133e400b 100644 --- a/talk/xmpp/presencestatus.h +++ b/talk/xmpp/presencestatus.h @@ -28,8 +28,8 @@ #ifndef THIRD_PARTY_LIBJINGLE_FILES_TALK_XMPP_PRESENCESTATUS_H_ #define THIRD_PARTY_LIBJINGLE_FILES_TALK_XMPP_PRESENCESTATUS_H_ -#include "talk/xmpp/constants.h" -#include "talk/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/jid.h" namespace buzz { diff --git a/talk/xmpp/prexmppauth.h b/talk/xmpp/prexmppauth.h index 71fcae413..7ddc85bf6 100644 --- a/talk/xmpp/prexmppauth.h +++ b/talk/xmpp/prexmppauth.h @@ -28,7 +28,7 @@ #ifndef TALK_XMPP_PREXMPPAUTH_H_ #define TALK_XMPP_PREXMPPAUTH_H_ -#include "talk/xmpp/saslhandler.h" +#include "webrtc/libjingle/xmpp/saslhandler.h" #include "webrtc/base/cryptstring.h" #include "webrtc/base/sigslot.h" diff --git a/talk/xmpp/pubsub_task.cc b/talk/xmpp/pubsub_task.cc index 3552764a9..98446fd58 100644 --- a/talk/xmpp/pubsub_task.cc +++ b/talk/xmpp/pubsub_task.cc @@ -25,13 +25,13 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/pubsub_task.h" +#include "webrtc/libjingle/xmpp/pubsub_task.h" #include #include -#include "talk/xmpp/constants.h" -#include "talk/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" #include "webrtc/base/common.h" namespace buzz { diff --git a/talk/xmpp/pubsub_task.h b/talk/xmpp/pubsub_task.h index 2787cbc9b..a219b6009 100644 --- a/talk/xmpp/pubsub_task.h +++ b/talk/xmpp/pubsub_task.h @@ -31,8 +31,8 @@ #include #include #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/jid.h" -#include "talk/xmpp/xmpptask.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" namespace buzz { diff --git a/talk/xmpp/pubsubclient.cc b/talk/xmpp/pubsubclient.cc index b62758710..a5793efae 100644 --- a/talk/xmpp/pubsubclient.cc +++ b/talk/xmpp/pubsubclient.cc @@ -25,14 +25,14 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/pubsubclient.h" +#include "webrtc/libjingle/xmpp/pubsubclient.h" #include #include -#include "talk/xmpp/constants.h" -#include "talk/xmpp/jid.h" -#include "talk/xmpp/pubsubtasks.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/pubsubtasks.h" namespace buzz { diff --git a/talk/xmpp/pubsubclient.h b/talk/xmpp/pubsubclient.h index d7b5066ae..64659577c 100644 --- a/talk/xmpp/pubsubclient.h +++ b/talk/xmpp/pubsubclient.h @@ -31,8 +31,8 @@ #include #include -#include "talk/xmpp/jid.h" -#include "talk/xmpp/pubsubtasks.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/pubsubtasks.h" #include "webrtc/base/sigslot.h" #include "webrtc/base/sigslotrepeater.h" #include "webrtc/base/task.h" diff --git a/talk/xmpp/pubsubclient_unittest.cc b/talk/xmpp/pubsubclient_unittest.cc index f191a18d1..b3049d809 100644 --- a/talk/xmpp/pubsubclient_unittest.cc +++ b/talk/xmpp/pubsubclient_unittest.cc @@ -5,10 +5,10 @@ #include "webrtc/libjingle/xmllite/qname.h" #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/fakexmppclient.h" -#include "talk/xmpp/jid.h" -#include "talk/xmpp/pubsubclient.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/pubsubclient.h" #include "webrtc/base/faketaskrunner.h" #include "webrtc/base/gunit.h" #include "webrtc/base/sigslot.h" diff --git a/talk/xmpp/pubsubstateclient.cc b/talk/xmpp/pubsubstateclient.cc index 5cd7b1a70..2ed7b20c9 100644 --- a/talk/xmpp/pubsubstateclient.cc +++ b/talk/xmpp/pubsubstateclient.cc @@ -25,7 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/pubsubstateclient.h" +#include "webrtc/libjingle/xmpp/pubsubstateclient.h" namespace buzz { diff --git a/talk/xmpp/pubsubstateclient.h b/talk/xmpp/pubsubstateclient.h index 09ef0f454..79b1329e9 100644 --- a/talk/xmpp/pubsubstateclient.h +++ b/talk/xmpp/pubsubstateclient.h @@ -34,9 +34,9 @@ #include "webrtc/libjingle/xmllite/qname.h" #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/jid.h" -#include "talk/xmpp/pubsubclient.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/pubsubclient.h" #include "webrtc/base/scoped_ptr.h" #include "webrtc/base/sigslot.h" #include "webrtc/base/sigslotrepeater.h" diff --git a/talk/xmpp/pubsubtasks.cc b/talk/xmpp/pubsubtasks.cc index 015708eb5..091a7ad96 100644 --- a/talk/xmpp/pubsubtasks.cc +++ b/talk/xmpp/pubsubtasks.cc @@ -25,13 +25,13 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/pubsubtasks.h" +#include "webrtc/libjingle/xmpp/pubsubtasks.h" #include #include -#include "talk/xmpp/constants.h" -#include "talk/xmpp/receivetask.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/receivetask.h" // An implementation of the tasks for XEP-0060 // (http://xmpp.org/extensions/xep-0060.html). diff --git a/talk/xmpp/pubsubtasks.h b/talk/xmpp/pubsubtasks.h index 94be276a4..280c61309 100644 --- a/talk/xmpp/pubsubtasks.h +++ b/talk/xmpp/pubsubtasks.h @@ -30,8 +30,8 @@ #include -#include "talk/xmpp/iqtask.h" -#include "talk/xmpp/receivetask.h" +#include "webrtc/libjingle/xmpp/iqtask.h" +#include "webrtc/libjingle/xmpp/receivetask.h" #include "webrtc/base/sigslot.h" namespace buzz { diff --git a/talk/xmpp/pubsubtasks_unittest.cc b/talk/xmpp/pubsubtasks_unittest.cc index 48cd04ab8..7ea21e73d 100644 --- a/talk/xmpp/pubsubtasks_unittest.cc +++ b/talk/xmpp/pubsubtasks_unittest.cc @@ -5,11 +5,11 @@ #include "webrtc/libjingle/xmllite/qname.h" #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/fakexmppclient.h" -#include "talk/xmpp/iqtask.h" -#include "talk/xmpp/jid.h" -#include "talk/xmpp/pubsubtasks.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/iqtask.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/pubsubtasks.h" #include "webrtc/base/faketaskrunner.h" #include "webrtc/base/gunit.h" #include "webrtc/base/sigslot.h" diff --git a/talk/xmpp/receivetask.cc b/talk/xmpp/receivetask.cc index c2fb244ef..4c4fae5b6 100644 --- a/talk/xmpp/receivetask.cc +++ b/talk/xmpp/receivetask.cc @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/constants.h" -#include "talk/xmpp/receivetask.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/receivetask.h" namespace buzz { diff --git a/talk/xmpp/receivetask.h b/talk/xmpp/receivetask.h index b18e0f0bb..7055339e2 100644 --- a/talk/xmpp/receivetask.h +++ b/talk/xmpp/receivetask.h @@ -28,7 +28,7 @@ #ifndef TALK_XMPP_RECEIVETASK_H_ #define TALK_XMPP_RECEIVETASK_H_ -#include "talk/xmpp/xmpptask.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" namespace buzz { diff --git a/talk/xmpp/rostermodule.h b/talk/xmpp/rostermodule.h index dfb647d6a..9ae7f70cc 100644 --- a/talk/xmpp/rostermodule.h +++ b/talk/xmpp/rostermodule.h @@ -28,7 +28,7 @@ #ifndef TALK_XMPP_ROSTERMODULE_H_ #define TALK_XMPP_ROSTERMODULE_H_ -#include "talk/xmpp/module.h" +#include "webrtc/libjingle/xmpp/module.h" namespace buzz { diff --git a/talk/xmpp/rostermodule_unittest.cc b/talk/xmpp/rostermodule_unittest.cc index cb7f773b4..fb82546c8 100644 --- a/talk/xmpp/rostermodule_unittest.cc +++ b/talk/xmpp/rostermodule_unittest.cc @@ -30,10 +30,10 @@ #include #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/rostermodule.h" -#include "talk/xmpp/util_unittest.h" -#include "talk/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/rostermodule.h" +#include "webrtc/libjingle/xmpp/util_unittest.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" #include "webrtc/base/gunit.h" #include "webrtc/base/scoped_ptr.h" diff --git a/talk/xmpp/rostermoduleimpl.cc b/talk/xmpp/rostermoduleimpl.cc index e52e78e35..8841db6f4 100644 --- a/talk/xmpp/rostermoduleimpl.cc +++ b/talk/xmpp/rostermoduleimpl.cc @@ -31,8 +31,8 @@ #include #include #include -#include "talk/xmpp/constants.h" -#include "talk/xmpp/rostermoduleimpl.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/rostermoduleimpl.h" #include "webrtc/base/common.h" #include "webrtc/base/stringencode.h" diff --git a/talk/xmpp/rostermoduleimpl.h b/talk/xmpp/rostermoduleimpl.h index 37d1117cc..efef5eda4 100644 --- a/talk/xmpp/rostermoduleimpl.h +++ b/talk/xmpp/rostermoduleimpl.h @@ -28,8 +28,8 @@ #ifndef TALK_XMPP_XMPPTHREAD_H_ #define TALK_XMPP_XMPPTHREAD_H_ -#include "talk/xmpp/moduleimpl.h" -#include "talk/xmpp/rostermodule.h" +#include "webrtc/libjingle/xmpp/moduleimpl.h" +#include "webrtc/libjingle/xmpp/rostermodule.h" namespace buzz { diff --git a/talk/xmpp/saslcookiemechanism.h b/talk/xmpp/saslcookiemechanism.h index eda142c96..0f193f243 100644 --- a/talk/xmpp/saslcookiemechanism.h +++ b/talk/xmpp/saslcookiemechanism.h @@ -30,8 +30,8 @@ #include "webrtc/libjingle/xmllite/qname.h" #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/saslmechanism.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/saslmechanism.h" namespace buzz { diff --git a/talk/xmpp/saslmechanism.cc b/talk/xmpp/saslmechanism.cc index 887708429..9fb01bc30 100644 --- a/talk/xmpp/saslmechanism.cc +++ b/talk/xmpp/saslmechanism.cc @@ -26,8 +26,8 @@ */ #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/saslmechanism.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/saslmechanism.h" #include "webrtc/base/base64.h" using rtc::Base64; diff --git a/talk/xmpp/saslplainmechanism.h b/talk/xmpp/saslplainmechanism.h index 36b9bb9bc..c9d428e71 100644 --- a/talk/xmpp/saslplainmechanism.h +++ b/talk/xmpp/saslplainmechanism.h @@ -28,7 +28,7 @@ #ifndef TALK_XMPP_SASLPLAINMECHANISM_H_ #define TALK_XMPP_SASLPLAINMECHANISM_H_ -#include "talk/xmpp/saslmechanism.h" +#include "webrtc/libjingle/xmpp/saslmechanism.h" #include "webrtc/base/cryptstring.h" namespace buzz { diff --git a/talk/xmpp/util_unittest.cc b/talk/xmpp/util_unittest.cc index 3e47d3f66..d245efe1d 100644 --- a/talk/xmpp/util_unittest.cc +++ b/talk/xmpp/util_unittest.cc @@ -5,8 +5,8 @@ #include #include #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/util_unittest.h" -#include "talk/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/util_unittest.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" #include "webrtc/base/gunit.h" namespace buzz { diff --git a/talk/xmpp/util_unittest.h b/talk/xmpp/util_unittest.h index c9377ffc6..806b505d4 100644 --- a/talk/xmpp/util_unittest.h +++ b/talk/xmpp/util_unittest.h @@ -30,7 +30,7 @@ #include #include -#include "talk/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" namespace buzz { diff --git a/talk/xmpp/xmppauth.cc b/talk/xmpp/xmppauth.cc index d828475fa..255f3a7ef 100644 --- a/talk/xmpp/xmppauth.cc +++ b/talk/xmpp/xmppauth.cc @@ -25,13 +25,13 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/xmppauth.h" +#include "webrtc/libjingle/xmpp/xmppauth.h" #include -#include "talk/xmpp/constants.h" -#include "talk/xmpp/saslcookiemechanism.h" -#include "talk/xmpp/saslplainmechanism.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/saslcookiemechanism.h" +#include "webrtc/libjingle/xmpp/saslplainmechanism.h" XmppAuth::XmppAuth() : done_(false) { } diff --git a/talk/xmpp/xmppauth.h b/talk/xmpp/xmppauth.h index fec73e612..9fabd5ea8 100644 --- a/talk/xmpp/xmppauth.h +++ b/talk/xmpp/xmppauth.h @@ -30,9 +30,9 @@ #include -#include "talk/xmpp/jid.h" -#include "talk/xmpp/prexmppauth.h" -#include "talk/xmpp/saslhandler.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/prexmppauth.h" +#include "webrtc/libjingle/xmpp/saslhandler.h" #include "webrtc/base/cryptstring.h" #include "webrtc/base/sigslot.h" diff --git a/talk/xmpp/xmppclient.cc b/talk/xmpp/xmppclient.cc index 27aa5e493..66d1e970e 100644 --- a/talk/xmpp/xmppclient.cc +++ b/talk/xmpp/xmppclient.cc @@ -25,12 +25,12 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/xmppclient.h" +#include "webrtc/libjingle/xmpp/xmppclient.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/plainsaslhandler.h" -#include "talk/xmpp/prexmppauth.h" -#include "talk/xmpp/saslplainmechanism.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/plainsaslhandler.h" +#include "webrtc/libjingle/xmpp/prexmppauth.h" +#include "webrtc/libjingle/xmpp/saslplainmechanism.h" #include "webrtc/base/logging.h" #include "webrtc/base/scoped_ptr.h" #include "webrtc/base/sigslot.h" diff --git a/talk/xmpp/xmppclient.h b/talk/xmpp/xmppclient.h index 53b2d24f4..74e403053 100644 --- a/talk/xmpp/xmppclient.h +++ b/talk/xmpp/xmppclient.h @@ -29,10 +29,10 @@ #define TALK_XMPP_XMPPCLIENT_H_ #include -#include "talk/xmpp/asyncsocket.h" -#include "talk/xmpp/xmppclientsettings.h" -#include "talk/xmpp/xmppengine.h" -#include "talk/xmpp/xmpptask.h" +#include "webrtc/libjingle/xmpp/asyncsocket.h" +#include "webrtc/libjingle/xmpp/xmppclientsettings.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" #include "webrtc/base/basicdefs.h" #include "webrtc/base/sigslot.h" #include "webrtc/base/task.h" diff --git a/talk/xmpp/xmppclientsettings.h b/talk/xmpp/xmppclientsettings.h index 50028b7c7..04acf36f3 100644 --- a/talk/xmpp/xmppclientsettings.h +++ b/talk/xmpp/xmppclientsettings.h @@ -28,8 +28,8 @@ #ifndef TALK_XMPP_XMPPCLIENTSETTINGS_H_ #define TALK_XMPP_XMPPCLIENTSETTINGS_H_ -#include "talk/p2p/base/port.h" -#include "talk/xmpp/xmppengine.h" +#include "webrtc/p2p/base/port.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" #include "webrtc/base/cryptstring.h" namespace buzz { diff --git a/talk/xmpp/xmppengine.h b/talk/xmpp/xmppengine.h index 461e90f7b..1806ba9da 100644 --- a/talk/xmpp/xmppengine.h +++ b/talk/xmpp/xmppengine.h @@ -31,7 +31,7 @@ // also part of the API #include "webrtc/libjingle/xmllite/qname.h" #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/jid.h" namespace buzz { diff --git a/talk/xmpp/xmppengine_unittest.cc b/talk/xmpp/xmppengine_unittest.cc index b519a65eb..6b0dc9670 100644 --- a/talk/xmpp/xmppengine_unittest.cc +++ b/talk/xmpp/xmppengine_unittest.cc @@ -5,11 +5,11 @@ #include #include #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/plainsaslhandler.h" -#include "talk/xmpp/saslplainmechanism.h" -#include "talk/xmpp/util_unittest.h" -#include "talk/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/plainsaslhandler.h" +#include "webrtc/libjingle/xmpp/saslplainmechanism.h" +#include "webrtc/libjingle/xmpp/util_unittest.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" #include "webrtc/base/common.h" #include "webrtc/base/gunit.h" diff --git a/talk/xmpp/xmppengineimpl.cc b/talk/xmpp/xmppengineimpl.cc index 5de9de710..98d89e226 100644 --- a/talk/xmpp/xmppengineimpl.cc +++ b/talk/xmpp/xmppengineimpl.cc @@ -25,7 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/xmppengineimpl.h" +#include "webrtc/libjingle/xmpp/xmppengineimpl.h" #include #include @@ -33,9 +33,9 @@ #include "webrtc/libjingle/xmllite/xmlelement.h" #include "webrtc/libjingle/xmllite/xmlprinter.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/saslhandler.h" -#include "talk/xmpp/xmpplogintask.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/saslhandler.h" +#include "webrtc/libjingle/xmpp/xmpplogintask.h" #include "webrtc/base/common.h" namespace buzz { diff --git a/talk/xmpp/xmppengineimpl.h b/talk/xmpp/xmppengineimpl.h index 4eacf2fb1..a3da795ab 100644 --- a/talk/xmpp/xmppengineimpl.h +++ b/talk/xmpp/xmppengineimpl.h @@ -30,8 +30,8 @@ #include #include -#include "talk/xmpp/xmppengine.h" -#include "talk/xmpp/xmppstanzaparser.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmppstanzaparser.h" namespace buzz { diff --git a/talk/xmpp/xmppengineimpl_iq.cc b/talk/xmpp/xmppengineimpl_iq.cc index 208e16478..d48f021b4 100644 --- a/talk/xmpp/xmppengineimpl_iq.cc +++ b/talk/xmpp/xmppengineimpl_iq.cc @@ -27,8 +27,8 @@ #include #include -#include "talk/xmpp/constants.h" -#include "talk/xmpp/xmppengineimpl.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/xmppengineimpl.h" #include "webrtc/base/common.h" namespace buzz { diff --git a/talk/xmpp/xmpplogintask.cc b/talk/xmpp/xmpplogintask.cc index a48a94c75..7788c0d60 100644 --- a/talk/xmpp/xmpplogintask.cc +++ b/talk/xmpp/xmpplogintask.cc @@ -25,16 +25,16 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/xmpplogintask.h" +#include "webrtc/libjingle/xmpp/xmpplogintask.h" #include #include #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/jid.h" -#include "talk/xmpp/saslmechanism.h" -#include "talk/xmpp/xmppengineimpl.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/saslmechanism.h" +#include "webrtc/libjingle/xmpp/xmppengineimpl.h" #include "webrtc/base/base64.h" #include "webrtc/base/common.h" diff --git a/talk/xmpp/xmpplogintask.h b/talk/xmpp/xmpplogintask.h index 86856580d..631ebdfe4 100644 --- a/talk/xmpp/xmpplogintask.h +++ b/talk/xmpp/xmpplogintask.h @@ -31,8 +31,8 @@ #include #include -#include "talk/xmpp/jid.h" -#include "talk/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" #include "webrtc/base/logging.h" #include "webrtc/base/scoped_ptr.h" diff --git a/talk/xmpp/xmpplogintask_unittest.cc b/talk/xmpp/xmpplogintask_unittest.cc index ae9a55473..82973c46d 100644 --- a/talk/xmpp/xmpplogintask_unittest.cc +++ b/talk/xmpp/xmpplogintask_unittest.cc @@ -5,11 +5,11 @@ #include #include #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/plainsaslhandler.h" -#include "talk/xmpp/saslplainmechanism.h" -#include "talk/xmpp/util_unittest.h" -#include "talk/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/plainsaslhandler.h" +#include "webrtc/libjingle/xmpp/saslplainmechanism.h" +#include "webrtc/libjingle/xmpp/util_unittest.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" #include "webrtc/base/common.h" #include "webrtc/base/cryptstring.h" #include "webrtc/base/gunit.h" diff --git a/talk/xmpp/xmpppump.cc b/talk/xmpp/xmpppump.cc index cf7aa7b1a..e9e7823a4 100644 --- a/talk/xmpp/xmpppump.cc +++ b/talk/xmpp/xmpppump.cc @@ -25,9 +25,9 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/xmpppump.h" +#include "webrtc/libjingle/xmpp/xmpppump.h" -#include "talk/xmpp/xmppauth.h" +#include "webrtc/libjingle/xmpp/xmppauth.h" namespace buzz { diff --git a/talk/xmpp/xmpppump.h b/talk/xmpp/xmpppump.h index ff72f54be..082afb9db 100644 --- a/talk/xmpp/xmpppump.h +++ b/talk/xmpp/xmpppump.h @@ -28,9 +28,9 @@ #ifndef TALK_XMPP_XMPPPUMP_H_ #define TALK_XMPP_XMPPPUMP_H_ -#include "talk/xmpp/xmppclient.h" -#include "talk/xmpp/xmppengine.h" -#include "talk/xmpp/xmpptask.h" +#include "webrtc/libjingle/xmpp/xmppclient.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" #include "webrtc/base/messagequeue.h" #include "webrtc/base/taskrunner.h" #include "webrtc/base/thread.h" diff --git a/talk/xmpp/xmppsocket.h b/talk/xmpp/xmppsocket.h index 3d6df5229..240bbe97d 100644 --- a/talk/xmpp/xmppsocket.h +++ b/talk/xmpp/xmppsocket.h @@ -28,8 +28,8 @@ #ifndef TALK_XMPP_XMPPSOCKET_H_ #define TALK_XMPP_XMPPSOCKET_H_ -#include "talk/xmpp/asyncsocket.h" -#include "talk/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/asyncsocket.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" #include "webrtc/base/asyncsocket.h" #include "webrtc/base/bytebuffer.h" #include "webrtc/base/sigslot.h" diff --git a/talk/xmpp/xmppstanzaparser.cc b/talk/xmpp/xmppstanzaparser.cc index 47958397b..a4ca2ed7c 100644 --- a/talk/xmpp/xmppstanzaparser.cc +++ b/talk/xmpp/xmppstanzaparser.cc @@ -25,10 +25,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/xmppstanzaparser.h" +#include "webrtc/libjingle/xmpp/xmppstanzaparser.h" #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/constants.h" #include "webrtc/base/common.h" #ifdef EXPAT_RELATIVE_PATH #include "expat.h" diff --git a/talk/xmpp/xmppstanzaparser_unittest.cc b/talk/xmpp/xmppstanzaparser_unittest.cc index 0b114c0d9..433de5e2e 100644 --- a/talk/xmpp/xmppstanzaparser_unittest.cc +++ b/talk/xmpp/xmppstanzaparser_unittest.cc @@ -5,7 +5,7 @@ #include #include #include "webrtc/libjingle/xmllite/xmlelement.h" -#include "talk/xmpp/xmppstanzaparser.h" +#include "webrtc/libjingle/xmpp/xmppstanzaparser.h" #include "webrtc/base/common.h" #include "webrtc/base/gunit.h" diff --git a/talk/xmpp/xmpptask.cc b/talk/xmpp/xmpptask.cc index c5e86fad9..31aba621b 100644 --- a/talk/xmpp/xmpptask.cc +++ b/talk/xmpp/xmpptask.cc @@ -25,10 +25,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/constants.h" -#include "talk/xmpp/xmppclient.h" -#include "talk/xmpp/xmppengine.h" -#include "talk/xmpp/xmpptask.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/xmppclient.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" namespace buzz { diff --git a/talk/xmpp/xmpptask.h b/talk/xmpp/xmpptask.h index f6dd156e1..a8d1124f5 100644 --- a/talk/xmpp/xmpptask.h +++ b/talk/xmpp/xmpptask.h @@ -30,7 +30,7 @@ #include #include -#include "talk/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" #include "webrtc/base/sigslot.h" #include "webrtc/base/task.h" #include "webrtc/base/taskparent.h" diff --git a/talk/xmpp/xmppthread.cc b/talk/xmpp/xmppthread.cc index e67bffe80..ad9246b1b 100644 --- a/talk/xmpp/xmppthread.cc +++ b/talk/xmpp/xmppthread.cc @@ -25,10 +25,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmpp/xmppthread.h" +#include "webrtc/libjingle/xmpp/xmppthread.h" -#include "talk/xmpp/xmppauth.h" -#include "talk/xmpp/xmppclientsettings.h" +#include "webrtc/libjingle/xmpp/xmppauth.h" +#include "webrtc/libjingle/xmpp/xmppclientsettings.h" namespace buzz { namespace { diff --git a/talk/xmpp/xmppthread.h b/talk/xmpp/xmppthread.h index bfb74d6ef..5a77f0094 100644 --- a/talk/xmpp/xmppthread.h +++ b/talk/xmpp/xmppthread.h @@ -28,10 +28,10 @@ #ifndef TALK_XMPP_XMPPTHREAD_H_ #define TALK_XMPP_XMPPTHREAD_H_ -#include "talk/xmpp/xmppclientsettings.h" -#include "talk/xmpp/xmppengine.h" -#include "talk/xmpp/xmpppump.h" -#include "talk/xmpp/xmppsocket.h" +#include "webrtc/libjingle/xmpp/xmppclientsettings.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpppump.h" +#include "webrtc/libjingle/xmpp/xmppsocket.h" #include "webrtc/base/thread.h" namespace buzz { diff --git a/webrtc/build/merge_libs.gyp b/webrtc/build/merge_libs.gyp index d66100296..d28d71bac 100644 --- a/webrtc/build/merge_libs.gyp +++ b/webrtc/build/merge_libs.gyp @@ -21,6 +21,8 @@ '../webrtc.gyp:webrtc', '../sound/sound.gyp:rtc_sound', '../libjingle/xmllite/xmllite.gyp:rtc_xmllite', + '../libjingle/xmpp/xmpp.gyp:rtc_xmpp', + '../p2p/p2p.gyp:rtc_p2p', ], 'sources': ['no_op.cc',], }, diff --git a/webrtc/libjingle/xmpp/OWNERS b/webrtc/libjingle/xmpp/OWNERS new file mode 100644 index 000000000..1a24a6a8a --- /dev/null +++ b/webrtc/libjingle/xmpp/OWNERS @@ -0,0 +1,13 @@ +henrika@webrtc.org +henrike@webrtc.org +henrikg@webrtc.org +hta@webrtc.org +jiayl@webrtc.org +juberti@webrtc.org +mflodman@webrtc.org +perkj@webrtc.org +pthatcher@webrtc.org +sergeyu@chromium.org +tommi@webrtc.org + +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/libjingle/xmpp/asyncsocket.h b/webrtc/libjingle/xmpp/asyncsocket.h new file mode 100644 index 000000000..6d77ce084 --- /dev/null +++ b/webrtc/libjingle/xmpp/asyncsocket.h @@ -0,0 +1,72 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_ASYNCSOCKET_H_ +#define WEBRTC_LIBJINGLE_XMPP_ASYNCSOCKET_H_ + +#include + +#include "webrtc/base/sigslot.h" + +namespace rtc { + 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 int GetError() = 0; // winsock error code + + virtual bool Connect(const rtc::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. This allows us to avoid + // handling the valuable certificates for logins into proxies. 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 // WEBRTC_LIBJINGLE_XMPP_ASYNCSOCKET_H_ diff --git a/webrtc/libjingle/xmpp/chatroommodule.h b/webrtc/libjingle/xmpp/chatroommodule.h new file mode 100644 index 000000000..a68f82b34 --- /dev/null +++ b/webrtc/libjingle/xmpp/chatroommodule.h @@ -0,0 +1,253 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_CHATROOMMODULE_H_ +#define WEBRTC_LIBJINGLE_XMPP_CHATROOMMODULE_H_ + +#include "webrtc/libjingle/xmpp/module.h" +#include "webrtc/libjingle/xmpp/rostermodule.h" + +namespace buzz { + +// forward declarations +class XmppChatroomModule; +class XmppChatroomHandler; +class XmppChatroomMember; +class XmppChatroomMemberEnumerator; + +enum XmppChatroomState { + XMPP_CHATROOM_STATE_NOT_IN_ROOM = 0, + XMPP_CHATROOM_STATE_REQUESTED_ENTER = 1, + XMPP_CHATROOM_STATE_IN_ROOM = 2, + XMPP_CHATROOM_STATE_REQUESTED_EXIT = 3, +}; + +//! Module that encapsulates a chatroom. +class XmppChatroomModule : public XmppModule { +public: + + //! Creates a new XmppChatroomModule + static XmppChatroomModule* Create(); + virtual ~XmppChatroomModule() {} + + //! Sets the chatroom handler (callbacks) for the chatroom + virtual XmppReturnStatus set_chatroom_handler(XmppChatroomHandler* handler) = 0; + + //! Gets the chatroom handler for the module + virtual XmppChatroomHandler* chatroom_handler() = 0; + + //! Sets the jid of the chatroom. + //! Has to be set before entering the chatroom and can't be changed + //! while in the chatroom + virtual XmppReturnStatus set_chatroom_jid(const Jid& chatroom_jid) = 0; + + //! The jid for the chatroom + virtual const Jid& chatroom_jid() const = 0; + + //! Sets the nickname of the member + //! Has to be set before entering the chatroom and can't be changed + //! while in the chatroom + virtual XmppReturnStatus set_nickname(const std::string& nickname) = 0; + + //! The nickname of the member in the chatroom + virtual const std::string& nickname() const = 0; + + //! Returns the jid of the member (this is the chatroom_jid plus the + //! nickname as the resource name) + virtual const Jid member_jid() const = 0; + + //! Requests that the user enter a chatroom + //! The EnterChatroom callback will be called when the request is complete. + //! Password should be empty for a room that doesn't require a password + //! If the room doesn't exist, the server will create an "Instant Room" if the + //! server policy supports this action. + //! There will be different methods for creating/configuring a "Reserved Room" + //! Async callback for this method is ChatroomEnteredStatus + virtual XmppReturnStatus RequestEnterChatroom(const std::string& password, + const std::string& client_version, + const std::string& locale) = 0; + + //! Requests that the user exit a chatroom + //! Async callback for this method is ChatroomExitedStatus + virtual XmppReturnStatus RequestExitChatroom() = 0; + + //! Requests a status change + //! status is the standard XMPP status code + //! extended_status is the extended status when status is XMPP_PRESENCE_XA + virtual XmppReturnStatus RequestConnectionStatusChange( + XmppPresenceConnectionStatus connection_status) = 0; + + //! Returns the number of members in the room + virtual size_t GetChatroomMemberCount() = 0; + + //! Gets an enumerator for the members in the chatroom + //! The caller must delete the enumerator when the caller is finished with it. + //! The caller must also ensure that the lifetime of the enumerator is + //! scoped by the XmppChatRoomModule that created it. + virtual XmppReturnStatus CreateMemberEnumerator(XmppChatroomMemberEnumerator** enumerator) = 0; + + //! Gets the subject of the chatroom + virtual const std::string subject() = 0; + + //! Returns the current state of the user with respect to the chatroom + virtual XmppChatroomState state() = 0; + + virtual XmppReturnStatus SendMessage(const XmlElement& message) = 0; +}; + +//! Class for enumerating participatns +class XmppChatroomMemberEnumerator { +public: + virtual ~XmppChatroomMemberEnumerator() { } + //! Returns the member at the current position + //! Returns null if the enumerator is before the beginning + //! or after the end of the collection + virtual XmppChatroomMember* current() = 0; + + //! Returns whether the enumerator is valid + //! This returns true if the collection has changed + //! since the enumerator was created + virtual bool IsValid() = 0; + + //! Returns whether the enumerator is before the beginning + //! This is the initial state of the enumerator + virtual bool IsBeforeBeginning() = 0; + + //! Returns whether the enumerator is after the end + virtual bool IsAfterEnd() = 0; + + //! Advances the enumerator to the next position + //! Returns false is the enumerator is advanced + //! off the end of the collection + virtual bool Next() = 0; + + //! Advances the enumerator to the previous position + //! Returns false is the enumerator is advanced + //! off the end of the collection + virtual bool Prev() = 0; +}; + + +//! Represents a single member in a chatroom +class XmppChatroomMember { +public: + virtual ~XmppChatroomMember() { } + + //! The jid for the member in the chatroom + virtual const Jid member_jid() const = 0; + + //! The full jid for the member + //! This is only available in non-anonymous rooms. + //! If the room is anonymous, this returns JID_EMPTY + virtual const Jid full_jid() const = 0; + + //! Returns the backing presence for this member + virtual const XmppPresence* presence() const = 0; + + //! The nickname for this member + virtual const std::string name() const = 0; +}; + +//! Status codes for ChatroomEnteredStatus callback +enum XmppChatroomEnteredStatus +{ + //! User successfully entered the room + XMPP_CHATROOM_ENTERED_SUCCESS = 0, + //! The nickname confliced with somebody already in the room + XMPP_CHATROOM_ENTERED_FAILURE_NICKNAME_CONFLICT = 1, + //! A password is required to enter the room + XMPP_CHATROOM_ENTERED_FAILURE_PASSWORD_REQUIRED = 2, + //! The specified password was incorrect + XMPP_CHATROOM_ENTERED_FAILURE_PASSWORD_INCORRECT = 3, + //! The user is not a member of a member-only room + XMPP_CHATROOM_ENTERED_FAILURE_NOT_A_MEMBER = 4, + //! The user cannot enter because the user has been banned + XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BANNED = 5, + //! The room has the maximum number of users already + XMPP_CHATROOM_ENTERED_FAILURE_MAX_USERS = 6, + //! The room has been locked by an administrator + XMPP_CHATROOM_ENTERED_FAILURE_ROOM_LOCKED = 7, + //! Someone in the room has blocked you + XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BLOCKED = 8, + //! You have blocked someone in the room + XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BLOCKING = 9, + //! Client is old. User must upgrade to a more recent version for + // hangouts to work. + XMPP_CHATROOM_ENTERED_FAILURE_OUTDATED_CLIENT = 10, + //! Some other reason + XMPP_CHATROOM_ENTERED_FAILURE_UNSPECIFIED = 2000, +}; + +//! Status codes for ChatroomExitedStatus callback +enum XmppChatroomExitedStatus +{ + //! The user requested to exit and did so + XMPP_CHATROOM_EXITED_REQUESTED = 0, + //! The user was banned from the room + XMPP_CHATROOM_EXITED_BANNED = 1, + //! The user has been kicked out of the room + XMPP_CHATROOM_EXITED_KICKED = 2, + //! The user has been removed from the room because the + //! user is no longer a member of a member-only room + //! or the room has changed to membership-only + XMPP_CHATROOM_EXITED_NOT_A_MEMBER = 3, + //! The system is shutting down + XMPP_CHATROOM_EXITED_SYSTEM_SHUTDOWN = 4, + //! For some other reason + XMPP_CHATROOM_EXITED_UNSPECIFIED = 5, +}; + +//! The XmppChatroomHandler is the interface for callbacks from the +//! the chatroom +class XmppChatroomHandler { +public: + virtual ~XmppChatroomHandler() {} + + //! Indicates the response to RequestEnterChatroom method + //! XMPP_CHATROOM_SUCCESS represents success. + //! Other status codes are for errors + virtual void ChatroomEnteredStatus(XmppChatroomModule* room, + const XmppPresence* presence, + XmppChatroomEnteredStatus status) = 0; + + + //! Indicates that the user has exited the chatroom, either due to + //! a call to RequestExitChatroom or for some other reason. + //! status indicates the reason the user exited + virtual void ChatroomExitedStatus(XmppChatroomModule* room, + XmppChatroomExitedStatus status) = 0; + + //! Indicates a member entered the room. + //! It can be called before ChatroomEnteredStatus. + virtual void MemberEntered(XmppChatroomModule* room, + const XmppChatroomMember* entered_member) = 0; + + //! Indicates that a member exited the room. + virtual void MemberExited(XmppChatroomModule* room, + const XmppChatroomMember* exited_member) = 0; + + //! Indicates that the data for the member has changed + //! (such as the nickname or presence) + virtual void MemberChanged(XmppChatroomModule* room, + const XmppChatroomMember* changed_member) = 0; + + //! Indicates a new message has been received + //! message is the message - + // $TODO - message should be changed + //! to a strongly-typed message class that contains info + //! such as the sender, message bodies, etc., + virtual void MessageReceived(XmppChatroomModule* room, + const XmlElement& message) = 0; +}; + + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_CHATROOMMODULE_H_ diff --git a/webrtc/libjingle/xmpp/chatroommodule_unittest.cc b/webrtc/libjingle/xmpp/chatroommodule_unittest.cc new file mode 100644 index 000000000..27b52111a --- /dev/null +++ b/webrtc/libjingle/xmpp/chatroommodule_unittest.cc @@ -0,0 +1,280 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include "buzz/chatroommodule.h" +#include "buzz/constants.h" +#include "buzz/xmlelement.h" +#include "buzz/xmppengine.h" +#include "common/common.h" +#include "engine/util_unittest.h" +#include "test/unittest-inl.h" +#include "test/unittest.h" + +#define TEST_OK(x) TEST_EQ((x),XMPP_RETURN_OK) +#define TEST_BADARGUMENT(x) TEST_EQ((x),XMPP_RETURN_BADARGUMENT) + +namespace buzz { + +class MultiUserChatModuleTest; + +static void +WriteEnteredStatus(std::ostream& os, XmppChatroomEnteredStatus status) { + switch(status) { + case XMPP_CHATROOM_ENTERED_SUCCESS: + os<<"success"; + break; + case XMPP_CHATROOM_ENTERED_FAILURE_NICKNAME_CONFLICT: + os<<"failure(nickname conflict)"; + break; + case XMPP_CHATROOM_ENTERED_FAILURE_PASSWORD_REQUIRED: + os<<"failure(password required)"; + break; + case XMPP_CHATROOM_ENTERED_FAILURE_PASSWORD_INCORRECT: + os<<"failure(password incorrect)"; + break; + case XMPP_CHATROOM_ENTERED_FAILURE_NOT_A_MEMBER: + os<<"failure(not a member)"; + break; + case XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BANNED: + os<<"failure(member banned)"; + break; + case XMPP_CHATROOM_ENTERED_FAILURE_MAX_USERS: + os<<"failure(max users)"; + break; + case XMPP_CHATROOM_ENTERED_FAILURE_ROOM_LOCKED: + os<<"failure(room locked)"; + break; + case XMPP_CHATROOM_ENTERED_FAILURE_UNSPECIFIED: + os<<"failure(unspecified)"; + break; + default: + os<<"unknown"; + break; + } +} + +static void +WriteExitedStatus(std::ostream& os, XmppChatroomExitedStatus status) { + switch (status) { + case XMPP_CHATROOM_EXITED_REQUESTED: + os<<"requested"; + break; + case XMPP_CHATROOM_EXITED_BANNED: + os<<"banned"; + break; + case XMPP_CHATROOM_EXITED_KICKED: + os<<"kicked"; + break; + case XMPP_CHATROOM_EXITED_NOT_A_MEMBER: + os<<"not member"; + break; + case XMPP_CHATROOM_EXITED_SYSTEM_SHUTDOWN: + os<<"system shutdown"; + break; + case XMPP_CHATROOM_EXITED_UNSPECIFIED: + os<<"unspecified"; + break; + default: + os<<"unknown"; + break; + } +} + +//! This session handler saves all calls to a string. These are events and +//! data delivered form the engine to application code. +class XmppTestChatroomHandler : public XmppChatroomHandler { +public: + XmppTestChatroomHandler() {} + virtual ~XmppTestChatroomHandler() {} + + void ChatroomEnteredStatus(XmppChatroomModule* room, + XmppChatroomEnteredStatus status) { + RTC_UNUSED(room); + ss_ <<"[ChatroomEnteredStatus status: "; + WriteEnteredStatus(ss_, status); + ss_ <<"]"; + } + + + void ChatroomExitedStatus(XmppChatroomModule* room, + XmppChatroomExitedStatus status) { + RTC_UNUSED(room); + ss_ <<"[ChatroomExitedStatus status: "; + WriteExitedStatus(ss_, status); + ss_ <<"]"; + } + + void MemberEntered(XmppChatroomModule* room, + const XmppChatroomMember* entered_member) { + RTC_UNUSED(room); + ss_ << "[MemberEntered " << entered_member->member_jid().Str() << "]"; + } + + void MemberExited(XmppChatroomModule* room, + const XmppChatroomMember* exited_member) { + RTC_UNUSED(room); + ss_ << "[MemberExited " << exited_member->member_jid().Str() << "]"; + } + + void MemberChanged(XmppChatroomModule* room, + const XmppChatroomMember* changed_member) { + RTC_UNUSED(room); + ss_ << "[MemberChanged " << changed_member->member_jid().Str() << "]"; + } + + virtual void MessageReceived(XmppChatroomModule* room, const XmlElement& message) { + RTC_UNUSED2(room, message); + } + + + std::string Str() { + return ss_.str(); + } + + std::string StrClear() { + std::string result = ss_.str(); + ss_.str(""); + return result; + } + +private: + std::stringstream ss_; +}; + +//! This is the class that holds all of the unit test code for the +//! roster module +class XmppChatroomModuleTest : public UnitTest { +public: + XmppChatroomModuleTest() {} + + void TestEnterExitChatroom() { + std::stringstream dump; + + // Configure the engine + scoped_ptr engine(XmppEngine::Create()); + XmppTestHandler handler(engine.get()); + + // Configure the module and handler + scoped_ptr chatroom(XmppChatroomModule::Create()); + + // Configure the module handler + chatroom->RegisterEngine(engine.get()); + + // Set up callbacks + engine->SetOutputHandler(&handler); + engine->AddStanzaHandler(&handler); + engine->SetSessionHandler(&handler); + + // Set up minimal login info + engine->SetUser(Jid("david@my-server")); + engine->SetPassword("david"); + + // Do the whole login handshake + RunLogin(this, engine.get(), &handler); + TEST_EQ("", handler.OutputActivity()); + + // Get the chatroom and set the handler + XmppTestChatroomHandler chatroom_handler; + chatroom->set_chatroom_handler(static_cast(&chatroom_handler)); + + // try to enter the chatroom + TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_NOT_IN_ROOM); + chatroom->set_nickname("thirdwitch"); + chatroom->set_chatroom_jid(Jid("darkcave@my-server")); + chatroom->RequestEnterChatroom("", XMPP_CONNECTION_STATUS_UNKNOWN, "en"); + TEST_EQ(chatroom_handler.StrClear(), ""); + TEST_EQ(handler.OutputActivity(), + "" + "" + ""); + TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_REQUESTED_ENTER); + + // simulate the server and test the client + std::string input; + input = "" + "" + "" + "" + ""; + TEST_OK(engine->HandleInput(input.c_str(), input.length())); + TEST_EQ(chatroom_handler.StrClear(), ""); + TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_REQUESTED_ENTER); + + input = "" + "" + "" + "" + ""; + TEST_OK(engine->HandleInput(input.c_str(), input.length())); + TEST_EQ(chatroom_handler.StrClear(), ""); + TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_REQUESTED_ENTER); + + input = "" + "" + "" + "" + ""; + TEST_OK(engine->HandleInput(input.c_str(), input.length())); + TEST_EQ(chatroom_handler.StrClear(), + "[ChatroomEnteredStatus status: success]"); + TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_IN_ROOM); + + // simulate somebody else entering the room after we entered + input = "" + "" + "" + "" + ""; + TEST_OK(engine->HandleInput(input.c_str(), input.length())); + TEST_EQ(chatroom_handler.StrClear(), "[MemberEntered darkcave@my-server/fourthwitch]"); + TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_IN_ROOM); + + // simulate somebody else leaving the room after we entered + input = "" + "" + "" + "" + ""; + TEST_OK(engine->HandleInput(input.c_str(), input.length())); + TEST_EQ(chatroom_handler.StrClear(), "[MemberExited darkcave@my-server/secondwitch]"); + TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_IN_ROOM); + + // try to leave the room + chatroom->RequestExitChatroom(); + TEST_EQ(chatroom_handler.StrClear(), ""); + TEST_EQ(handler.OutputActivity(), + ""); + TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_REQUESTED_EXIT); + + // simulate the server and test the client + input = "" + "" + "" + "" + ""; + TEST_OK(engine->HandleInput(input.c_str(), input.length())); + TEST_EQ(chatroom_handler.StrClear(), + "[ChatroomExitedStatus status: requested]"); + TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_NOT_IN_ROOM); + } + +}; + +// A global function that creates the test suite for this set of tests. +TestBase* ChatroomModuleTest_Create() { + TestSuite* suite = new TestSuite("ChatroomModuleTest"); + ADD_TEST(suite, XmppChatroomModuleTest, TestEnterExitChatroom); + return suite; +} + +} diff --git a/webrtc/libjingle/xmpp/chatroommoduleimpl.cc b/webrtc/libjingle/xmpp/chatroommoduleimpl.cc new file mode 100644 index 000000000..546aa75f9 --- /dev/null +++ b/webrtc/libjingle/xmpp/chatroommoduleimpl.cc @@ -0,0 +1,735 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include +#include +#include +#include "webrtc/libjingle/xmpp/chatroommodule.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/moduleimpl.h" +#include "webrtc/base/common.h" + +namespace buzz { + +// forward declarations +class XmppChatroomImpl; +class XmppChatroomMemberImpl; + +//! Module that encapsulates multiple chatrooms. +//! Each chatroom is represented by an XmppChatroomImpl instance +class XmppChatroomModuleImpl : public XmppChatroomModule, + public XmppModuleImpl, public XmppIqHandler { +public: + IMPLEMENT_XMPPMODULE + + // Creates a chatroom with specified Jid + XmppChatroomModuleImpl(); + ~XmppChatroomModuleImpl(); + + // XmppChatroomModule + virtual XmppReturnStatus set_chatroom_handler(XmppChatroomHandler* handler); + virtual XmppChatroomHandler* chatroom_handler(); + virtual XmppReturnStatus set_chatroom_jid(const Jid& chatroom_jid); + virtual const Jid& chatroom_jid() const; + virtual XmppReturnStatus set_nickname(const std::string& nickname); + virtual const std::string& nickname() const; + virtual const Jid member_jid() const; + virtual XmppReturnStatus RequestEnterChatroom(const std::string& password, + const std::string& client_version, + const std::string& locale); + virtual XmppReturnStatus RequestExitChatroom(); + virtual XmppReturnStatus RequestConnectionStatusChange( + XmppPresenceConnectionStatus connection_status); + virtual size_t GetChatroomMemberCount(); + virtual XmppReturnStatus CreateMemberEnumerator(XmppChatroomMemberEnumerator** enumerator); + virtual const std::string subject(); + virtual XmppChatroomState state() { return chatroom_state_; } + virtual XmppReturnStatus SendMessage(const XmlElement& message); + + // XmppModule + virtual void IqResponse(XmppIqCookie cookie, const XmlElement * pelStanza) {RTC_UNUSED2(cookie, pelStanza);} + virtual bool HandleStanza(const XmlElement *); + +private: + friend class XmppChatroomMemberEnumeratorImpl; + + XmppReturnStatus ServerChangeMyPresence(const XmlElement& presence); + XmppReturnStatus ClientChangeMyPresence(XmppChatroomState new_state); + XmppReturnStatus ChangePresence(XmppChatroomState new_state, const XmlElement* presence, bool isServer); + XmppReturnStatus ServerChangedOtherPresence(const XmlElement& presence_element); + XmppChatroomEnteredStatus GetEnterFailureFromXml(const XmlElement* presence); + XmppChatroomExitedStatus GetExitFailureFromXml(const XmlElement* presence); + + bool CheckEnterChatroomStateOk(); + + void FireEnteredStatus(const XmlElement* presence, + XmppChatroomEnteredStatus status); + void FireExitStatus(XmppChatroomExitedStatus status); + void FireMessageReceived(const XmlElement& message); + void FireMemberEntered(const XmppChatroomMember* entered_member); + void FireMemberChanged(const XmppChatroomMember* changed_member); + void FireMemberExited(const XmppChatroomMember* exited_member); + + + typedef std::map JidMemberMap; + + XmppChatroomHandler* chatroom_handler_; + Jid chatroom_jid_; + std::string nickname_; + XmppChatroomState chatroom_state_; + JidMemberMap chatroom_jid_members_; + int chatroom_jid_members_version_; +}; + + +class XmppChatroomMemberImpl : public XmppChatroomMember { +public: + ~XmppChatroomMemberImpl() {} + XmppReturnStatus SetPresence(const XmppPresence* presence); + + // XmppChatroomMember + const Jid member_jid() const; + const Jid full_jid() const; + const std::string name() const; + const XmppPresence* presence() const; + +private: + rtc::scoped_ptr presence_; +}; + +class XmppChatroomMemberEnumeratorImpl : + public XmppChatroomMemberEnumerator { +public: + XmppChatroomMemberEnumeratorImpl(XmppChatroomModuleImpl::JidMemberMap* chatroom_jid_members, + int* map_version); + + // XmppChatroomMemberEnumerator + virtual XmppChatroomMember* current(); + virtual bool Next(); + virtual bool Prev(); + virtual bool IsValid(); + virtual bool IsBeforeBeginning(); + virtual bool IsAfterEnd(); + +private: + XmppChatroomModuleImpl::JidMemberMap* map_; + int map_version_created_; + int* map_version_; + XmppChatroomModuleImpl::JidMemberMap::iterator iterator_; + bool before_beginning_; +}; + + +// XmppChatroomModuleImpl ------------------------------------------------ +XmppChatroomModule * +XmppChatroomModule::Create() { + return new XmppChatroomModuleImpl(); +} + +XmppChatroomModuleImpl::XmppChatroomModuleImpl() : + chatroom_handler_(NULL), + chatroom_jid_(STR_EMPTY), + chatroom_state_(XMPP_CHATROOM_STATE_NOT_IN_ROOM), + chatroom_jid_members_version_(0) { +} + +XmppChatroomModuleImpl::~XmppChatroomModuleImpl() { + JidMemberMap::iterator iterator = chatroom_jid_members_.begin(); + while (iterator != chatroom_jid_members_.end()) { + delete iterator->second; + iterator++; + } +} + + +bool +XmppChatroomModuleImpl::HandleStanza(const XmlElement* stanza) { + ASSERT(engine() != NULL); + + // we handle stanzas that are for one of our chatrooms + Jid from_jid = Jid(stanza->Attr(QN_FROM)); + // see if it's one of our chatrooms + if (chatroom_jid_ != from_jid.BareJid()) { + return false; // not one of our chatrooms + } else { + // handle presence stanza + if (stanza->Name() == QN_PRESENCE) { + if (from_jid == member_jid()) { + ServerChangeMyPresence(*stanza); + } else { + ServerChangedOtherPresence(*stanza); + } + } else if (stanza->Name() == QN_MESSAGE) { + FireMessageReceived(*stanza); + } + return true; + } +} + + +XmppReturnStatus +XmppChatroomModuleImpl::set_chatroom_handler(XmppChatroomHandler* handler) { + // Calling with NULL removes the handler. + chatroom_handler_ = handler; + return XMPP_RETURN_OK; +} + + +XmppChatroomHandler* +XmppChatroomModuleImpl::chatroom_handler() { + return chatroom_handler_; +} + +XmppReturnStatus +XmppChatroomModuleImpl::set_chatroom_jid(const Jid& chatroom_jid) { + if (chatroom_state_ != XMPP_CHATROOM_STATE_NOT_IN_ROOM) { + return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call, diff error code? + } + if (chatroom_jid != chatroom_jid.BareJid()) { + // chatroom_jid must be a bare jid + return XMPP_RETURN_BADARGUMENT; + } + + chatroom_jid_ = chatroom_jid; + return XMPP_RETURN_OK; +} + +const Jid& +XmppChatroomModuleImpl::chatroom_jid() const { + return chatroom_jid_; +} + + XmppReturnStatus + XmppChatroomModuleImpl::set_nickname(const std::string& nickname) { + if (chatroom_state_ != XMPP_CHATROOM_STATE_NOT_IN_ROOM) { + return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call, diff error code? + } + nickname_ = nickname; + return XMPP_RETURN_OK; + } + + const std::string& + XmppChatroomModuleImpl::nickname() const { + return nickname_; + } + +const Jid +XmppChatroomModuleImpl::member_jid() const { + return Jid(chatroom_jid_.node(), chatroom_jid_.domain(), nickname_); +} + + +bool +XmppChatroomModuleImpl::CheckEnterChatroomStateOk() { + if (chatroom_jid_.IsValid() == false) { + ASSERT(0); + return false; + } + if (nickname_ == STR_EMPTY) { + ASSERT(0); + return false; + } + return true; +} + +std::string GetAttrValueFor(XmppPresenceConnectionStatus connection_status) { + switch (connection_status) { + default: + case XMPP_CONNECTION_STATUS_UNKNOWN: + return ""; + case XMPP_CONNECTION_STATUS_CONNECTING: + return STR_PSTN_CONFERENCE_STATUS_CONNECTING; + case XMPP_CONNECTION_STATUS_CONNECTED: + return STR_PSTN_CONFERENCE_STATUS_CONNECTED; + } +} + +XmppReturnStatus +XmppChatroomModuleImpl::RequestEnterChatroom( + const std::string& password, + const std::string& client_version, + const std::string& locale) { + RTC_UNUSED(password); + if (!engine()) + return XMPP_RETURN_BADSTATE; + + if (chatroom_state_ != XMPP_CHATROOM_STATE_NOT_IN_ROOM) + return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call, diff error code? + + if (CheckEnterChatroomStateOk() == false) { + return XMPP_RETURN_BADSTATE; + } + + // entering a chatroom is a presence request to the server + XmlElement element(QN_PRESENCE); + element.AddAttr(QN_TO, member_jid().Str()); + + XmlElement* muc_x = new XmlElement(QN_MUC_X); + element.AddElement(muc_x); + + if (!client_version.empty()) { + XmlElement* client_version_element = new XmlElement(QN_CLIENT_VERSION, + false); + client_version_element->SetBodyText(client_version); + muc_x->AddElement(client_version_element); + } + + if (!locale.empty()) { + XmlElement* locale_element = new XmlElement(QN_LOCALE, false); + + locale_element->SetBodyText(locale); + muc_x->AddElement(locale_element); + } + + XmppReturnStatus status = engine()->SendStanza(&element); + if (status == XMPP_RETURN_OK) { + return ClientChangeMyPresence(XMPP_CHATROOM_STATE_REQUESTED_ENTER); + } + return status; +} + +XmppReturnStatus +XmppChatroomModuleImpl::RequestExitChatroom() { + if (!engine()) + return XMPP_RETURN_BADSTATE; + + // exiting a chatroom is a presence request to the server + XmlElement element(QN_PRESENCE); + element.AddAttr(QN_TO, member_jid().Str()); + element.AddAttr(QN_TYPE, "unavailable"); + XmppReturnStatus status = engine()->SendStanza(&element); + if (status == XMPP_RETURN_OK && + chatroom_state_ == XMPP_CHATROOM_STATE_IN_ROOM) { + return ClientChangeMyPresence(XMPP_CHATROOM_STATE_REQUESTED_EXIT); + } + return status; +} + +XmppReturnStatus +XmppChatroomModuleImpl::RequestConnectionStatusChange( + XmppPresenceConnectionStatus connection_status) { + if (!engine()) + return XMPP_RETURN_BADSTATE; + + if (chatroom_state_ != XMPP_CHATROOM_STATE_IN_ROOM) { + // $TODO - this isn't a bad state, it's a bad call, diff error code? + return XMPP_RETURN_BADSTATE; + } + + if (CheckEnterChatroomStateOk() == false) { + return XMPP_RETURN_BADSTATE; + } + + // entering a chatroom is a presence request to the server + XmlElement element(QN_PRESENCE); + element.AddAttr(QN_TO, member_jid().Str()); + element.AddElement(new XmlElement(QN_MUC_X)); + if (connection_status != XMPP_CONNECTION_STATUS_UNKNOWN) { + XmlElement* con_status_element = + new XmlElement(QN_GOOGLE_PSTN_CONFERENCE_STATUS); + con_status_element->AddAttr(QN_STATUS, GetAttrValueFor(connection_status)); + element.AddElement(con_status_element); + } + XmppReturnStatus status = engine()->SendStanza(&element); + + return status; +} + +size_t +XmppChatroomModuleImpl::GetChatroomMemberCount() { + return chatroom_jid_members_.size(); +} + +XmppReturnStatus +XmppChatroomModuleImpl::CreateMemberEnumerator(XmppChatroomMemberEnumerator** enumerator) { + *enumerator = new XmppChatroomMemberEnumeratorImpl(&chatroom_jid_members_, &chatroom_jid_members_version_); + return XMPP_RETURN_OK; +} + +const std::string +XmppChatroomModuleImpl::subject() { + return ""; //NYI +} + +XmppReturnStatus +XmppChatroomModuleImpl::SendMessage(const XmlElement& message) { + XmppReturnStatus xmpp_status = XMPP_RETURN_OK; + + // can only send a message if we're in the room + if (chatroom_state_ != XMPP_CHATROOM_STATE_IN_ROOM) { + return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call, diff error code? + } + + if (message.Name() != QN_MESSAGE) { + IFR(XMPP_RETURN_BADARGUMENT); + } + + const std::string& type = message.Attr(QN_TYPE); + if (type != "groupchat") { + IFR(XMPP_RETURN_BADARGUMENT); + } + + if (message.HasAttr(QN_FROM)) { + IFR(XMPP_RETURN_BADARGUMENT); + } + + if (message.Attr(QN_TO) != chatroom_jid_.Str()) { + IFR(XMPP_RETURN_BADARGUMENT); + } + + IFR(engine()->SendStanza(&message)); + + return xmpp_status; +} + +enum TransitionType { + TRANSITION_TYPE_NONE = 0, + TRANSITION_TYPE_ENTER_SUCCESS = 1, + TRANSITION_TYPE_ENTER_FAILURE = 2, + TRANSITION_TYPE_EXIT_VOLUNTARILY = 3, + TRANSITION_TYPE_EXIT_INVOLUNTARILY = 4, +}; + +struct StateTransitionDescription { + XmppChatroomState old_state; + XmppChatroomState new_state; + bool is_valid_server_transition; + bool is_valid_client_transition; + TransitionType transition_type; +}; + +StateTransitionDescription Transitions[] = { + { XMPP_CHATROOM_STATE_NOT_IN_ROOM, XMPP_CHATROOM_STATE_REQUESTED_ENTER, false, true, TRANSITION_TYPE_NONE, }, + { XMPP_CHATROOM_STATE_NOT_IN_ROOM, XMPP_CHATROOM_STATE_IN_ROOM, false, false, TRANSITION_TYPE_ENTER_SUCCESS, }, + { XMPP_CHATROOM_STATE_NOT_IN_ROOM, XMPP_CHATROOM_STATE_REQUESTED_EXIT, false, false, TRANSITION_TYPE_NONE, }, + { XMPP_CHATROOM_STATE_REQUESTED_ENTER, XMPP_CHATROOM_STATE_NOT_IN_ROOM, true, false, TRANSITION_TYPE_ENTER_FAILURE, }, + { XMPP_CHATROOM_STATE_REQUESTED_ENTER, XMPP_CHATROOM_STATE_IN_ROOM, true, false, TRANSITION_TYPE_ENTER_SUCCESS, }, + { XMPP_CHATROOM_STATE_REQUESTED_ENTER, XMPP_CHATROOM_STATE_REQUESTED_EXIT, false, false, TRANSITION_TYPE_NONE, }, + { XMPP_CHATROOM_STATE_IN_ROOM, XMPP_CHATROOM_STATE_NOT_IN_ROOM, true, false, TRANSITION_TYPE_EXIT_INVOLUNTARILY, }, + { XMPP_CHATROOM_STATE_IN_ROOM, XMPP_CHATROOM_STATE_REQUESTED_ENTER, false, false, TRANSITION_TYPE_NONE, }, + { XMPP_CHATROOM_STATE_IN_ROOM, XMPP_CHATROOM_STATE_REQUESTED_EXIT, false, true, TRANSITION_TYPE_NONE, }, + { XMPP_CHATROOM_STATE_REQUESTED_EXIT, XMPP_CHATROOM_STATE_NOT_IN_ROOM, true, false, TRANSITION_TYPE_EXIT_VOLUNTARILY, }, + { XMPP_CHATROOM_STATE_REQUESTED_EXIT, XMPP_CHATROOM_STATE_REQUESTED_ENTER, false, false, TRANSITION_TYPE_NONE, }, + { XMPP_CHATROOM_STATE_REQUESTED_EXIT, XMPP_CHATROOM_STATE_IN_ROOM, false, false, TRANSITION_TYPE_NONE, }, +}; + + + +void +XmppChatroomModuleImpl::FireEnteredStatus(const XmlElement* presence, + XmppChatroomEnteredStatus status) { + if (chatroom_handler_) { + rtc::scoped_ptr xmpp_presence(XmppPresence::Create()); + xmpp_presence->set_raw_xml(presence); + chatroom_handler_->ChatroomEnteredStatus(this, xmpp_presence.get(), status); + } +} + +void +XmppChatroomModuleImpl::FireExitStatus(XmppChatroomExitedStatus status) { + if (chatroom_handler_) + chatroom_handler_->ChatroomExitedStatus(this, status); +} + +void +XmppChatroomModuleImpl::FireMessageReceived(const XmlElement& message) { + if (chatroom_handler_) + chatroom_handler_->MessageReceived(this, message); +} + +void +XmppChatroomModuleImpl::FireMemberEntered(const XmppChatroomMember* entered_member) { + if (chatroom_handler_) + chatroom_handler_->MemberEntered(this, entered_member); +} + +void +XmppChatroomModuleImpl::FireMemberChanged( + const XmppChatroomMember* changed_member) { + if (chatroom_handler_) + chatroom_handler_->MemberChanged(this, changed_member); +} + +void +XmppChatroomModuleImpl::FireMemberExited(const XmppChatroomMember* exited_member) { + if (chatroom_handler_) + chatroom_handler_->MemberExited(this, exited_member); +} + + +XmppReturnStatus +XmppChatroomModuleImpl::ServerChangedOtherPresence(const XmlElement& + presence_element) { + XmppReturnStatus xmpp_status = XMPP_RETURN_OK; + rtc::scoped_ptr presence(XmppPresence::Create()); + IFR(presence->set_raw_xml(&presence_element)); + + JidMemberMap::iterator pos = chatroom_jid_members_.find(presence->jid()); + + if (pos == chatroom_jid_members_.end()) { + if (presence->available() == XMPP_PRESENCE_AVAILABLE) { + XmppChatroomMemberImpl* member = new XmppChatroomMemberImpl(); + member->SetPresence(presence.get()); + chatroom_jid_members_.insert(std::make_pair(member->member_jid(), member)); + chatroom_jid_members_version_++; + FireMemberEntered(member); + } + } else { + XmppChatroomMemberImpl* member = pos->second; + if (presence->available() == XMPP_PRESENCE_AVAILABLE) { + member->SetPresence(presence.get()); + chatroom_jid_members_version_++; + FireMemberChanged(member); + } + else if (presence->available() == XMPP_PRESENCE_UNAVAILABLE) { + member->SetPresence(presence.get()); + chatroom_jid_members_.erase(pos); + chatroom_jid_members_version_++; + FireMemberExited(member); + delete member; + } + } + + return xmpp_status; +} + +XmppReturnStatus +XmppChatroomModuleImpl::ClientChangeMyPresence(XmppChatroomState new_state) { + return ChangePresence(new_state, NULL, false); +} + +XmppReturnStatus +XmppChatroomModuleImpl::ServerChangeMyPresence(const XmlElement& presence) { + XmppChatroomState new_state; + + if (presence.HasAttr(QN_TYPE) == false) { + new_state = XMPP_CHATROOM_STATE_IN_ROOM; + } else { + new_state = XMPP_CHATROOM_STATE_NOT_IN_ROOM; + } + return ChangePresence(new_state, &presence, true); + +} + +XmppReturnStatus +XmppChatroomModuleImpl::ChangePresence(XmppChatroomState new_state, + const XmlElement* presence, + bool isServer) { + RTC_UNUSED(presence); + + XmppChatroomState old_state = chatroom_state_; + + // do nothing if state hasn't changed + if (old_state == new_state) + return XMPP_RETURN_OK; + + // find the right transition description + StateTransitionDescription* transition_desc = NULL; + for (int i=0; i < ARRAY_SIZE(Transitions); i++) { + if (Transitions[i].old_state == old_state && + Transitions[i].new_state == new_state) { + transition_desc = &Transitions[i]; + break; + } + } + + if (transition_desc == NULL) { + ASSERT(0); + return XMPP_RETURN_BADSTATE; + } + + // we assert for any invalid transition states, and we'll + if (isServer) { + // $TODO send original stanza back to server and log an error? + // Disable the assert because of b/6133072 + // ASSERT(transition_desc->is_valid_server_transition); + if (!transition_desc->is_valid_server_transition) { + return XMPP_RETURN_BADSTATE; + } + } else { + if (transition_desc->is_valid_client_transition == false) { + ASSERT(0); + return XMPP_RETURN_BADARGUMENT; + } + } + + // set the new state and then fire any notifications to the handler + chatroom_state_ = new_state; + + switch (transition_desc->transition_type) { + case TRANSITION_TYPE_ENTER_SUCCESS: + FireEnteredStatus(presence, XMPP_CHATROOM_ENTERED_SUCCESS); + break; + case TRANSITION_TYPE_ENTER_FAILURE: + FireEnteredStatus(presence, GetEnterFailureFromXml(presence)); + break; + case TRANSITION_TYPE_EXIT_INVOLUNTARILY: + FireExitStatus(GetExitFailureFromXml(presence)); + break; + case TRANSITION_TYPE_EXIT_VOLUNTARILY: + FireExitStatus(XMPP_CHATROOM_EXITED_REQUESTED); + break; + case TRANSITION_TYPE_NONE: + break; + } + + return XMPP_RETURN_OK; +} + +XmppChatroomEnteredStatus +XmppChatroomModuleImpl::GetEnterFailureFromXml(const XmlElement* presence) { + XmppChatroomEnteredStatus status = XMPP_CHATROOM_ENTERED_FAILURE_UNSPECIFIED; + const XmlElement* error = presence->FirstNamed(QN_ERROR); + if (error != NULL && error->HasAttr(QN_CODE)) { + int code = atoi(error->Attr(QN_CODE).c_str()); + switch (code) { + case 401: status = XMPP_CHATROOM_ENTERED_FAILURE_PASSWORD_REQUIRED; break; + case 403: { + status = XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BANNED; + if (error->FirstNamed(QN_GOOGLE_SESSION_BLOCKED)) { + status = XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BLOCKED; + } else if (error->FirstNamed(QN_GOOGLE_SESSION_BLOCKING)) { + status = XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BLOCKING; + } + break; + } + case 405: status = XMPP_CHATROOM_ENTERED_FAILURE_ROOM_LOCKED; break; + case 406: status = XMPP_CHATROOM_ENTERED_FAILURE_OUTDATED_CLIENT; break; + case 407: status = XMPP_CHATROOM_ENTERED_FAILURE_NOT_A_MEMBER; break; + case 409: status = XMPP_CHATROOM_ENTERED_FAILURE_NICKNAME_CONFLICT; break; + // http://xmpp.org/extensions/xep-0045.html#enter-maxusers + case 503: status = XMPP_CHATROOM_ENTERED_FAILURE_MAX_USERS; break; + } + } + return status; +} + +XmppChatroomExitedStatus +XmppChatroomModuleImpl::GetExitFailureFromXml(const XmlElement* presence) { + XmppChatroomExitedStatus status = XMPP_CHATROOM_EXITED_UNSPECIFIED; + const XmlElement* muc_user = presence->FirstNamed(QN_MUC_USER_X); + if (muc_user != NULL) { + const XmlElement* user_status = muc_user->FirstNamed(QN_MUC_USER_STATUS); + if (user_status != NULL && user_status->HasAttr(QN_CODE)) { + int code = atoi(user_status->Attr(QN_CODE).c_str()); + switch (code) { + case 307: status = XMPP_CHATROOM_EXITED_KICKED; break; + case 322: status = XMPP_CHATROOM_EXITED_NOT_A_MEMBER; break; + case 332: status = XMPP_CHATROOM_EXITED_SYSTEM_SHUTDOWN; break; + } + } + } + return status; +} + +XmppReturnStatus +XmppChatroomMemberImpl::SetPresence(const XmppPresence* presence) { + ASSERT(presence != NULL); + + // copy presence + presence_.reset(XmppPresence::Create()); + presence_->set_raw_xml(presence->raw_xml()); + return XMPP_RETURN_OK; +} + +const Jid +XmppChatroomMemberImpl::member_jid() const { + return presence_->jid(); +} + +const Jid +XmppChatroomMemberImpl::full_jid() const { + return Jid(""); +} + +const std::string +XmppChatroomMemberImpl::name() const { + return member_jid().resource(); +} + +const XmppPresence* +XmppChatroomMemberImpl::presence() const { + return presence_.get(); +} + + +// XmppChatroomMemberEnumeratorImpl -------------------------------------- +XmppChatroomMemberEnumeratorImpl::XmppChatroomMemberEnumeratorImpl( + XmppChatroomModuleImpl::JidMemberMap* map, int* map_version) { + map_ = map; + map_version_ = map_version; + map_version_created_ = *map_version_; + iterator_ = map->begin(); + before_beginning_ = true; +} + +XmppChatroomMember* +XmppChatroomMemberEnumeratorImpl::current() { + if (IsValid() == false) { + return NULL; + } else if (IsBeforeBeginning() || IsAfterEnd()) { + return NULL; + } else { + return iterator_->second; + } +} + +bool +XmppChatroomMemberEnumeratorImpl::Prev() { + if (IsValid() == false) { + return false; + } else if (IsBeforeBeginning()) { + return false; + } else if (iterator_ == map_->begin()) { + before_beginning_ = true; + return false; + } else { + iterator_--; + return current() != NULL; + } +} + +bool +XmppChatroomMemberEnumeratorImpl::Next() { + if (IsValid() == false) { + return false; + } else if (IsBeforeBeginning()) { + before_beginning_ = false; + iterator_ = map_->begin(); + return current() != NULL; + } else if (IsAfterEnd()) { + return false; + } else { + iterator_++; + return current() != NULL; + } +} + +bool +XmppChatroomMemberEnumeratorImpl::IsValid() { + return map_version_created_ == *map_version_; +} + +bool +XmppChatroomMemberEnumeratorImpl::IsBeforeBeginning() { + return before_beginning_; +} + +bool +XmppChatroomMemberEnumeratorImpl::IsAfterEnd() { + return (iterator_ == map_->end()); +} + + + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/constants.cc b/webrtc/libjingle/xmpp/constants.cc new file mode 100644 index 000000000..38e0cec48 --- /dev/null +++ b/webrtc/libjingle/xmpp/constants.cc @@ -0,0 +1,614 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/constants.h" + +#include + +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlconstants.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/base/basicdefs.h" + +namespace buzz { + +// TODO: Remove static objects of complex types, particularly +// Jid and QName. + +const char NS_CLIENT[] = "jabber:client"; +const char NS_SERVER[] = "jabber:server"; +const char NS_STREAM[] = "http://etherx.jabber.org/streams"; +const char NS_XSTREAM[] = "urn:ietf:params:xml:ns:xmpp-streams"; +const char NS_TLS[] = "urn:ietf:params:xml:ns:xmpp-tls"; +const char NS_SASL[] = "urn:ietf:params:xml:ns:xmpp-sasl"; +const char NS_BIND[] = "urn:ietf:params:xml:ns:xmpp-bind"; +const char NS_DIALBACK[] = "jabber:server:dialback"; +const char NS_SESSION[] = "urn:ietf:params:xml:ns:xmpp-session"; +const char NS_STANZA[] = "urn:ietf:params:xml:ns:xmpp-stanzas"; +const char NS_PRIVACY[] = "jabber:iq:privacy"; +const char NS_ROSTER[] = "jabber:iq:roster"; +const char NS_VCARD[] = "vcard-temp"; +const char NS_AVATAR_HASH[] = "google:avatar"; +const char NS_VCARD_UPDATE[] = "vcard-temp:x:update"; +const char STR_CLIENT[] = "client"; +const char STR_SERVER[] = "server"; +const char STR_STREAM[] = "stream"; + +const char STR_GET[] = "get"; +const char STR_SET[] = "set"; +const char STR_RESULT[] = "result"; +const char STR_ERROR[] = "error"; + +const char STR_FORM[] = "form"; +const char STR_SUBMIT[] = "submit"; +const char STR_TEXT_SINGLE[] = "text-single"; +const char STR_LIST_SINGLE[] = "list-single"; +const char STR_LIST_MULTI[] = "list-multi"; +const char STR_HIDDEN[] = "hidden"; +const char STR_FORM_TYPE[] = "FORM_TYPE"; + +const char STR_FROM[] = "from"; +const char STR_TO[] = "to"; +const char STR_BOTH[] = "both"; +const char STR_REMOVE[] = "remove"; +const char STR_TRUE[] = "true"; + +const char STR_TYPE[] = "type"; +const char STR_NAME[] = "name"; +const char STR_ID[] = "id"; +const char STR_JID[] = "jid"; +const char STR_SUBSCRIPTION[] = "subscription"; +const char STR_ASK[] = "ask"; +const char STR_X[] = "x"; +const char STR_GOOGLE_COM[] = "google.com"; +const char STR_GMAIL_COM[] = "gmail.com"; +const char STR_GOOGLEMAIL_COM[] = "googlemail.com"; +const char STR_DEFAULT_DOMAIN[] = "default.talk.google.com"; +const char STR_TALK_GOOGLE_COM[] = "talk.google.com"; +const char STR_TALKX_L_GOOGLE_COM[] = "talkx.l.google.com"; +const char STR_XMPP_GOOGLE_COM[] = "xmpp.google.com"; +const char STR_XMPPX_L_GOOGLE_COM[] = "xmppx.l.google.com"; + +#ifdef FEATURE_ENABLE_VOICEMAIL +const char STR_VOICEMAIL[] = "voicemail"; +const char STR_OUTGOINGVOICEMAIL[] = "outgoingvoicemail"; +#endif + +const char STR_UNAVAILABLE[] = "unavailable"; + +const char NS_PING[] = "urn:xmpp:ping"; +const StaticQName QN_PING = { NS_PING, "ping" }; + +const char NS_MUC_UNIQUE[] = "http://jabber.org/protocol/muc#unique"; +const StaticQName QN_MUC_UNIQUE_QUERY = { NS_MUC_UNIQUE, "unique" }; +const StaticQName QN_HANGOUT_ID = { STR_EMPTY, "hangout-id" }; + +const char STR_GOOGLE_MUC_LOOKUP_JID[] = "lookup.groupchat.google.com"; + +const char STR_MUC_ROOMCONFIG_ROOMNAME[] = "muc#roomconfig_roomname"; +const char STR_MUC_ROOMCONFIG_FEATURES[] = "muc#roomconfig_features"; +const char STR_MUC_ROOM_FEATURE_ENTERPRISE[] = "muc_enterprise"; +const char STR_MUC_ROOMCONFIG[] = "http://jabber.org/protocol/muc#roomconfig"; +const char STR_MUC_ROOM_FEATURE_HANGOUT[] = "muc_es"; +const char STR_MUC_ROOM_FEATURE_HANGOUT_LITE[] = "muc_lite"; +const char STR_MUC_ROOM_FEATURE_BROADCAST[] = "broadcast"; +const char STR_MUC_ROOM_FEATURE_MULTI_USER_VC[] = "muc_muvc"; +const char STR_MUC_ROOM_FEATURE_RECORDABLE[] = "recordable"; +const char STR_MUC_ROOM_FEATURE_CUSTOM_RECORDING[] = "custom_recording"; +const char STR_MUC_ROOM_OWNER_PROFILE_ID[] = "muc#roominfo_owner_profile_id"; +const char STR_MUC_ROOM_FEATURE_ABUSE_RECORDABLE[] = "abuse_recordable"; + +const char STR_ID_TYPE_CONVERSATION[] = "conversation"; +const char NS_GOOGLE_MUC_HANGOUT[] = "google:muc#hangout"; +const StaticQName QN_GOOGLE_MUC_HANGOUT_INVITE = + { NS_GOOGLE_MUC_HANGOUT, "invite" }; +const StaticQName QN_GOOGLE_MUC_HANGOUT_INVITE_TYPE = + { NS_GOOGLE_MUC_HANGOUT, "invite-type" }; +const StaticQName QN_ATTR_CREATE_ACTIVITY = + { STR_EMPTY, "create-activity" }; +const StaticQName QN_GOOGLE_MUC_HANGOUT_PUBLIC = + { NS_GOOGLE_MUC_HANGOUT, "public" }; +const StaticQName QN_GOOGLE_MUC_HANGOUT_INVITEE = + { NS_GOOGLE_MUC_HANGOUT, "invitee" }; +const StaticQName QN_GOOGLE_MUC_HANGOUT_NOTIFICATION_STATUS = + { NS_GOOGLE_MUC_HANGOUT, "notification-status" }; +const StaticQName QN_GOOGLE_MUC_HANGOUT_NOTIFICATION_TYPE = { + NS_GOOGLE_MUC_HANGOUT, "notification-type" }; +const StaticQName QN_GOOGLE_MUC_HANGOUT_HANGOUT_START_CONTEXT = { + NS_GOOGLE_MUC_HANGOUT, "hangout-start-context" }; +const StaticQName QN_GOOGLE_MUC_HANGOUT_CONVERSATION_ID = { + NS_GOOGLE_MUC_HANGOUT, "conversation-id" }; + +const StaticQName QN_STREAM_STREAM = { NS_STREAM, STR_STREAM }; +const StaticQName QN_STREAM_FEATURES = { NS_STREAM, "features" }; +const StaticQName QN_STREAM_ERROR = { NS_STREAM, "error" }; + +const StaticQName QN_XSTREAM_BAD_FORMAT = { NS_XSTREAM, "bad-format" }; +const StaticQName QN_XSTREAM_BAD_NAMESPACE_PREFIX = + { NS_XSTREAM, "bad-namespace-prefix" }; +const StaticQName QN_XSTREAM_CONFLICT = { NS_XSTREAM, "conflict" }; +const StaticQName QN_XSTREAM_CONNECTION_TIMEOUT = + { NS_XSTREAM, "connection-timeout" }; +const StaticQName QN_XSTREAM_HOST_GONE = { NS_XSTREAM, "host-gone" }; +const StaticQName QN_XSTREAM_HOST_UNKNOWN = { NS_XSTREAM, "host-unknown" }; +const StaticQName QN_XSTREAM_IMPROPER_ADDRESSIING = + { NS_XSTREAM, "improper-addressing" }; +const StaticQName QN_XSTREAM_INTERNAL_SERVER_ERROR = + { NS_XSTREAM, "internal-server-error" }; +const StaticQName QN_XSTREAM_INVALID_FROM = { NS_XSTREAM, "invalid-from" }; +const StaticQName QN_XSTREAM_INVALID_ID = { NS_XSTREAM, "invalid-id" }; +const StaticQName QN_XSTREAM_INVALID_NAMESPACE = + { NS_XSTREAM, "invalid-namespace" }; +const StaticQName QN_XSTREAM_INVALID_XML = { NS_XSTREAM, "invalid-xml" }; +const StaticQName QN_XSTREAM_NOT_AUTHORIZED = { NS_XSTREAM, "not-authorized" }; +const StaticQName QN_XSTREAM_POLICY_VIOLATION = + { NS_XSTREAM, "policy-violation" }; +const StaticQName QN_XSTREAM_REMOTE_CONNECTION_FAILED = + { NS_XSTREAM, "remote-connection-failed" }; +const StaticQName QN_XSTREAM_RESOURCE_CONSTRAINT = + { NS_XSTREAM, "resource-constraint" }; +const StaticQName QN_XSTREAM_RESTRICTED_XML = { NS_XSTREAM, "restricted-xml" }; +const StaticQName QN_XSTREAM_SEE_OTHER_HOST = { NS_XSTREAM, "see-other-host" }; +const StaticQName QN_XSTREAM_SYSTEM_SHUTDOWN = + { NS_XSTREAM, "system-shutdown" }; +const StaticQName QN_XSTREAM_UNDEFINED_CONDITION = + { NS_XSTREAM, "undefined-condition" }; +const StaticQName QN_XSTREAM_UNSUPPORTED_ENCODING = + { NS_XSTREAM, "unsupported-encoding" }; +const StaticQName QN_XSTREAM_UNSUPPORTED_STANZA_TYPE = + { NS_XSTREAM, "unsupported-stanza-type" }; +const StaticQName QN_XSTREAM_UNSUPPORTED_VERSION = + { NS_XSTREAM, "unsupported-version" }; +const StaticQName QN_XSTREAM_XML_NOT_WELL_FORMED = + { NS_XSTREAM, "xml-not-well-formed" }; +const StaticQName QN_XSTREAM_TEXT = { NS_XSTREAM, "text" }; + +const StaticQName QN_TLS_STARTTLS = { NS_TLS, "starttls" }; +const StaticQName QN_TLS_REQUIRED = { NS_TLS, "required" }; +const StaticQName QN_TLS_PROCEED = { NS_TLS, "proceed" }; +const StaticQName QN_TLS_FAILURE = { NS_TLS, "failure" }; + +const StaticQName QN_SASL_MECHANISMS = { NS_SASL, "mechanisms" }; +const StaticQName QN_SASL_MECHANISM = { NS_SASL, "mechanism" }; +const StaticQName QN_SASL_AUTH = { NS_SASL, "auth" }; +const StaticQName QN_SASL_CHALLENGE = { NS_SASL, "challenge" }; +const StaticQName QN_SASL_RESPONSE = { NS_SASL, "response" }; +const StaticQName QN_SASL_ABORT = { NS_SASL, "abort" }; +const StaticQName QN_SASL_SUCCESS = { NS_SASL, "success" }; +const StaticQName QN_SASL_FAILURE = { NS_SASL, "failure" }; +const StaticQName QN_SASL_ABORTED = { NS_SASL, "aborted" }; +const StaticQName QN_SASL_INCORRECT_ENCODING = + { NS_SASL, "incorrect-encoding" }; +const StaticQName QN_SASL_INVALID_AUTHZID = { NS_SASL, "invalid-authzid" }; +const StaticQName QN_SASL_INVALID_MECHANISM = { NS_SASL, "invalid-mechanism" }; +const StaticQName QN_SASL_MECHANISM_TOO_WEAK = + { NS_SASL, "mechanism-too-weak" }; +const StaticQName QN_SASL_NOT_AUTHORIZED = { NS_SASL, "not-authorized" }; +const StaticQName QN_SASL_TEMPORARY_AUTH_FAILURE = + { NS_SASL, "temporary-auth-failure" }; + +// These are non-standard. +const char NS_GOOGLE_AUTH_PROTOCOL[] = + "http://www.google.com/talk/protocol/auth"; +const StaticQName QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT = + { NS_GOOGLE_AUTH_PROTOCOL, "client-uses-full-bind-result" }; +const StaticQName QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN = + { NS_GOOGLE_AUTH_PROTOCOL, "allow-non-google-login" }; +const StaticQName QN_GOOGLE_AUTH_SERVICE = + { NS_GOOGLE_AUTH_PROTOCOL, "service" }; + +const StaticQName QN_DIALBACK_RESULT = { NS_DIALBACK, "result" }; +const StaticQName QN_DIALBACK_VERIFY = { NS_DIALBACK, "verify" }; + +const StaticQName QN_STANZA_BAD_REQUEST = { NS_STANZA, "bad-request" }; +const StaticQName QN_STANZA_CONFLICT = { NS_STANZA, "conflict" }; +const StaticQName QN_STANZA_FEATURE_NOT_IMPLEMENTED = + { NS_STANZA, "feature-not-implemented" }; +const StaticQName QN_STANZA_FORBIDDEN = { NS_STANZA, "forbidden" }; +const StaticQName QN_STANZA_GONE = { NS_STANZA, "gone" }; +const StaticQName QN_STANZA_INTERNAL_SERVER_ERROR = + { NS_STANZA, "internal-server-error" }; +const StaticQName QN_STANZA_ITEM_NOT_FOUND = { NS_STANZA, "item-not-found" }; +const StaticQName QN_STANZA_JID_MALFORMED = { NS_STANZA, "jid-malformed" }; +const StaticQName QN_STANZA_NOT_ACCEPTABLE = { NS_STANZA, "not-acceptable" }; +const StaticQName QN_STANZA_NOT_ALLOWED = { NS_STANZA, "not-allowed" }; +const StaticQName QN_STANZA_PAYMENT_REQUIRED = + { NS_STANZA, "payment-required" }; +const StaticQName QN_STANZA_RECIPIENT_UNAVAILABLE = + { NS_STANZA, "recipient-unavailable" }; +const StaticQName QN_STANZA_REDIRECT = { NS_STANZA, "redirect" }; +const StaticQName QN_STANZA_REGISTRATION_REQUIRED = + { NS_STANZA, "registration-required" }; +const StaticQName QN_STANZA_REMOTE_SERVER_NOT_FOUND = + { NS_STANZA, "remote-server-not-found" }; +const StaticQName QN_STANZA_REMOTE_SERVER_TIMEOUT = + { NS_STANZA, "remote-server-timeout" }; +const StaticQName QN_STANZA_RESOURCE_CONSTRAINT = + { NS_STANZA, "resource-constraint" }; +const StaticQName QN_STANZA_SERVICE_UNAVAILABLE = + { NS_STANZA, "service-unavailable" }; +const StaticQName QN_STANZA_SUBSCRIPTION_REQUIRED = + { NS_STANZA, "subscription-required" }; +const StaticQName QN_STANZA_UNDEFINED_CONDITION = + { NS_STANZA, "undefined-condition" }; +const StaticQName QN_STANZA_UNEXPECTED_REQUEST = + { NS_STANZA, "unexpected-request" }; +const StaticQName QN_STANZA_TEXT = { NS_STANZA, "text" }; + +const StaticQName QN_BIND_BIND = { NS_BIND, "bind" }; +const StaticQName QN_BIND_RESOURCE = { NS_BIND, "resource" }; +const StaticQName QN_BIND_JID = { NS_BIND, "jid" }; + +const StaticQName QN_MESSAGE = { NS_CLIENT, "message" }; +const StaticQName QN_BODY = { NS_CLIENT, "body" }; +const StaticQName QN_SUBJECT = { NS_CLIENT, "subject" }; +const StaticQName QN_THREAD = { NS_CLIENT, "thread" }; +const StaticQName QN_PRESENCE = { NS_CLIENT, "presence" }; +const StaticQName QN_SHOW = { NS_CLIENT, "show" }; +const StaticQName QN_STATUS = { NS_CLIENT, "status" }; +const StaticQName QN_LANG = { NS_CLIENT, "lang" }; +const StaticQName QN_PRIORITY = { NS_CLIENT, "priority" }; +const StaticQName QN_IQ = { NS_CLIENT, "iq" }; +const StaticQName QN_ERROR = { NS_CLIENT, "error" }; + +const StaticQName QN_SERVER_MESSAGE = { NS_SERVER, "message" }; +const StaticQName QN_SERVER_BODY = { NS_SERVER, "body" }; +const StaticQName QN_SERVER_SUBJECT = { NS_SERVER, "subject" }; +const StaticQName QN_SERVER_THREAD = { NS_SERVER, "thread" }; +const StaticQName QN_SERVER_PRESENCE = { NS_SERVER, "presence" }; +const StaticQName QN_SERVER_SHOW = { NS_SERVER, "show" }; +const StaticQName QN_SERVER_STATUS = { NS_SERVER, "status" }; +const StaticQName QN_SERVER_LANG = { NS_SERVER, "lang" }; +const StaticQName QN_SERVER_PRIORITY = { NS_SERVER, "priority" }; +const StaticQName QN_SERVER_IQ = { NS_SERVER, "iq" }; +const StaticQName QN_SERVER_ERROR = { NS_SERVER, "error" }; + +const StaticQName QN_SESSION_SESSION = { NS_SESSION, "session" }; + +const StaticQName QN_PRIVACY_QUERY = { NS_PRIVACY, "query" }; +const StaticQName QN_PRIVACY_ACTIVE = { NS_PRIVACY, "active" }; +const StaticQName QN_PRIVACY_DEFAULT = { NS_PRIVACY, "default" }; +const StaticQName QN_PRIVACY_LIST = { NS_PRIVACY, "list" }; +const StaticQName QN_PRIVACY_ITEM = { NS_PRIVACY, "item" }; +const StaticQName QN_PRIVACY_IQ = { NS_PRIVACY, "iq" }; +const StaticQName QN_PRIVACY_MESSAGE = { NS_PRIVACY, "message" }; +const StaticQName QN_PRIVACY_PRESENCE_IN = { NS_PRIVACY, "presence-in" }; +const StaticQName QN_PRIVACY_PRESENCE_OUT = { NS_PRIVACY, "presence-out" }; + +const StaticQName QN_ROSTER_QUERY = { NS_ROSTER, "query" }; +const StaticQName QN_ROSTER_ITEM = { NS_ROSTER, "item" }; +const StaticQName QN_ROSTER_GROUP = { NS_ROSTER, "group" }; + +const StaticQName QN_VCARD = { NS_VCARD, "vCard" }; +const StaticQName QN_VCARD_FN = { NS_VCARD, "FN" }; +const StaticQName QN_VCARD_PHOTO = { NS_VCARD, "PHOTO" }; +const StaticQName QN_VCARD_PHOTO_BINVAL = { NS_VCARD, "BINVAL" }; +const StaticQName QN_VCARD_AVATAR_HASH = { NS_AVATAR_HASH, "hash" }; +const StaticQName QN_VCARD_AVATAR_HASH_MODIFIED = + { NS_AVATAR_HASH, "modified" }; + +const StaticQName QN_NAME = { STR_EMPTY, "name" }; +const StaticQName QN_AFFILIATION = { STR_EMPTY, "affiliation" }; +const StaticQName QN_ROLE = { STR_EMPTY, "role" }; + +#if defined(FEATURE_ENABLE_PSTN) +const StaticQName QN_VCARD_TEL = { NS_VCARD, "TEL" }; +const StaticQName QN_VCARD_VOICE = { NS_VCARD, "VOICE" }; +const StaticQName QN_VCARD_HOME = { NS_VCARD, "HOME" }; +const StaticQName QN_VCARD_WORK = { NS_VCARD, "WORK" }; +const StaticQName QN_VCARD_CELL = { NS_VCARD, "CELL" }; +const StaticQName QN_VCARD_NUMBER = { NS_VCARD, "NUMBER" }; +#endif + +const StaticQName QN_XML_LANG = { NS_XML, "lang" }; + +const StaticQName QN_ENCODING = { STR_EMPTY, STR_ENCODING }; +const StaticQName QN_VERSION = { STR_EMPTY, STR_VERSION }; +const StaticQName QN_TO = { STR_EMPTY, "to" }; +const StaticQName QN_FROM = { STR_EMPTY, "from" }; +const StaticQName QN_TYPE = { STR_EMPTY, "type" }; +const StaticQName QN_ID = { STR_EMPTY, "id" }; +const StaticQName QN_CODE = { STR_EMPTY, "code" }; + +const StaticQName QN_VALUE = { STR_EMPTY, "value" }; +const StaticQName QN_ACTION = { STR_EMPTY, "action" }; +const StaticQName QN_ORDER = { STR_EMPTY, "order" }; +const StaticQName QN_MECHANISM = { STR_EMPTY, "mechanism" }; +const StaticQName QN_ASK = { STR_EMPTY, "ask" }; +const StaticQName QN_JID = { STR_EMPTY, "jid" }; +const StaticQName QN_NICK = { STR_EMPTY, "nick" }; +const StaticQName QN_SUBSCRIPTION = { STR_EMPTY, "subscription" }; +const StaticQName QN_TITLE1 = { STR_EMPTY, "title1" }; +const StaticQName QN_TITLE2 = { STR_EMPTY, "title2" }; + +const StaticQName QN_XMLNS_CLIENT = { NS_XMLNS, STR_CLIENT }; +const StaticQName QN_XMLNS_SERVER = { NS_XMLNS, STR_SERVER }; +const StaticQName QN_XMLNS_STREAM = { NS_XMLNS, STR_STREAM }; + + +// Presence +const char STR_SHOW_AWAY[] = "away"; +const char STR_SHOW_CHAT[] = "chat"; +const char STR_SHOW_DND[] = "dnd"; +const char STR_SHOW_XA[] = "xa"; +const char STR_SHOW_OFFLINE[] = "offline"; + +const char NS_GOOGLE_PSTN_CONFERENCE[] = "http://www.google.com/pstn-conference"; +const StaticQName QN_GOOGLE_PSTN_CONFERENCE_STATUS = { NS_GOOGLE_PSTN_CONFERENCE, "status" }; +const StaticQName QN_ATTR_STATUS = { STR_EMPTY, "status" }; + +// Presence connection status +const char STR_PSTN_CONFERENCE_STATUS_CONNECTING[] = "connecting"; +const char STR_PSTN_CONFERENCE_STATUS_JOINING[] = "joining"; +const char STR_PSTN_CONFERENCE_STATUS_CONNECTED[] = "connected"; +const char STR_PSTN_CONFERENCE_STATUS_HANGUP[] = "hangup"; + +// Subscription +const char STR_SUBSCRIBE[] = "subscribe"; +const char STR_SUBSCRIBED[] = "subscribed"; +const char STR_UNSUBSCRIBE[] = "unsubscribe"; +const char STR_UNSUBSCRIBED[] = "unsubscribed"; + +// Google Invite +const char NS_GOOGLE_SUBSCRIBE[] = "google:subscribe"; +const StaticQName QN_INVITATION = { NS_GOOGLE_SUBSCRIBE, "invitation" }; +const StaticQName QN_INVITE_NAME = { NS_GOOGLE_SUBSCRIBE, "name" }; +const StaticQName QN_INVITE_SUBJECT = { NS_GOOGLE_SUBSCRIBE, "subject" }; +const StaticQName QN_INVITE_MESSAGE = { NS_GOOGLE_SUBSCRIBE, "body" }; + +// Kick +const char NS_GOOGLE_MUC_ADMIN[] = "google:muc#admin"; +const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY = { NS_GOOGLE_MUC_ADMIN, "query" }; +const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY_ITEM = + { NS_GOOGLE_MUC_ADMIN, "item" }; +const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY_ITEM_REASON = + { NS_GOOGLE_MUC_ADMIN, "reason" }; + +// PubSub: http://xmpp.org/extensions/xep-0060.html +const char NS_PUBSUB[] = "http://jabber.org/protocol/pubsub"; +const StaticQName QN_PUBSUB = { NS_PUBSUB, "pubsub" }; +const StaticQName QN_PUBSUB_ITEMS = { NS_PUBSUB, "items" }; +const StaticQName QN_PUBSUB_ITEM = { NS_PUBSUB, "item" }; +const StaticQName QN_PUBSUB_PUBLISH = { NS_PUBSUB, "publish" }; +const StaticQName QN_PUBSUB_RETRACT = { NS_PUBSUB, "retract" }; +const StaticQName QN_ATTR_PUBLISHER = { STR_EMPTY, "publisher" }; + +const char NS_PUBSUB_EVENT[] = "http://jabber.org/protocol/pubsub#event"; +const StaticQName QN_NODE = { STR_EMPTY, "node" }; +const StaticQName QN_PUBSUB_EVENT = { NS_PUBSUB_EVENT, "event" }; +const StaticQName QN_PUBSUB_EVENT_ITEMS = { NS_PUBSUB_EVENT, "items" }; +const StaticQName QN_PUBSUB_EVENT_ITEM = { NS_PUBSUB_EVENT, "item" }; +const StaticQName QN_PUBSUB_EVENT_RETRACT = { NS_PUBSUB_EVENT, "retract" }; +const StaticQName QN_NOTIFY = { STR_EMPTY, "notify" }; + +const char NS_PRESENTER[] = "google:presenter"; +const StaticQName QN_PRESENTER_PRESENTER = { NS_PRESENTER, "presenter" }; +const StaticQName QN_PRESENTER_PRESENTATION_ITEM = + { NS_PRESENTER, "presentation-item" }; +const StaticQName QN_PRESENTER_PRESENTATION_TYPE = + { NS_PRESENTER, "presentation-type" }; +const StaticQName QN_PRESENTER_PRESENTATION_ID = + { NS_PRESENTER, "presentation-id" }; + +// JEP 0030 +const StaticQName QN_CATEGORY = { STR_EMPTY, "category" }; +const StaticQName QN_VAR = { STR_EMPTY, "var" }; +const char NS_DISCO_INFO[] = "http://jabber.org/protocol/disco#info"; +const char NS_DISCO_ITEMS[] = "http://jabber.org/protocol/disco#items"; +const StaticQName QN_DISCO_INFO_QUERY = { NS_DISCO_INFO, "query" }; +const StaticQName QN_DISCO_IDENTITY = { NS_DISCO_INFO, "identity" }; +const StaticQName QN_DISCO_FEATURE = { NS_DISCO_INFO, "feature" }; + +const StaticQName QN_DISCO_ITEMS_QUERY = { NS_DISCO_ITEMS, "query" }; +const StaticQName QN_DISCO_ITEM = { NS_DISCO_ITEMS, "item" }; + +// JEP 0020 +const char NS_FEATURE[] = "http://jabber.org/protocol/feature-neg"; +const StaticQName QN_FEATURE_FEATURE = { NS_FEATURE, "feature" }; + +// JEP 0004 +const char NS_XDATA[] = "jabber:x:data"; +const StaticQName QN_XDATA_X = { NS_XDATA, "x" }; +const StaticQName QN_XDATA_INSTRUCTIONS = { NS_XDATA, "instructions" }; +const StaticQName QN_XDATA_TITLE = { NS_XDATA, "title" }; +const StaticQName QN_XDATA_FIELD = { NS_XDATA, "field" }; +const StaticQName QN_XDATA_REPORTED = { NS_XDATA, "reported" }; +const StaticQName QN_XDATA_ITEM = { NS_XDATA, "item" }; +const StaticQName QN_XDATA_DESC = { NS_XDATA, "desc" }; +const StaticQName QN_XDATA_REQUIRED = { NS_XDATA, "required" }; +const StaticQName QN_XDATA_VALUE = { NS_XDATA, "value" }; +const StaticQName QN_XDATA_OPTION = { NS_XDATA, "option" }; + +// JEP 0045 +const char NS_MUC[] = "http://jabber.org/protocol/muc"; +const StaticQName QN_MUC_X = { NS_MUC, "x" }; +const StaticQName QN_MUC_ITEM = { NS_MUC, "item" }; +const StaticQName QN_MUC_AFFILIATION = { NS_MUC, "affiliation" }; +const StaticQName QN_MUC_ROLE = { NS_MUC, "role" }; +const char STR_AFFILIATION_NONE[] = "none"; +const char STR_ROLE_PARTICIPANT[] = "participant"; + +const char NS_GOOGLE_SESSION[] = "http://www.google.com/session"; +const StaticQName QN_GOOGLE_CIRCLE_ID = { STR_EMPTY, "google-circle-id" }; +const StaticQName QN_GOOGLE_USER_ID = { STR_EMPTY, "google-user-id" }; +const StaticQName QN_GOOGLE_SESSION_BLOCKED = { NS_GOOGLE_SESSION, "blocked" }; +const StaticQName QN_GOOGLE_SESSION_BLOCKING = + { NS_GOOGLE_SESSION, "blocking" }; + +const char NS_MUC_OWNER[] = "http://jabber.org/protocol/muc#owner"; +const StaticQName QN_MUC_OWNER_QUERY = { NS_MUC_OWNER, "query" }; + +const char NS_MUC_USER[] = "http://jabber.org/protocol/muc#user"; +const StaticQName QN_MUC_USER_CONTINUE = { NS_MUC_USER, "continue" }; +const StaticQName QN_MUC_USER_X = { NS_MUC_USER, "x" }; +const StaticQName QN_MUC_USER_ITEM = { NS_MUC_USER, "item" }; +const StaticQName QN_MUC_USER_STATUS = { NS_MUC_USER, "status" }; +const StaticQName QN_MUC_USER_REASON = { NS_MUC_USER, "reason" }; +const StaticQName QN_MUC_USER_ABUSE_VIOLATION = { NS_MUC_USER, "abuse-violation" }; + +// JEP 0055 - Jabber Search +const char NS_SEARCH[] = "jabber:iq:search"; +const StaticQName QN_SEARCH_QUERY = { NS_SEARCH, "query" }; +const StaticQName QN_SEARCH_ITEM = { NS_SEARCH, "item" }; +const StaticQName QN_SEARCH_ROOM_NAME = { NS_SEARCH, "room-name" }; +const StaticQName QN_SEARCH_ROOM_DOMAIN = { NS_SEARCH, "room-domain" }; +const StaticQName QN_SEARCH_ROOM_JID = { NS_SEARCH, "room-jid" }; +const StaticQName QN_SEARCH_HANGOUT_ID = { NS_SEARCH, "hangout-id" }; +const StaticQName QN_SEARCH_EXTERNAL_ID = { NS_SEARCH, "external-id" }; + +// JEP 0115 +const char NS_CAPS[] = "http://jabber.org/protocol/caps"; +const StaticQName QN_CAPS_C = { NS_CAPS, "c" }; +const StaticQName QN_VER = { STR_EMPTY, "ver" }; +const StaticQName QN_EXT = { STR_EMPTY, "ext" }; + +// JEP 0153 +const char kNSVCard[] = "vcard-temp:x:update"; +const StaticQName kQnVCardX = { kNSVCard, "x" }; +const StaticQName kQnVCardPhoto = { kNSVCard, "photo" }; + +// JEP 0172 User Nickname +const char NS_NICKNAME[] = "http://jabber.org/protocol/nick"; +const StaticQName QN_NICKNAME = { NS_NICKNAME, "nick" }; + +// JEP 0085 chat state +const char NS_CHATSTATE[] = "http://jabber.org/protocol/chatstates"; +const StaticQName QN_CS_ACTIVE = { NS_CHATSTATE, "active" }; +const StaticQName QN_CS_COMPOSING = { NS_CHATSTATE, "composing" }; +const StaticQName QN_CS_PAUSED = { NS_CHATSTATE, "paused" }; +const StaticQName QN_CS_INACTIVE = { NS_CHATSTATE, "inactive" }; +const StaticQName QN_CS_GONE = { NS_CHATSTATE, "gone" }; + +// JEP 0091 Delayed Delivery +const char kNSDelay[] = "jabber:x:delay"; +const StaticQName kQnDelayX = { kNSDelay, "x" }; +const StaticQName kQnStamp = { STR_EMPTY, "stamp" }; + +// Google time stamping (higher resolution) +const char kNSTimestamp[] = "google:timestamp"; +const StaticQName kQnTime = { kNSTimestamp, "time" }; +const StaticQName kQnMilliseconds = { STR_EMPTY, "ms" }; + +// Jingle Info +const char NS_JINGLE_INFO[] = "google:jingleinfo"; +const StaticQName QN_JINGLE_INFO_QUERY = { NS_JINGLE_INFO, "query" }; +const StaticQName QN_JINGLE_INFO_STUN = { NS_JINGLE_INFO, "stun" }; +const StaticQName QN_JINGLE_INFO_RELAY = { NS_JINGLE_INFO, "relay" }; +const StaticQName QN_JINGLE_INFO_SERVER = { NS_JINGLE_INFO, "server" }; +const StaticQName QN_JINGLE_INFO_TOKEN = { NS_JINGLE_INFO, "token" }; +const StaticQName QN_JINGLE_INFO_HOST = { STR_EMPTY, "host" }; +const StaticQName QN_JINGLE_INFO_TCP = { STR_EMPTY, "tcp" }; +const StaticQName QN_JINGLE_INFO_UDP = { STR_EMPTY, "udp" }; +const StaticQName QN_JINGLE_INFO_TCPSSL = { STR_EMPTY, "tcpssl" }; + +// Call Performance Logging +const char NS_GOOGLE_CALLPERF_STATS[] = "google:call-perf-stats"; +const StaticQName QN_CALLPERF_STATS = + { NS_GOOGLE_CALLPERF_STATS, "callPerfStats" }; +const StaticQName QN_CALLPERF_SESSIONID = { STR_EMPTY, "sessionId" }; +const StaticQName QN_CALLPERF_LOCALUSER = { STR_EMPTY, "localUser" }; +const StaticQName QN_CALLPERF_REMOTEUSER = { STR_EMPTY, "remoteUser" }; +const StaticQName QN_CALLPERF_STARTTIME = { STR_EMPTY, "startTime" }; +const StaticQName QN_CALLPERF_CALL_LENGTH = { STR_EMPTY, "callLength" }; +const StaticQName QN_CALLPERF_CALL_ACCEPTED = { STR_EMPTY, "callAccepted" }; +const StaticQName QN_CALLPERF_CALL_ERROR_CODE = { STR_EMPTY, "callErrorCode" }; +const StaticQName QN_CALLPERF_TERMINATE_CODE = { STR_EMPTY, "terminateCode" }; +const StaticQName QN_CALLPERF_DATAPOINT = + { NS_GOOGLE_CALLPERF_STATS, "dataPoint" }; +const StaticQName QN_CALLPERF_DATAPOINT_TIME = { STR_EMPTY, "timeStamp" }; +const StaticQName QN_CALLPERF_DATAPOINT_FRACTION_LOST = + { STR_EMPTY, "fraction_lost" }; +const StaticQName QN_CALLPERF_DATAPOINT_CUM_LOST = { STR_EMPTY, "cum_lost" }; +const StaticQName QN_CALLPERF_DATAPOINT_EXT_MAX = { STR_EMPTY, "ext_max" }; +const StaticQName QN_CALLPERF_DATAPOINT_JITTER = { STR_EMPTY, "jitter" }; +const StaticQName QN_CALLPERF_DATAPOINT_RTT = { STR_EMPTY, "RTT" }; +const StaticQName QN_CALLPERF_DATAPOINT_BYTES_R = + { STR_EMPTY, "bytesReceived" }; +const StaticQName QN_CALLPERF_DATAPOINT_PACKETS_R = + { STR_EMPTY, "packetsReceived" }; +const StaticQName QN_CALLPERF_DATAPOINT_BYTES_S = { STR_EMPTY, "bytesSent" }; +const StaticQName QN_CALLPERF_DATAPOINT_PACKETS_S = + { STR_EMPTY, "packetsSent" }; +const StaticQName QN_CALLPERF_DATAPOINT_PROCESS_CPU = + { STR_EMPTY, "processCpu" }; +const StaticQName QN_CALLPERF_DATAPOINT_SYSTEM_CPU = { STR_EMPTY, "systemCpu" }; +const StaticQName QN_CALLPERF_DATAPOINT_CPUS = { STR_EMPTY, "cpus" }; +const StaticQName QN_CALLPERF_CONNECTION = + { NS_GOOGLE_CALLPERF_STATS, "connection" }; +const StaticQName QN_CALLPERF_CONNECTION_LOCAL_ADDRESS = + { STR_EMPTY, "localAddress" }; +const StaticQName QN_CALLPERF_CONNECTION_REMOTE_ADDRESS = + { STR_EMPTY, "remoteAddress" }; +const StaticQName QN_CALLPERF_CONNECTION_FLAGS = { STR_EMPTY, "flags" }; +const StaticQName QN_CALLPERF_CONNECTION_RTT = { STR_EMPTY, "rtt" }; +const StaticQName QN_CALLPERF_CONNECTION_TOTAL_BYTES_S = + { STR_EMPTY, "totalBytesSent" }; +const StaticQName QN_CALLPERF_CONNECTION_BYTES_SECOND_S = + { STR_EMPTY, "bytesSecondSent" }; +const StaticQName QN_CALLPERF_CONNECTION_TOTAL_BYTES_R = + { STR_EMPTY, "totalBytesRecv" }; +const StaticQName QN_CALLPERF_CONNECTION_BYTES_SECOND_R = + { STR_EMPTY, "bytesSecondRecv" }; +const StaticQName QN_CALLPERF_CANDIDATE = + { NS_GOOGLE_CALLPERF_STATS, "candidate" }; +const StaticQName QN_CALLPERF_CANDIDATE_ENDPOINT = { STR_EMPTY, "endpoint" }; +const StaticQName QN_CALLPERF_CANDIDATE_PROTOCOL = { STR_EMPTY, "protocol" }; +const StaticQName QN_CALLPERF_CANDIDATE_ADDRESS = { STR_EMPTY, "address" }; +const StaticQName QN_CALLPERF_MEDIA = { NS_GOOGLE_CALLPERF_STATS, "media" }; +const StaticQName QN_CALLPERF_MEDIA_DIRECTION = { STR_EMPTY, "direction" }; +const StaticQName QN_CALLPERF_MEDIA_SSRC = { STR_EMPTY, "SSRC" }; +const StaticQName QN_CALLPERF_MEDIA_ENERGY = { STR_EMPTY, "energy" }; +const StaticQName QN_CALLPERF_MEDIA_FIR = { STR_EMPTY, "fir" }; +const StaticQName QN_CALLPERF_MEDIA_NACK = { STR_EMPTY, "nack" }; +const StaticQName QN_CALLPERF_MEDIA_FPS = { STR_EMPTY, "fps" }; +const StaticQName QN_CALLPERF_MEDIA_FPS_NETWORK = { STR_EMPTY, "fpsNetwork" }; +const StaticQName QN_CALLPERF_MEDIA_FPS_DECODED = { STR_EMPTY, "fpsDecoded" }; +const StaticQName QN_CALLPERF_MEDIA_JITTER_BUFFER_SIZE = + { STR_EMPTY, "jitterBufferSize" }; +const StaticQName QN_CALLPERF_MEDIA_PREFERRED_JITTER_BUFFER_SIZE = + { STR_EMPTY, "preferredJitterBufferSize" }; +const StaticQName QN_CALLPERF_MEDIA_TOTAL_PLAYOUT_DELAY = + { STR_EMPTY, "totalPlayoutDelay" }; + +// Muc invites. +const StaticQName QN_MUC_USER_INVITE = { NS_MUC_USER, "invite" }; + +// Multiway audio/video. +const char NS_GOOGLE_MUC_USER[] = "google:muc#user"; +const StaticQName QN_GOOGLE_MUC_USER_AVAILABLE_MEDIA = + { NS_GOOGLE_MUC_USER, "available-media" }; +const StaticQName QN_GOOGLE_MUC_USER_ENTRY = { NS_GOOGLE_MUC_USER, "entry" }; +const StaticQName QN_GOOGLE_MUC_USER_MEDIA = { NS_GOOGLE_MUC_USER, "media" }; +const StaticQName QN_GOOGLE_MUC_USER_TYPE = { NS_GOOGLE_MUC_USER, "type" }; +const StaticQName QN_GOOGLE_MUC_USER_SRC_ID = { NS_GOOGLE_MUC_USER, "src-id" }; +const StaticQName QN_GOOGLE_MUC_USER_STATUS = { NS_GOOGLE_MUC_USER, "status" }; +const StaticQName QN_CLIENT_VERSION = { NS_GOOGLE_MUC_USER, "client-version" }; +const StaticQName QN_LOCALE = { NS_GOOGLE_MUC_USER, "locale" }; +const StaticQName QN_LABEL = { STR_EMPTY, "label" }; + +const char NS_GOOGLE_MUC_MEDIA[] = "google:muc#media"; +const StaticQName QN_GOOGLE_MUC_AUDIO_MUTE = + { NS_GOOGLE_MUC_MEDIA, "audio-mute" }; +const StaticQName QN_GOOGLE_MUC_VIDEO_MUTE = + { NS_GOOGLE_MUC_MEDIA, "video-mute" }; +const StaticQName QN_GOOGLE_MUC_VIDEO_PAUSE = + { NS_GOOGLE_MUC_MEDIA, "video-pause" }; +const StaticQName QN_GOOGLE_MUC_RECORDING = + { NS_GOOGLE_MUC_MEDIA, "recording" }; +const StaticQName QN_GOOGLE_MUC_MEDIA_BLOCK = { NS_GOOGLE_MUC_MEDIA, "block" }; +const StaticQName QN_STATE_ATTR = { STR_EMPTY, "state" }; + +const char AUTH_MECHANISM_GOOGLE_COOKIE[] = "X-GOOGLE-COOKIE"; +const char AUTH_MECHANISM_GOOGLE_TOKEN[] = "X-GOOGLE-TOKEN"; +const char AUTH_MECHANISM_OAUTH2[] = "X-OAUTH2"; +const char AUTH_MECHANISM_PLAIN[] = "PLAIN"; + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/constants.h b/webrtc/libjingle/xmpp/constants.h new file mode 100644 index 000000000..5c1967e5f --- /dev/null +++ b/webrtc/libjingle/xmpp/constants.h @@ -0,0 +1,551 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_CONSTANTS_H_ +#define WEBRTC_LIBJINGLE_XMPP_CONSTANTS_H_ + +#include +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmpp/jid.h" + +namespace buzz { + +extern const char NS_CLIENT[]; +extern const char NS_SERVER[]; +extern const char NS_STREAM[]; +extern const char NS_XSTREAM[]; +extern const char NS_TLS[]; +extern const char NS_SASL[]; +extern const char NS_BIND[]; +extern const char NS_DIALBACK[]; +extern const char NS_SESSION[]; +extern const char NS_STANZA[]; +extern const char NS_PRIVACY[]; +extern const char NS_ROSTER[]; +extern const char NS_VCARD[]; +extern const char NS_AVATAR_HASH[]; +extern const char NS_VCARD_UPDATE[]; +extern const char STR_CLIENT[]; +extern const char STR_SERVER[]; +extern const char STR_STREAM[]; + +extern const char STR_GET[]; +extern const char STR_SET[]; +extern const char STR_RESULT[]; +extern const char STR_ERROR[]; + +extern const char STR_FORM[]; +extern const char STR_SUBMIT[]; +extern const char STR_TEXT_SINGLE[]; +extern const char STR_LIST_SINGLE[]; +extern const char STR_LIST_MULTI[]; +extern const char STR_HIDDEN[]; +extern const char STR_FORM_TYPE[]; + +extern const char STR_FROM[]; +extern const char STR_TO[]; +extern const char STR_BOTH[]; +extern const char STR_REMOVE[]; +extern const char STR_TRUE[]; + +extern const char STR_TYPE[]; +extern const char STR_NAME[]; +extern const char STR_ID[]; +extern const char STR_JID[]; +extern const char STR_SUBSCRIPTION[]; +extern const char STR_ASK[]; +extern const char STR_X[]; +extern const char STR_GOOGLE_COM[]; +extern const char STR_GMAIL_COM[]; +extern const char STR_GOOGLEMAIL_COM[]; +extern const char STR_DEFAULT_DOMAIN[]; +extern const char STR_TALK_GOOGLE_COM[]; +extern const char STR_TALKX_L_GOOGLE_COM[]; +extern const char STR_XMPP_GOOGLE_COM[]; +extern const char STR_XMPPX_L_GOOGLE_COM[]; + +#ifdef FEATURE_ENABLE_VOICEMAIL +extern const char STR_VOICEMAIL[]; +extern const char STR_OUTGOINGVOICEMAIL[]; +#endif + +extern const char STR_UNAVAILABLE[]; + +extern const char NS_PING[]; +extern const StaticQName QN_PING; + +extern const char NS_MUC_UNIQUE[]; +extern const StaticQName QN_MUC_UNIQUE_QUERY; +extern const StaticQName QN_HANGOUT_ID; + +extern const char STR_GOOGLE_MUC_LOOKUP_JID[]; +extern const char STR_MUC_ROOMCONFIG_ROOMNAME[]; +extern const char STR_MUC_ROOMCONFIG_FEATURES[]; +extern const char STR_MUC_ROOM_FEATURE_ENTERPRISE[]; +extern const char STR_MUC_ROOMCONFIG[]; +extern const char STR_MUC_ROOM_FEATURE_HANGOUT[]; +extern const char STR_MUC_ROOM_FEATURE_HANGOUT_LITE[]; +extern const char STR_MUC_ROOM_FEATURE_BROADCAST[]; +extern const char STR_MUC_ROOM_FEATURE_MULTI_USER_VC[]; +extern const char STR_MUC_ROOM_FEATURE_RECORDABLE[]; +extern const char STR_MUC_ROOM_FEATURE_CUSTOM_RECORDING[]; +extern const char STR_MUC_ROOM_OWNER_PROFILE_ID[]; +extern const char STR_MUC_ROOM_FEATURE_ABUSE_RECORDABLE[]; + +extern const char STR_ID_TYPE_CONVERSATION[]; +extern const char NS_GOOGLE_MUC_HANGOUT[]; +extern const StaticQName QN_GOOGLE_MUC_HANGOUT_INVITE; +extern const StaticQName QN_GOOGLE_MUC_HANGOUT_INVITE_TYPE; +extern const StaticQName QN_ATTR_CREATE_ACTIVITY; +extern const StaticQName QN_GOOGLE_MUC_HANGOUT_PUBLIC; +extern const StaticQName QN_GOOGLE_MUC_HANGOUT_INVITEE; +extern const StaticQName QN_GOOGLE_MUC_HANGOUT_NOTIFICATION_STATUS; +extern const StaticQName QN_GOOGLE_MUC_HANGOUT_NOTIFICATION_TYPE; +extern const StaticQName QN_GOOGLE_MUC_HANGOUT_HANGOUT_START_CONTEXT; +extern const StaticQName QN_GOOGLE_MUC_HANGOUT_CONVERSATION_ID; + +extern const StaticQName QN_STREAM_STREAM; +extern const StaticQName QN_STREAM_FEATURES; +extern const StaticQName QN_STREAM_ERROR; + +extern const StaticQName QN_XSTREAM_BAD_FORMAT; +extern const StaticQName QN_XSTREAM_BAD_NAMESPACE_PREFIX; +extern const StaticQName QN_XSTREAM_CONFLICT; +extern const StaticQName QN_XSTREAM_CONNECTION_TIMEOUT; +extern const StaticQName QN_XSTREAM_HOST_GONE; +extern const StaticQName QN_XSTREAM_HOST_UNKNOWN; +extern const StaticQName QN_XSTREAM_IMPROPER_ADDRESSIING; +extern const StaticQName QN_XSTREAM_INTERNAL_SERVER_ERROR; +extern const StaticQName QN_XSTREAM_INVALID_FROM; +extern const StaticQName QN_XSTREAM_INVALID_ID; +extern const StaticQName QN_XSTREAM_INVALID_NAMESPACE; +extern const StaticQName QN_XSTREAM_INVALID_XML; +extern const StaticQName QN_XSTREAM_NOT_AUTHORIZED; +extern const StaticQName QN_XSTREAM_POLICY_VIOLATION; +extern const StaticQName QN_XSTREAM_REMOTE_CONNECTION_FAILED; +extern const StaticQName QN_XSTREAM_RESOURCE_CONSTRAINT; +extern const StaticQName QN_XSTREAM_RESTRICTED_XML; +extern const StaticQName QN_XSTREAM_SEE_OTHER_HOST; +extern const StaticQName QN_XSTREAM_SYSTEM_SHUTDOWN; +extern const StaticQName QN_XSTREAM_UNDEFINED_CONDITION; +extern const StaticQName QN_XSTREAM_UNSUPPORTED_ENCODING; +extern const StaticQName QN_XSTREAM_UNSUPPORTED_STANZA_TYPE; +extern const StaticQName QN_XSTREAM_UNSUPPORTED_VERSION; +extern const StaticQName QN_XSTREAM_XML_NOT_WELL_FORMED; +extern const StaticQName QN_XSTREAM_TEXT; + +extern const StaticQName QN_TLS_STARTTLS; +extern const StaticQName QN_TLS_REQUIRED; +extern const StaticQName QN_TLS_PROCEED; +extern const StaticQName QN_TLS_FAILURE; + +extern const StaticQName QN_SASL_MECHANISMS; +extern const StaticQName QN_SASL_MECHANISM; +extern const StaticQName QN_SASL_AUTH; +extern const StaticQName QN_SASL_CHALLENGE; +extern const StaticQName QN_SASL_RESPONSE; +extern const StaticQName QN_SASL_ABORT; +extern const StaticQName QN_SASL_SUCCESS; +extern const StaticQName QN_SASL_FAILURE; +extern const StaticQName QN_SASL_ABORTED; +extern const StaticQName QN_SASL_INCORRECT_ENCODING; +extern const StaticQName QN_SASL_INVALID_AUTHZID; +extern const StaticQName QN_SASL_INVALID_MECHANISM; +extern const StaticQName QN_SASL_MECHANISM_TOO_WEAK; +extern const StaticQName QN_SASL_NOT_AUTHORIZED; +extern const StaticQName QN_SASL_TEMPORARY_AUTH_FAILURE; + +// These are non-standard. +extern const char NS_GOOGLE_AUTH[]; +extern const char NS_GOOGLE_AUTH_PROTOCOL[]; +extern const StaticQName QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT; +extern const StaticQName QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN; +extern const StaticQName QN_GOOGLE_AUTH_SERVICE; + +extern const StaticQName QN_DIALBACK_RESULT; +extern const StaticQName QN_DIALBACK_VERIFY; + +extern const StaticQName QN_STANZA_BAD_REQUEST; +extern const StaticQName QN_STANZA_CONFLICT; +extern const StaticQName QN_STANZA_FEATURE_NOT_IMPLEMENTED; +extern const StaticQName QN_STANZA_FORBIDDEN; +extern const StaticQName QN_STANZA_GONE; +extern const StaticQName QN_STANZA_INTERNAL_SERVER_ERROR; +extern const StaticQName QN_STANZA_ITEM_NOT_FOUND; +extern const StaticQName QN_STANZA_JID_MALFORMED; +extern const StaticQName QN_STANZA_NOT_ACCEPTABLE; +extern const StaticQName QN_STANZA_NOT_ALLOWED; +extern const StaticQName QN_STANZA_PAYMENT_REQUIRED; +extern const StaticQName QN_STANZA_RECIPIENT_UNAVAILABLE; +extern const StaticQName QN_STANZA_REDIRECT; +extern const StaticQName QN_STANZA_REGISTRATION_REQUIRED; +extern const StaticQName QN_STANZA_REMOTE_SERVER_NOT_FOUND; +extern const StaticQName QN_STANZA_REMOTE_SERVER_TIMEOUT; +extern const StaticQName QN_STANZA_RESOURCE_CONSTRAINT; +extern const StaticQName QN_STANZA_SERVICE_UNAVAILABLE; +extern const StaticQName QN_STANZA_SUBSCRIPTION_REQUIRED; +extern const StaticQName QN_STANZA_UNDEFINED_CONDITION; +extern const StaticQName QN_STANZA_UNEXPECTED_REQUEST; +extern const StaticQName QN_STANZA_TEXT; + +extern const StaticQName QN_BIND_BIND; +extern const StaticQName QN_BIND_RESOURCE; +extern const StaticQName QN_BIND_JID; + +extern const StaticQName QN_MESSAGE; +extern const StaticQName QN_BODY; +extern const StaticQName QN_SUBJECT; +extern const StaticQName QN_THREAD; +extern const StaticQName QN_PRESENCE; +extern const StaticQName QN_SHOW; +extern const StaticQName QN_STATUS; +extern const StaticQName QN_LANG; +extern const StaticQName QN_PRIORITY; +extern const StaticQName QN_IQ; +extern const StaticQName QN_ERROR; + +extern const StaticQName QN_SERVER_MESSAGE; +extern const StaticQName QN_SERVER_BODY; +extern const StaticQName QN_SERVER_SUBJECT; +extern const StaticQName QN_SERVER_THREAD; +extern const StaticQName QN_SERVER_PRESENCE; +extern const StaticQName QN_SERVER_SHOW; +extern const StaticQName QN_SERVER_STATUS; +extern const StaticQName QN_SERVER_LANG; +extern const StaticQName QN_SERVER_PRIORITY; +extern const StaticQName QN_SERVER_IQ; +extern const StaticQName QN_SERVER_ERROR; + +extern const StaticQName QN_SESSION_SESSION; + +extern const StaticQName QN_PRIVACY_QUERY; +extern const StaticQName QN_PRIVACY_ACTIVE; +extern const StaticQName QN_PRIVACY_DEFAULT; +extern const StaticQName QN_PRIVACY_LIST; +extern const StaticQName QN_PRIVACY_ITEM; +extern const StaticQName QN_PRIVACY_IQ; +extern const StaticQName QN_PRIVACY_MESSAGE; +extern const StaticQName QN_PRIVACY_PRESENCE_IN; +extern const StaticQName QN_PRIVACY_PRESENCE_OUT; + +extern const StaticQName QN_ROSTER_QUERY; +extern const StaticQName QN_ROSTER_ITEM; +extern const StaticQName QN_ROSTER_GROUP; + +extern const StaticQName QN_VCARD; +extern const StaticQName QN_VCARD_FN; +extern const StaticQName QN_VCARD_PHOTO; +extern const StaticQName QN_VCARD_PHOTO_BINVAL; +extern const StaticQName QN_VCARD_AVATAR_HASH; +extern const StaticQName QN_VCARD_AVATAR_HASH_MODIFIED; + +#if defined(FEATURE_ENABLE_PSTN) +extern const StaticQName QN_VCARD_TEL; +extern const StaticQName QN_VCARD_VOICE; +extern const StaticQName QN_VCARD_HOME; +extern const StaticQName QN_VCARD_WORK; +extern const StaticQName QN_VCARD_CELL; +extern const StaticQName QN_VCARD_NUMBER; +#endif + +#if defined(FEATURE_ENABLE_RICHPROFILES) +extern const StaticQName QN_USER_PROFILE_QUERY; +extern const StaticQName QN_USER_PROFILE_URL; + +extern const StaticQName QN_ATOM_FEED; +extern const StaticQName QN_ATOM_ENTRY; +extern const StaticQName QN_ATOM_TITLE; +extern const StaticQName QN_ATOM_ID; +extern const StaticQName QN_ATOM_MODIFIED; +extern const StaticQName QN_ATOM_IMAGE; +extern const StaticQName QN_ATOM_LINK; +extern const StaticQName QN_ATOM_HREF; +#endif + +extern const StaticQName QN_XML_LANG; + +extern const StaticQName QN_ENCODING; +extern const StaticQName QN_VERSION; +extern const StaticQName QN_TO; +extern const StaticQName QN_FROM; +extern const StaticQName QN_TYPE; +extern const StaticQName QN_ID; +extern const StaticQName QN_CODE; +extern const StaticQName QN_NAME; +extern const StaticQName QN_VALUE; +extern const StaticQName QN_ACTION; +extern const StaticQName QN_ORDER; +extern const StaticQName QN_MECHANISM; +extern const StaticQName QN_ASK; +extern const StaticQName QN_JID; +extern const StaticQName QN_NICK; +extern const StaticQName QN_SUBSCRIPTION; +extern const StaticQName QN_TITLE1; +extern const StaticQName QN_TITLE2; +extern const StaticQName QN_AFFILIATION; +extern const StaticQName QN_ROLE; +extern const StaticQName QN_TIME; + +extern const StaticQName QN_XMLNS_CLIENT; +extern const StaticQName QN_XMLNS_SERVER; +extern const StaticQName QN_XMLNS_STREAM; + +// Presence +extern const char STR_SHOW_AWAY[]; +extern const char STR_SHOW_CHAT[]; +extern const char STR_SHOW_DND[]; +extern const char STR_SHOW_XA[]; +extern const char STR_SHOW_OFFLINE[]; + +extern const char NS_GOOGLE_PSTN_CONFERENCE[]; +extern const StaticQName QN_GOOGLE_PSTN_CONFERENCE_STATUS; +extern const StaticQName QN_ATTR_STATUS; + +// Presence connection status +extern const char STR_PSTN_CONFERENCE_STATUS_CONNECTING[]; +extern const char STR_PSTN_CONFERENCE_STATUS_JOINING[]; +extern const char STR_PSTN_CONFERENCE_STATUS_CONNECTED[]; +extern const char STR_PSTN_CONFERENCE_STATUS_HANGUP[]; + +// Subscription +extern const char STR_SUBSCRIBE[]; +extern const char STR_SUBSCRIBED[]; +extern const char STR_UNSUBSCRIBE[]; +extern const char STR_UNSUBSCRIBED[]; + +// Google Invite +extern const char NS_GOOGLE_SUBSCRIBE[]; +extern const StaticQName QN_INVITATION; +extern const StaticQName QN_INVITE_NAME; +extern const StaticQName QN_INVITE_SUBJECT; +extern const StaticQName QN_INVITE_MESSAGE; + +// Kick +extern const char NS_GOOGLE_MUC_ADMIN[]; +extern const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY; +extern const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY_ITEM; +extern const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY_ITEM_REASON; + +// PubSub: http://xmpp.org/extensions/xep-0060.html +extern const char NS_PUBSUB[]; +extern const StaticQName QN_PUBSUB; +extern const StaticQName QN_PUBSUB_ITEMS; +extern const StaticQName QN_PUBSUB_ITEM; +extern const StaticQName QN_PUBSUB_PUBLISH; +extern const StaticQName QN_PUBSUB_RETRACT; +extern const StaticQName QN_ATTR_PUBLISHER; + +extern const char NS_PUBSUB_EVENT[]; +extern const StaticQName QN_NODE; +extern const StaticQName QN_PUBSUB_EVENT; +extern const StaticQName QN_PUBSUB_EVENT_ITEMS; +extern const StaticQName QN_PUBSUB_EVENT_ITEM; +extern const StaticQName QN_PUBSUB_EVENT_RETRACT; +extern const StaticQName QN_NOTIFY; + +extern const char NS_PRESENTER[]; +extern const StaticQName QN_PRESENTER_PRESENTER; +extern const StaticQName QN_PRESENTER_PRESENTATION_ITEM; +extern const StaticQName QN_PRESENTER_PRESENTATION_TYPE; +extern const StaticQName QN_PRESENTER_PRESENTATION_ID; + +// JEP 0030 +extern const StaticQName QN_CATEGORY; +extern const StaticQName QN_VAR; +extern const char NS_DISCO_INFO[]; +extern const char NS_DISCO_ITEMS[]; + +extern const StaticQName QN_DISCO_INFO_QUERY; +extern const StaticQName QN_DISCO_IDENTITY; +extern const StaticQName QN_DISCO_FEATURE; + +extern const StaticQName QN_DISCO_ITEMS_QUERY; +extern const StaticQName QN_DISCO_ITEM; + +// JEP 0020 +extern const char NS_FEATURE[]; +extern const StaticQName QN_FEATURE_FEATURE; + +// JEP 0004 +extern const char NS_XDATA[]; +extern const StaticQName QN_XDATA_X; +extern const StaticQName QN_XDATA_INSTRUCTIONS; +extern const StaticQName QN_XDATA_TITLE; +extern const StaticQName QN_XDATA_FIELD; +extern const StaticQName QN_XDATA_REPORTED; +extern const StaticQName QN_XDATA_ITEM; +extern const StaticQName QN_XDATA_DESC; +extern const StaticQName QN_XDATA_REQUIRED; +extern const StaticQName QN_XDATA_VALUE; +extern const StaticQName QN_XDATA_OPTION; + +// JEP 0045 +extern const char NS_MUC[]; +extern const StaticQName QN_MUC_X; +extern const StaticQName QN_MUC_ITEM; +extern const StaticQName QN_MUC_AFFILIATION; +extern const StaticQName QN_MUC_ROLE; +extern const StaticQName QN_CLIENT_VERSION; +extern const StaticQName QN_LOCALE; +extern const char STR_AFFILIATION_NONE[]; +extern const char STR_ROLE_PARTICIPANT[]; + +extern const char NS_GOOGLE_SESSION[]; +extern const StaticQName QN_GOOGLE_USER_ID; +extern const StaticQName QN_GOOGLE_CIRCLE_ID; +extern const StaticQName QN_GOOGLE_SESSION_BLOCKED; +extern const StaticQName QN_GOOGLE_SESSION_BLOCKING; + +extern const char NS_MUC_OWNER[]; +extern const StaticQName QN_MUC_OWNER_QUERY; + +extern const char NS_MUC_USER[]; +extern const StaticQName QN_MUC_USER_CONTINUE; +extern const StaticQName QN_MUC_USER_X; +extern const StaticQName QN_MUC_USER_ITEM; +extern const StaticQName QN_MUC_USER_STATUS; +extern const StaticQName QN_MUC_USER_REASON; +extern const StaticQName QN_MUC_USER_ABUSE_VIOLATION; + +// JEP 0055 - Jabber Search +extern const char NS_SEARCH[]; +extern const StaticQName QN_SEARCH_QUERY; +extern const StaticQName QN_SEARCH_ITEM; +extern const StaticQName QN_SEARCH_ROOM_NAME; +extern const StaticQName QN_SEARCH_ROOM_JID; +extern const StaticQName QN_SEARCH_ROOM_DOMAIN; +extern const StaticQName QN_SEARCH_HANGOUT_ID; +extern const StaticQName QN_SEARCH_EXTERNAL_ID; + +// JEP 0115 +extern const char NS_CAPS[]; +extern const StaticQName QN_CAPS_C; +extern const StaticQName QN_VER; +extern const StaticQName QN_EXT; + + +// Avatar - JEP 0153 +extern const char kNSVCard[]; +extern const StaticQName kQnVCardX; +extern const StaticQName kQnVCardPhoto; + +// JEP 0172 User Nickname +extern const char NS_NICKNAME[]; +extern const StaticQName QN_NICKNAME; + +// JEP 0085 chat state +extern const char NS_CHATSTATE[]; +extern const StaticQName QN_CS_ACTIVE; +extern const StaticQName QN_CS_COMPOSING; +extern const StaticQName QN_CS_PAUSED; +extern const StaticQName QN_CS_INACTIVE; +extern const StaticQName QN_CS_GONE; + +// JEP 0091 Delayed Delivery +extern const char kNSDelay[]; +extern const StaticQName kQnDelayX; +extern const StaticQName kQnStamp; + +// Google time stamping (higher resolution) +extern const char kNSTimestamp[]; +extern const StaticQName kQnTime; +extern const StaticQName kQnMilliseconds; + +extern const char NS_JINGLE_INFO[]; +extern const StaticQName QN_JINGLE_INFO_QUERY; +extern const StaticQName QN_JINGLE_INFO_STUN; +extern const StaticQName QN_JINGLE_INFO_RELAY; +extern const StaticQName QN_JINGLE_INFO_SERVER; +extern const StaticQName QN_JINGLE_INFO_TOKEN; +extern const StaticQName QN_JINGLE_INFO_HOST; +extern const StaticQName QN_JINGLE_INFO_TCP; +extern const StaticQName QN_JINGLE_INFO_UDP; +extern const StaticQName QN_JINGLE_INFO_TCPSSL; + +extern const char NS_GOOGLE_CALLPERF_STATS[]; +extern const StaticQName QN_CALLPERF_STATS; +extern const StaticQName QN_CALLPERF_SESSIONID; +extern const StaticQName QN_CALLPERF_LOCALUSER; +extern const StaticQName QN_CALLPERF_REMOTEUSER; +extern const StaticQName QN_CALLPERF_STARTTIME; +extern const StaticQName QN_CALLPERF_CALL_LENGTH; +extern const StaticQName QN_CALLPERF_CALL_ACCEPTED; +extern const StaticQName QN_CALLPERF_CALL_ERROR_CODE; +extern const StaticQName QN_CALLPERF_TERMINATE_CODE; +extern const StaticQName QN_CALLPERF_DATAPOINT; +extern const StaticQName QN_CALLPERF_DATAPOINT_TIME; +extern const StaticQName QN_CALLPERF_DATAPOINT_FRACTION_LOST; +extern const StaticQName QN_CALLPERF_DATAPOINT_CUM_LOST; +extern const StaticQName QN_CALLPERF_DATAPOINT_EXT_MAX; +extern const StaticQName QN_CALLPERF_DATAPOINT_JITTER; +extern const StaticQName QN_CALLPERF_DATAPOINT_RTT; +extern const StaticQName QN_CALLPERF_DATAPOINT_BYTES_R; +extern const StaticQName QN_CALLPERF_DATAPOINT_PACKETS_R; +extern const StaticQName QN_CALLPERF_DATAPOINT_BYTES_S; +extern const StaticQName QN_CALLPERF_DATAPOINT_PACKETS_S; +extern const StaticQName QN_CALLPERF_DATAPOINT_PROCESS_CPU; +extern const StaticQName QN_CALLPERF_DATAPOINT_SYSTEM_CPU; +extern const StaticQName QN_CALLPERF_DATAPOINT_CPUS; +extern const StaticQName QN_CALLPERF_CONNECTION; +extern const StaticQName QN_CALLPERF_CONNECTION_LOCAL_ADDRESS; +extern const StaticQName QN_CALLPERF_CONNECTION_REMOTE_ADDRESS; +extern const StaticQName QN_CALLPERF_CONNECTION_FLAGS; +extern const StaticQName QN_CALLPERF_CONNECTION_RTT; +extern const StaticQName QN_CALLPERF_CONNECTION_TOTAL_BYTES_S; +extern const StaticQName QN_CALLPERF_CONNECTION_BYTES_SECOND_S; +extern const StaticQName QN_CALLPERF_CONNECTION_TOTAL_BYTES_R; +extern const StaticQName QN_CALLPERF_CONNECTION_BYTES_SECOND_R; +extern const StaticQName QN_CALLPERF_CANDIDATE; +extern const StaticQName QN_CALLPERF_CANDIDATE_ENDPOINT; +extern const StaticQName QN_CALLPERF_CANDIDATE_PROTOCOL; +extern const StaticQName QN_CALLPERF_CANDIDATE_ADDRESS; +extern const StaticQName QN_CALLPERF_MEDIA; +extern const StaticQName QN_CALLPERF_MEDIA_DIRECTION; +extern const StaticQName QN_CALLPERF_MEDIA_SSRC; +extern const StaticQName QN_CALLPERF_MEDIA_ENERGY; +extern const StaticQName QN_CALLPERF_MEDIA_FIR; +extern const StaticQName QN_CALLPERF_MEDIA_NACK; +extern const StaticQName QN_CALLPERF_MEDIA_FPS; +extern const StaticQName QN_CALLPERF_MEDIA_FPS_NETWORK; +extern const StaticQName QN_CALLPERF_MEDIA_FPS_DECODED; +extern const StaticQName QN_CALLPERF_MEDIA_JITTER_BUFFER_SIZE; +extern const StaticQName QN_CALLPERF_MEDIA_PREFERRED_JITTER_BUFFER_SIZE; +extern const StaticQName QN_CALLPERF_MEDIA_TOTAL_PLAYOUT_DELAY; + +// Muc invites. +extern const StaticQName QN_MUC_USER_INVITE; + +// Multiway audio/video. +extern const char NS_GOOGLE_MUC_USER[]; +extern const StaticQName QN_GOOGLE_MUC_USER_AVAILABLE_MEDIA; +extern const StaticQName QN_GOOGLE_MUC_USER_ENTRY; +extern const StaticQName QN_GOOGLE_MUC_USER_MEDIA; +extern const StaticQName QN_GOOGLE_MUC_USER_TYPE; +extern const StaticQName QN_GOOGLE_MUC_USER_SRC_ID; +extern const StaticQName QN_GOOGLE_MUC_USER_STATUS; +extern const StaticQName QN_LABEL; + +extern const char NS_GOOGLE_MUC_MEDIA[]; +extern const StaticQName QN_GOOGLE_MUC_AUDIO_MUTE; +extern const StaticQName QN_GOOGLE_MUC_VIDEO_MUTE; +extern const StaticQName QN_GOOGLE_MUC_VIDEO_PAUSE; +extern const StaticQName QN_GOOGLE_MUC_RECORDING; +extern const StaticQName QN_GOOGLE_MUC_MEDIA_BLOCK; +extern const StaticQName QN_STATE_ATTR; + + +extern const char AUTH_MECHANISM_GOOGLE_COOKIE[]; +extern const char AUTH_MECHANISM_GOOGLE_TOKEN[]; +extern const char AUTH_MECHANISM_OAUTH2[]; +extern const char AUTH_MECHANISM_PLAIN[]; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_CONSTANTS_H_ diff --git a/webrtc/libjingle/xmpp/discoitemsquerytask.cc b/webrtc/libjingle/xmpp/discoitemsquerytask.cc new file mode 100644 index 000000000..765ee1439 --- /dev/null +++ b/webrtc/libjingle/xmpp/discoitemsquerytask.cc @@ -0,0 +1,62 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/discoitemsquerytask.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" +#include "webrtc/base/scoped_ptr.h" + +namespace buzz { + +DiscoItemsQueryTask::DiscoItemsQueryTask(XmppTaskParentInterface* parent, + const Jid& to, + const std::string& node) + : IqTask(parent, STR_GET, to, MakeRequest(node)) { +} + +XmlElement* DiscoItemsQueryTask::MakeRequest(const std::string& node) { + XmlElement* element = new XmlElement(QN_DISCO_ITEMS_QUERY, true); + if (!node.empty()) { + element->AddAttr(QN_NODE, node); + } + return element; +} + +void DiscoItemsQueryTask::HandleResult(const XmlElement* stanza) { + const XmlElement* query = stanza->FirstNamed(QN_DISCO_ITEMS_QUERY); + if (query) { + std::vector items; + for (const buzz::XmlChild* child = query->FirstChild(); child; + child = child->NextChild()) { + DiscoItem item; + const buzz::XmlElement* child_element = child->AsElement(); + if (ParseItem(child_element, &item)) { + items.push_back(item); + } + } + SignalResult(items); + } else { + SignalError(this, NULL); + } +} + +bool DiscoItemsQueryTask::ParseItem(const XmlElement* element, + DiscoItem* item) { + if (element->HasAttr(QN_JID)) { + return false; + } + + item->jid = element->Attr(QN_JID); + item->name = element->Attr(QN_NAME); + item->node = element->Attr(QN_NODE); + return true; +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/discoitemsquerytask.h b/webrtc/libjingle/xmpp/discoitemsquerytask.h new file mode 100644 index 000000000..62e862e19 --- /dev/null +++ b/webrtc/libjingle/xmpp/discoitemsquerytask.h @@ -0,0 +1,65 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Fires a disco items query, such as the following example: +// +// +// +// +// +// Sample response: +// +// +// +// +// +// + + +#ifndef WEBRTC_LIBJINGLE_XMPP_DISCOITEMSQUERYTASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_DISCOITEMSQUERYTASK_H_ + +#include +#include + +#include "webrtc/libjingle/xmpp/iqtask.h" + +namespace buzz { + +struct DiscoItem { + std::string jid; + std::string node; + std::string name; +}; + +class DiscoItemsQueryTask : public IqTask { + public: + DiscoItemsQueryTask(XmppTaskParentInterface* parent, + const Jid& to, const std::string& node); + + sigslot::signal1 > SignalResult; + + private: + static XmlElement* MakeRequest(const std::string& node); + virtual void HandleResult(const XmlElement* result); + static bool ParseItem(const XmlElement* element, DiscoItem* item); +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_DISCOITEMSQUERYTASK_H_ diff --git a/webrtc/libjingle/xmpp/fakexmppclient.h b/webrtc/libjingle/xmpp/fakexmppclient.h new file mode 100644 index 000000000..453a7c86f --- /dev/null +++ b/webrtc/libjingle/xmpp/fakexmppclient.h @@ -0,0 +1,106 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// A fake XmppClient for use in unit tests. + +#ifndef WEBRTC_LIBJINGLE_XMPP_FAKEXMPPCLIENT_H_ +#define WEBRTC_LIBJINGLE_XMPP_FAKEXMPPCLIENT_H_ + +#include +#include + +#include "webrtc/libjingle/xmpp/xmpptask.h" + +namespace buzz { + +class XmlElement; + +class FakeXmppClient : public XmppTaskParentInterface, + public XmppClientInterface { + public: + explicit FakeXmppClient(rtc::TaskParent* parent) + : XmppTaskParentInterface(parent) { + } + + // As XmppTaskParentInterface + virtual XmppClientInterface* GetClient() { + return this; + } + + virtual int ProcessStart() { + return STATE_RESPONSE; + } + + // As XmppClientInterface + virtual XmppEngine::State GetState() const { + return XmppEngine::STATE_OPEN; + } + + virtual const Jid& jid() const { + return jid_; + } + + virtual std::string NextId() { + // Implement if needed for tests. + return "0"; + } + + virtual XmppReturnStatus SendStanza(const XmlElement* stanza) { + sent_stanzas_.push_back(stanza); + return XMPP_RETURN_OK; + } + + const std::vector& sent_stanzas() { + return sent_stanzas_; + } + + virtual XmppReturnStatus SendStanzaError( + const XmlElement * pelOriginal, + XmppStanzaError code, + const std::string & text) { + // Implement if needed for tests. + return XMPP_RETURN_OK; + } + + virtual void AddXmppTask(XmppTask* task, + XmppEngine::HandlerLevel level) { + tasks_.push_back(task); + } + + virtual void RemoveXmppTask(XmppTask* task) { + std::remove(tasks_.begin(), tasks_.end(), task); + } + + // As FakeXmppClient + void set_jid(const Jid& jid) { + jid_ = jid; + } + + // Takes ownership of stanza. + void HandleStanza(XmlElement* stanza) { + for (std::vector::iterator task = tasks_.begin(); + task != tasks_.end(); ++task) { + if ((*task)->HandleStanza(stanza)) { + delete stanza; + return; + } + } + delete stanza; + } + + private: + Jid jid_; + std::vector tasks_; + std::vector sent_stanzas_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_FAKEXMPPCLIENT_H_ diff --git a/webrtc/libjingle/xmpp/hangoutpubsubclient.cc b/webrtc/libjingle/xmpp/hangoutpubsubclient.cc new file mode 100644 index 000000000..db1ac3149 --- /dev/null +++ b/webrtc/libjingle/xmpp/hangoutpubsubclient.cc @@ -0,0 +1,400 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/hangoutpubsubclient.h" + +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/base/logging.h" + + +// Gives a high-level API for MUC call PubSub needs such as +// presenter state, recording state, mute state, and remote mute. + +namespace buzz { + +namespace { +const char kPresenting[] = "s"; +const char kNotPresenting[] = "o"; + +} // namespace + +// A simple serialiazer where presence of item => true, lack of item +// => false. +class BoolStateSerializer : public PubSubStateSerializer { + virtual XmlElement* Write(const QName& state_name, const bool& state) { + if (!state) { + return NULL; + } + + return new XmlElement(state_name, true); + } + + virtual void Parse(const XmlElement* state_elem, bool *state_out) { + *state_out = state_elem != NULL; + } +}; + +class PresenterStateClient : public PubSubStateClient { + public: + PresenterStateClient(const std::string& publisher_nick, + PubSubClient* client, + const QName& state_name, + bool default_state) + : PubSubStateClient( + publisher_nick, client, state_name, default_state, + new PublishedNickKeySerializer(), NULL) { + } + + virtual void Publish(const std::string& published_nick, + const bool& state, + std::string* task_id_out) { + XmlElement* presenter_elem = new XmlElement(QN_PRESENTER_PRESENTER, true); + presenter_elem->AddAttr(QN_NICK, published_nick); + + XmlElement* presentation_item_elem = + new XmlElement(QN_PRESENTER_PRESENTATION_ITEM, false); + const std::string& presentation_type = state ? kPresenting : kNotPresenting; + presentation_item_elem->AddAttr( + QN_PRESENTER_PRESENTATION_TYPE, presentation_type); + + // The Presenter state is kind of dumb in that it doesn't always use + // retracts. It relies on setting the "type" to a special value. + std::string itemid = published_nick; + std::vector children; + children.push_back(presenter_elem); + children.push_back(presentation_item_elem); + client()->PublishItem(itemid, children, task_id_out); + } + + protected: + virtual bool ParseStateItem(const PubSubItem& item, + StateItemInfo* info_out, + bool* state_out) { + const XmlElement* presenter_elem = + item.elem->FirstNamed(QN_PRESENTER_PRESENTER); + const XmlElement* presentation_item_elem = + item.elem->FirstNamed(QN_PRESENTER_PRESENTATION_ITEM); + if (presentation_item_elem == NULL || presenter_elem == NULL) { + return false; + } + + info_out->publisher_nick = + client()->GetPublisherNickFromPubSubItem(item.elem); + info_out->published_nick = presenter_elem->Attr(QN_NICK); + *state_out = (presentation_item_elem->Attr( + QN_PRESENTER_PRESENTATION_TYPE) != kNotPresenting); + return true; + } + + virtual bool StatesEqual(const bool& state1, const bool& state2) { + // Make every item trigger an event, even if state doesn't change. + return false; + } +}; + +HangoutPubSubClient::HangoutPubSubClient(XmppTaskParentInterface* parent, + const Jid& mucjid, + const std::string& nick) + : mucjid_(mucjid), + nick_(nick) { + presenter_client_.reset(new PubSubClient(parent, mucjid, NS_PRESENTER)); + presenter_client_->SignalRequestError.connect( + this, &HangoutPubSubClient::OnPresenterRequestError); + + media_client_.reset(new PubSubClient(parent, mucjid, NS_GOOGLE_MUC_MEDIA)); + media_client_->SignalRequestError.connect( + this, &HangoutPubSubClient::OnMediaRequestError); + + presenter_state_client_.reset(new PresenterStateClient( + nick_, presenter_client_.get(), QN_PRESENTER_PRESENTER, false)); + presenter_state_client_->SignalStateChange.connect( + this, &HangoutPubSubClient::OnPresenterStateChange); + presenter_state_client_->SignalPublishResult.connect( + this, &HangoutPubSubClient::OnPresenterPublishResult); + presenter_state_client_->SignalPublishError.connect( + this, &HangoutPubSubClient::OnPresenterPublishError); + + audio_mute_state_client_.reset(new PubSubStateClient( + nick_, media_client_.get(), QN_GOOGLE_MUC_AUDIO_MUTE, false, + new PublishedNickKeySerializer(), new BoolStateSerializer())); + // Can't just repeat because we need to watch for remote mutes. + audio_mute_state_client_->SignalStateChange.connect( + this, &HangoutPubSubClient::OnAudioMuteStateChange); + audio_mute_state_client_->SignalPublishResult.connect( + this, &HangoutPubSubClient::OnAudioMutePublishResult); + audio_mute_state_client_->SignalPublishError.connect( + this, &HangoutPubSubClient::OnAudioMutePublishError); + + video_mute_state_client_.reset(new PubSubStateClient( + nick_, media_client_.get(), QN_GOOGLE_MUC_VIDEO_MUTE, false, + new PublishedNickKeySerializer(), new BoolStateSerializer())); + // Can't just repeat because we need to watch for remote mutes. + video_mute_state_client_->SignalStateChange.connect( + this, &HangoutPubSubClient::OnVideoMuteStateChange); + video_mute_state_client_->SignalPublishResult.connect( + this, &HangoutPubSubClient::OnVideoMutePublishResult); + video_mute_state_client_->SignalPublishError.connect( + this, &HangoutPubSubClient::OnVideoMutePublishError); + + video_pause_state_client_.reset(new PubSubStateClient( + nick_, media_client_.get(), QN_GOOGLE_MUC_VIDEO_PAUSE, false, + new PublishedNickKeySerializer(), new BoolStateSerializer())); + video_pause_state_client_->SignalStateChange.connect( + this, &HangoutPubSubClient::OnVideoPauseStateChange); + video_pause_state_client_->SignalPublishResult.connect( + this, &HangoutPubSubClient::OnVideoPausePublishResult); + video_pause_state_client_->SignalPublishError.connect( + this, &HangoutPubSubClient::OnVideoPausePublishError); + + recording_state_client_.reset(new PubSubStateClient( + nick_, media_client_.get(), QN_GOOGLE_MUC_RECORDING, false, + new PublishedNickKeySerializer(), new BoolStateSerializer())); + recording_state_client_->SignalStateChange.connect( + this, &HangoutPubSubClient::OnRecordingStateChange); + recording_state_client_->SignalPublishResult.connect( + this, &HangoutPubSubClient::OnRecordingPublishResult); + recording_state_client_->SignalPublishError.connect( + this, &HangoutPubSubClient::OnRecordingPublishError); + + media_block_state_client_.reset(new PubSubStateClient( + nick_, media_client_.get(), QN_GOOGLE_MUC_MEDIA_BLOCK, false, + new PublisherAndPublishedNicksKeySerializer(), + new BoolStateSerializer())); + media_block_state_client_->SignalStateChange.connect( + this, &HangoutPubSubClient::OnMediaBlockStateChange); + media_block_state_client_->SignalPublishResult.connect( + this, &HangoutPubSubClient::OnMediaBlockPublishResult); + media_block_state_client_->SignalPublishError.connect( + this, &HangoutPubSubClient::OnMediaBlockPublishError); +} + +HangoutPubSubClient::~HangoutPubSubClient() { +} + +void HangoutPubSubClient::RequestAll() { + presenter_client_->RequestItems(); + media_client_->RequestItems(); +} + +void HangoutPubSubClient::OnPresenterRequestError( + PubSubClient* client, const XmlElement* stanza) { + SignalRequestError(client->node(), stanza); +} + +void HangoutPubSubClient::OnMediaRequestError( + PubSubClient* client, const XmlElement* stanza) { + SignalRequestError(client->node(), stanza); +} + +void HangoutPubSubClient::PublishPresenterState( + bool presenting, std::string* task_id_out) { + presenter_state_client_->Publish(nick_, presenting, task_id_out); +} + +void HangoutPubSubClient::PublishAudioMuteState( + bool muted, std::string* task_id_out) { + audio_mute_state_client_->Publish(nick_, muted, task_id_out); +} + +void HangoutPubSubClient::PublishVideoMuteState( + bool muted, std::string* task_id_out) { + video_mute_state_client_->Publish(nick_, muted, task_id_out); +} + +void HangoutPubSubClient::PublishVideoPauseState( + bool paused, std::string* task_id_out) { + video_pause_state_client_->Publish(nick_, paused, task_id_out); +} + +void HangoutPubSubClient::PublishRecordingState( + bool recording, std::string* task_id_out) { + recording_state_client_->Publish(nick_, recording, task_id_out); +} + +// Remote mute is accomplished by setting another client's mute state. +void HangoutPubSubClient::RemoteMute( + const std::string& mutee_nick, std::string* task_id_out) { + audio_mute_state_client_->Publish(mutee_nick, true, task_id_out); +} + +// Block media is accomplished by setting another client's block +// state, kind of like remote mute. +void HangoutPubSubClient::BlockMedia( + const std::string& blockee_nick, std::string* task_id_out) { + media_block_state_client_->Publish(blockee_nick, true, task_id_out); +} + +void HangoutPubSubClient::OnPresenterStateChange( + const PubSubStateChange& change) { + SignalPresenterStateChange( + change.published_nick, change.old_state, change.new_state); +} + +void HangoutPubSubClient::OnPresenterPublishResult( + const std::string& task_id, const XmlElement* item) { + SignalPublishPresenterResult(task_id); +} + +void HangoutPubSubClient::OnPresenterPublishError( + const std::string& task_id, const XmlElement* item, + const XmlElement* stanza) { + SignalPublishPresenterError(task_id, stanza); +} + +// Since a remote mute is accomplished by another client setting our +// mute state, if our state changes to muted, we should mute ourselves. +// Note that remote un-muting is disallowed by the RoomServer. +void HangoutPubSubClient::OnAudioMuteStateChange( + const PubSubStateChange& change) { + bool was_muted = change.old_state; + bool is_muted = change.new_state; + bool remote_action = (!change.publisher_nick.empty() && + (change.publisher_nick != change.published_nick)); + + if (remote_action) { + const std::string& mutee_nick = change.published_nick; + const std::string& muter_nick = change.publisher_nick; + if (!is_muted) { + // The server should prevent remote un-mute. + LOG(LS_WARNING) << muter_nick << " remote unmuted " << mutee_nick; + return; + } + bool should_mute_locally = (mutee_nick == nick_); + SignalRemoteMute(mutee_nick, muter_nick, should_mute_locally); + } + SignalAudioMuteStateChange(change.published_nick, was_muted, is_muted); +} + +const std::string GetAudioMuteNickFromItem(const XmlElement* item) { + if (item != NULL) { + const XmlElement* audio_mute_state = + item->FirstNamed(QN_GOOGLE_MUC_AUDIO_MUTE); + if (audio_mute_state != NULL) { + return audio_mute_state->Attr(QN_NICK); + } + } + return std::string(); +} + +const std::string GetBlockeeNickFromItem(const XmlElement* item) { + if (item != NULL) { + const XmlElement* media_block_state = + item->FirstNamed(QN_GOOGLE_MUC_MEDIA_BLOCK); + if (media_block_state != NULL) { + return media_block_state->Attr(QN_NICK); + } + } + return std::string(); +} + +void HangoutPubSubClient::OnAudioMutePublishResult( + const std::string& task_id, const XmlElement* item) { + const std::string& mutee_nick = GetAudioMuteNickFromItem(item); + if (mutee_nick != nick_) { + SignalRemoteMuteResult(task_id, mutee_nick); + } else { + SignalPublishAudioMuteResult(task_id); + } +} + +void HangoutPubSubClient::OnAudioMutePublishError( + const std::string& task_id, const XmlElement* item, + const XmlElement* stanza) { + const std::string& mutee_nick = GetAudioMuteNickFromItem(item); + if (mutee_nick != nick_) { + SignalRemoteMuteError(task_id, mutee_nick, stanza); + } else { + SignalPublishAudioMuteError(task_id, stanza); + } +} + +void HangoutPubSubClient::OnVideoMuteStateChange( + const PubSubStateChange& change) { + SignalVideoMuteStateChange( + change.published_nick, change.old_state, change.new_state); +} + +void HangoutPubSubClient::OnVideoMutePublishResult( + const std::string& task_id, const XmlElement* item) { + SignalPublishVideoMuteResult(task_id); +} + +void HangoutPubSubClient::OnVideoMutePublishError( + const std::string& task_id, const XmlElement* item, + const XmlElement* stanza) { + SignalPublishVideoMuteError(task_id, stanza); +} + +void HangoutPubSubClient::OnVideoPauseStateChange( + const PubSubStateChange& change) { + SignalVideoPauseStateChange( + change.published_nick, change.old_state, change.new_state); +} + +void HangoutPubSubClient::OnVideoPausePublishResult( + const std::string& task_id, const XmlElement* item) { + SignalPublishVideoPauseResult(task_id); +} + +void HangoutPubSubClient::OnVideoPausePublishError( + const std::string& task_id, const XmlElement* item, + const XmlElement* stanza) { + SignalPublishVideoPauseError(task_id, stanza); +} + +void HangoutPubSubClient::OnRecordingStateChange( + const PubSubStateChange& change) { + SignalRecordingStateChange( + change.published_nick, change.old_state, change.new_state); +} + +void HangoutPubSubClient::OnRecordingPublishResult( + const std::string& task_id, const XmlElement* item) { + SignalPublishRecordingResult(task_id); +} + +void HangoutPubSubClient::OnRecordingPublishError( + const std::string& task_id, const XmlElement* item, + const XmlElement* stanza) { + SignalPublishRecordingError(task_id, stanza); +} + +void HangoutPubSubClient::OnMediaBlockStateChange( + const PubSubStateChange& change) { + const std::string& blockee_nick = change.published_nick; + const std::string& blocker_nick = change.publisher_nick; + + bool was_blockee = change.old_state; + bool is_blockee = change.new_state; + if (!was_blockee && is_blockee) { + SignalMediaBlock(blockee_nick, blocker_nick); + } + // TODO: Should we bother signaling unblock? Currently + // it isn't allowed, but it might happen when a participant leaves + // the room and the item is retracted. +} + +void HangoutPubSubClient::OnMediaBlockPublishResult( + const std::string& task_id, const XmlElement* item) { + const std::string& blockee_nick = GetBlockeeNickFromItem(item); + SignalMediaBlockResult(task_id, blockee_nick); +} + +void HangoutPubSubClient::OnMediaBlockPublishError( + const std::string& task_id, const XmlElement* item, + const XmlElement* stanza) { + const std::string& blockee_nick = GetBlockeeNickFromItem(item); + SignalMediaBlockError(task_id, blockee_nick, stanza); +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/hangoutpubsubclient.h b/webrtc/libjingle/xmpp/hangoutpubsubclient.h new file mode 100644 index 000000000..2586768e2 --- /dev/null +++ b/webrtc/libjingle/xmpp/hangoutpubsubclient.h @@ -0,0 +1,178 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_HANGOUTPUBSUBCLIENT_H_ +#define WEBRTC_LIBJINGLE_XMPP_HANGOUTPUBSUBCLIENT_H_ + +#include +#include +#include + +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/pubsubclient.h" +#include "webrtc/libjingle/xmpp/pubsubstateclient.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/sigslotrepeater.h" + +// Gives a high-level API for MUC call PubSub needs such as +// presenter state, recording state, mute state, and remote mute. + +namespace buzz { + +class Jid; +class XmlElement; +class XmppTaskParentInterface; + +// A client tied to a specific MUC jid and local nick. Provides ways +// to get updates and publish state and events. Must call +// RequestAll() to start getting updates. +class HangoutPubSubClient : public sigslot::has_slots<> { + public: + HangoutPubSubClient(XmppTaskParentInterface* parent, + const Jid& mucjid, + const std::string& nick); + ~HangoutPubSubClient(); + const Jid& mucjid() const { return mucjid_; } + const std::string& nick() const { return nick_; } + + // Requests all of the different states and subscribes for updates. + // Responses and updates will be signalled via the various signals. + void RequestAll(); + // Signal (nick, was_presenting, is_presenting) + sigslot::signal3 SignalPresenterStateChange; + // Signal (nick, was_muted, is_muted) + sigslot::signal3 SignalAudioMuteStateChange; + // Signal (nick, was_muted, is_muted) + sigslot::signal3 SignalVideoMuteStateChange; + // Signal (nick, was_paused, is_paused) + sigslot::signal3 SignalVideoPauseStateChange; + // Signal (nick, was_recording, is_recording) + sigslot::signal3 SignalRecordingStateChange; + // Signal (mutee_nick, muter_nick, should_mute_locally) + sigslot::signal3 SignalRemoteMute; + // Signal (blockee_nick, blocker_nick) + sigslot::signal2 SignalMediaBlock; + + // Signal (node, error stanza) + sigslot::signal2 SignalRequestError; + + // On each of these, provide a task_id_out to get the task_id, which + // can be correlated to the error and result signals. + void PublishPresenterState( + bool presenting, std::string* task_id_out = NULL); + void PublishAudioMuteState( + bool muted, std::string* task_id_out = NULL); + void PublishVideoMuteState( + bool muted, std::string* task_id_out = NULL); + void PublishVideoPauseState( + bool paused, std::string* task_id_out = NULL); + void PublishRecordingState( + bool recording, std::string* task_id_out = NULL); + void RemoteMute( + const std::string& mutee_nick, std::string* task_id_out = NULL); + void BlockMedia( + const std::string& blockee_nick, std::string* task_id_out = NULL); + + // Signal task_id + sigslot::signal1 SignalPublishAudioMuteResult; + sigslot::signal1 SignalPublishVideoMuteResult; + sigslot::signal1 SignalPublishVideoPauseResult; + sigslot::signal1 SignalPublishPresenterResult; + sigslot::signal1 SignalPublishRecordingResult; + // Signal (task_id, mutee_nick) + sigslot::signal2 SignalRemoteMuteResult; + // Signal (task_id, blockee_nick) + sigslot::signal2 SignalMediaBlockResult; + + // Signal (task_id, error stanza) + sigslot::signal2 SignalPublishAudioMuteError; + sigslot::signal2 SignalPublishVideoMuteError; + sigslot::signal2 SignalPublishVideoPauseError; + sigslot::signal2 SignalPublishPresenterError; + sigslot::signal2 SignalPublishRecordingError; + sigslot::signal2 SignalPublishMediaBlockError; + // Signal (task_id, mutee_nick, error stanza) + sigslot::signal3 SignalRemoteMuteError; + // Signal (task_id, blockee_nick, error stanza) + sigslot::signal3 SignalMediaBlockError; + + + private: + void OnPresenterRequestError(PubSubClient* client, + const XmlElement* stanza); + void OnMediaRequestError(PubSubClient* client, + const XmlElement* stanza); + + void OnPresenterStateChange(const PubSubStateChange& change); + void OnPresenterPublishResult(const std::string& task_id, + const XmlElement* item); + void OnPresenterPublishError(const std::string& task_id, + const XmlElement* item, + const XmlElement* stanza); + void OnAudioMuteStateChange(const PubSubStateChange& change); + void OnAudioMutePublishResult(const std::string& task_id, + const XmlElement* item); + void OnAudioMutePublishError(const std::string& task_id, + const XmlElement* item, + const XmlElement* stanza); + void OnVideoMuteStateChange(const PubSubStateChange& change); + void OnVideoMutePublishResult(const std::string& task_id, + const XmlElement* item); + void OnVideoMutePublishError(const std::string& task_id, + const XmlElement* item, + const XmlElement* stanza); + void OnVideoPauseStateChange(const PubSubStateChange& change); + void OnVideoPausePublishResult(const std::string& task_id, + const XmlElement* item); + void OnVideoPausePublishError(const std::string& task_id, + const XmlElement* item, + const XmlElement* stanza); + void OnRecordingStateChange(const PubSubStateChange& change); + void OnRecordingPublishResult(const std::string& task_id, + const XmlElement* item); + void OnRecordingPublishError(const std::string& task_id, + const XmlElement* item, + const XmlElement* stanza); + void OnMediaBlockStateChange(const PubSubStateChange& change); + void OnMediaBlockPublishResult(const std::string& task_id, + const XmlElement* item); + void OnMediaBlockPublishError(const std::string& task_id, + const XmlElement* item, + const XmlElement* stanza); + Jid mucjid_; + std::string nick_; + rtc::scoped_ptr media_client_; + rtc::scoped_ptr presenter_client_; + rtc::scoped_ptr > presenter_state_client_; + rtc::scoped_ptr > audio_mute_state_client_; + rtc::scoped_ptr > video_mute_state_client_; + rtc::scoped_ptr > video_pause_state_client_; + rtc::scoped_ptr > recording_state_client_; + rtc::scoped_ptr > media_block_state_client_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_HANGOUTPUBSUBCLIENT_H_ diff --git a/webrtc/libjingle/xmpp/hangoutpubsubclient_unittest.cc b/webrtc/libjingle/xmpp/hangoutpubsubclient_unittest.cc new file mode 100644 index 000000000..7c6ea58f2 --- /dev/null +++ b/webrtc/libjingle/xmpp/hangoutpubsubclient_unittest.cc @@ -0,0 +1,753 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/hangoutpubsubclient.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/base/faketaskrunner.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/sigslot.h" + +class TestHangoutPubSubListener : public sigslot::has_slots<> { + public: + TestHangoutPubSubListener() : + request_error_count(0), + publish_audio_mute_error_count(0), + publish_video_mute_error_count(0), + publish_video_pause_error_count(0), + publish_presenter_error_count(0), + publish_recording_error_count(0), + remote_mute_error_count(0) { + } + + void OnPresenterStateChange( + const std::string& nick, bool was_presenting, bool is_presenting) { + last_presenter_nick = nick; + last_was_presenting = was_presenting; + last_is_presenting = is_presenting; + } + + void OnAudioMuteStateChange( + const std::string& nick, bool was_muted, bool is_muted) { + last_audio_muted_nick = nick; + last_was_audio_muted = was_muted; + last_is_audio_muted = is_muted; + } + + void OnVideoMuteStateChange( + const std::string& nick, bool was_muted, bool is_muted) { + last_video_muted_nick = nick; + last_was_video_muted = was_muted; + last_is_video_muted = is_muted; + } + + void OnVideoPauseStateChange( + const std::string& nick, bool was_paused, bool is_paused) { + last_video_paused_nick = nick; + last_was_video_paused = was_paused; + last_is_video_paused = is_paused; + } + + void OnRecordingStateChange( + const std::string& nick, bool was_recording, bool is_recording) { + last_recording_nick = nick; + last_was_recording = was_recording; + last_is_recording = is_recording; + } + + void OnRemoteMute( + const std::string& mutee_nick, + const std::string& muter_nick, + bool should_mute_locally) { + last_mutee_nick = mutee_nick; + last_muter_nick = muter_nick; + last_should_mute = should_mute_locally; + } + + void OnMediaBlock( + const std::string& blockee_nick, + const std::string& blocker_nick) { + last_blockee_nick = blockee_nick; + last_blocker_nick = blocker_nick; + } + + void OnRequestError(const std::string& node, const buzz::XmlElement* stanza) { + ++request_error_count; + request_error_node = node; + } + + void OnPublishAudioMuteError(const std::string& task_id, + const buzz::XmlElement* stanza) { + ++publish_audio_mute_error_count; + error_task_id = task_id; + } + + void OnPublishVideoMuteError(const std::string& task_id, + const buzz::XmlElement* stanza) { + ++publish_video_mute_error_count; + error_task_id = task_id; + } + + void OnPublishVideoPauseError(const std::string& task_id, + const buzz::XmlElement* stanza) { + ++publish_video_pause_error_count; + error_task_id = task_id; + } + + void OnPublishPresenterError(const std::string& task_id, + const buzz::XmlElement* stanza) { + ++publish_presenter_error_count; + error_task_id = task_id; + } + + void OnPublishRecordingError(const std::string& task_id, + const buzz::XmlElement* stanza) { + ++publish_recording_error_count; + error_task_id = task_id; + } + + void OnRemoteMuteResult(const std::string& task_id, + const std::string& mutee_nick) { + result_task_id = task_id; + remote_mute_mutee_nick = mutee_nick; + } + + void OnRemoteMuteError(const std::string& task_id, + const std::string& mutee_nick, + const buzz::XmlElement* stanza) { + ++remote_mute_error_count; + error_task_id = task_id; + remote_mute_mutee_nick = mutee_nick; + } + + void OnMediaBlockResult(const std::string& task_id, + const std::string& blockee_nick) { + result_task_id = task_id; + media_blockee_nick = blockee_nick; + } + + void OnMediaBlockError(const std::string& task_id, + const std::string& blockee_nick, + const buzz::XmlElement* stanza) { + ++media_block_error_count; + error_task_id = task_id; + media_blockee_nick = blockee_nick; + } + + std::string last_presenter_nick; + bool last_is_presenting; + bool last_was_presenting; + std::string last_audio_muted_nick; + bool last_is_audio_muted; + bool last_was_audio_muted; + std::string last_video_muted_nick; + bool last_is_video_muted; + bool last_was_video_muted; + std::string last_video_paused_nick; + bool last_is_video_paused; + bool last_was_video_paused; + std::string last_recording_nick; + bool last_is_recording; + bool last_was_recording; + std::string last_mutee_nick; + std::string last_muter_nick; + bool last_should_mute; + std::string last_blockee_nick; + std::string last_blocker_nick; + + int request_error_count; + std::string request_error_node; + int publish_audio_mute_error_count; + int publish_video_mute_error_count; + int publish_video_pause_error_count; + int publish_presenter_error_count; + int publish_recording_error_count; + int remote_mute_error_count; + std::string result_task_id; + std::string error_task_id; + std::string remote_mute_mutee_nick; + int media_block_error_count; + std::string media_blockee_nick; +}; + +class HangoutPubSubClientTest : public testing::Test { + public: + HangoutPubSubClientTest() : + pubsubjid("room@domain.com"), + nick("me") { + + runner.reset(new rtc::FakeTaskRunner()); + xmpp_client = new buzz::FakeXmppClient(runner.get()); + client.reset(new buzz::HangoutPubSubClient(xmpp_client, pubsubjid, nick)); + listener.reset(new TestHangoutPubSubListener()); + client->SignalPresenterStateChange.connect( + listener.get(), &TestHangoutPubSubListener::OnPresenterStateChange); + client->SignalAudioMuteStateChange.connect( + listener.get(), &TestHangoutPubSubListener::OnAudioMuteStateChange); + client->SignalVideoMuteStateChange.connect( + listener.get(), &TestHangoutPubSubListener::OnVideoMuteStateChange); + client->SignalVideoPauseStateChange.connect( + listener.get(), &TestHangoutPubSubListener::OnVideoPauseStateChange); + client->SignalRecordingStateChange.connect( + listener.get(), &TestHangoutPubSubListener::OnRecordingStateChange); + client->SignalRemoteMute.connect( + listener.get(), &TestHangoutPubSubListener::OnRemoteMute); + client->SignalMediaBlock.connect( + listener.get(), &TestHangoutPubSubListener::OnMediaBlock); + client->SignalRequestError.connect( + listener.get(), &TestHangoutPubSubListener::OnRequestError); + client->SignalPublishAudioMuteError.connect( + listener.get(), &TestHangoutPubSubListener::OnPublishAudioMuteError); + client->SignalPublishVideoMuteError.connect( + listener.get(), &TestHangoutPubSubListener::OnPublishVideoMuteError); + client->SignalPublishVideoPauseError.connect( + listener.get(), &TestHangoutPubSubListener::OnPublishVideoPauseError); + client->SignalPublishPresenterError.connect( + listener.get(), &TestHangoutPubSubListener::OnPublishPresenterError); + client->SignalPublishRecordingError.connect( + listener.get(), &TestHangoutPubSubListener::OnPublishRecordingError); + client->SignalRemoteMuteResult.connect( + listener.get(), &TestHangoutPubSubListener::OnRemoteMuteResult); + client->SignalRemoteMuteError.connect( + listener.get(), &TestHangoutPubSubListener::OnRemoteMuteError); + client->SignalMediaBlockResult.connect( + listener.get(), &TestHangoutPubSubListener::OnMediaBlockResult); + client->SignalMediaBlockError.connect( + listener.get(), &TestHangoutPubSubListener::OnMediaBlockError); + } + + rtc::scoped_ptr runner; + // xmpp_client deleted by deleting runner. + buzz::FakeXmppClient* xmpp_client; + rtc::scoped_ptr client; + rtc::scoped_ptr listener; + buzz::Jid pubsubjid; + std::string nick; +}; + +TEST_F(HangoutPubSubClientTest, TestRequest) { + ASSERT_EQ(0U, xmpp_client->sent_stanzas().size()); + + client->RequestAll(); + std::string expected_presenter_request = + "" + "" + "" + "" + ""; + + std::string expected_media_request = + "" + "" + "" + "" + ""; + + ASSERT_EQ(2U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_presenter_request, xmpp_client->sent_stanzas()[0]->Str()); + EXPECT_EQ(expected_media_request, xmpp_client->sent_stanzas()[1]->Str()); + + std::string presenter_response = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + // Some clients are "bad" in that they'll jam multiple states in + // all at once. We have to deal with it. + " " + " " + " " + " " + " " + " " + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(presenter_response)); + EXPECT_EQ("presenting-nick", listener->last_presenter_nick); + EXPECT_FALSE(listener->last_was_presenting); + EXPECT_TRUE(listener->last_is_presenting); + + std::string media_response = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(media_response)); + EXPECT_EQ("muted-nick", listener->last_audio_muted_nick); + EXPECT_FALSE(listener->last_was_audio_muted); + EXPECT_TRUE(listener->last_is_audio_muted); + + EXPECT_EQ("video-muted-nick", listener->last_video_muted_nick); + EXPECT_FALSE(listener->last_was_video_muted); + EXPECT_TRUE(listener->last_is_video_muted); + + EXPECT_EQ("video-paused-nick", listener->last_video_paused_nick); + EXPECT_FALSE(listener->last_was_video_paused); + EXPECT_TRUE(listener->last_is_video_paused); + + EXPECT_EQ("recording-nick", listener->last_recording_nick); + EXPECT_FALSE(listener->last_was_recording); + EXPECT_TRUE(listener->last_is_recording); + + std::string incoming_presenter_resets_message = + "" + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + xmpp_client->HandleStanza( + buzz::XmlElement::ForStr(incoming_presenter_resets_message)); + EXPECT_EQ("presenting-nick", listener->last_presenter_nick); + //EXPECT_TRUE(listener->last_was_presenting); + EXPECT_FALSE(listener->last_is_presenting); + + std::string incoming_presenter_retracts_message = + "" + " " + " " + " " + " " + " " + ""; + + xmpp_client->HandleStanza( + buzz::XmlElement::ForStr(incoming_presenter_retracts_message)); + EXPECT_EQ("presenting-nick2", listener->last_presenter_nick); + EXPECT_TRUE(listener->last_was_presenting); + EXPECT_FALSE(listener->last_is_presenting); + + std::string incoming_media_retracts_message = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + xmpp_client->HandleStanza( + buzz::XmlElement::ForStr(incoming_media_retracts_message)); + EXPECT_EQ("muted-nick", listener->last_audio_muted_nick); + EXPECT_TRUE(listener->last_was_audio_muted); + EXPECT_FALSE(listener->last_is_audio_muted); + + EXPECT_EQ("video-paused-nick", listener->last_video_paused_nick); + EXPECT_TRUE(listener->last_was_video_paused); + EXPECT_FALSE(listener->last_is_video_paused); + + EXPECT_EQ("recording-nick", listener->last_recording_nick); + EXPECT_TRUE(listener->last_was_recording); + EXPECT_FALSE(listener->last_is_recording); + + std::string incoming_presenter_changes_message = + "" + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + xmpp_client->HandleStanza( + buzz::XmlElement::ForStr(incoming_presenter_changes_message)); + EXPECT_EQ("presenting-nick2", listener->last_presenter_nick); + EXPECT_FALSE(listener->last_was_presenting); + EXPECT_TRUE(listener->last_is_presenting); + + xmpp_client->HandleStanza( + buzz::XmlElement::ForStr(incoming_presenter_changes_message)); + EXPECT_EQ("presenting-nick2", listener->last_presenter_nick); + EXPECT_TRUE(listener->last_was_presenting); + EXPECT_TRUE(listener->last_is_presenting); + + std::string incoming_media_changes_message = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + xmpp_client->HandleStanza( + buzz::XmlElement::ForStr(incoming_media_changes_message)); + EXPECT_EQ("muted-nick2", listener->last_audio_muted_nick); + EXPECT_FALSE(listener->last_was_audio_muted); + EXPECT_TRUE(listener->last_is_audio_muted); + + EXPECT_EQ("video-paused-nick2", listener->last_video_paused_nick); + EXPECT_FALSE(listener->last_was_video_paused); + EXPECT_TRUE(listener->last_is_video_paused); + + EXPECT_EQ("recording-nick2", listener->last_recording_nick); + EXPECT_FALSE(listener->last_was_recording); + EXPECT_TRUE(listener->last_is_recording); + + std::string incoming_remote_mute_message = + "" + " " + " " + " " + " " + " " + " " + " " + ""; + + listener->last_is_audio_muted = false; + xmpp_client->HandleStanza( + buzz::XmlElement::ForStr(incoming_remote_mute_message)); + EXPECT_EQ("mutee", listener->last_mutee_nick); + EXPECT_EQ("muter", listener->last_muter_nick); + EXPECT_FALSE(listener->last_should_mute); + EXPECT_EQ("mutee", listener->last_audio_muted_nick); + EXPECT_TRUE(listener->last_is_audio_muted); + + std::string incoming_remote_mute_me_message = + "" + " " + " " + " " + " " + " " + " " + " " + ""; + + listener->last_is_audio_muted = false; + xmpp_client->HandleStanza( + buzz::XmlElement::ForStr(incoming_remote_mute_me_message)); + EXPECT_EQ("me", listener->last_mutee_nick); + EXPECT_EQ("muter", listener->last_muter_nick); + EXPECT_TRUE(listener->last_should_mute); + EXPECT_EQ("me", listener->last_audio_muted_nick); + EXPECT_TRUE(listener->last_is_audio_muted); + + std::string incoming_media_block_message = + "" + " " + " " + " " + " " + " " + " " + " " + ""; + + xmpp_client->HandleStanza( + buzz::XmlElement::ForStr(incoming_media_block_message)); + EXPECT_EQ("blockee", listener->last_blockee_nick); + EXPECT_EQ("blocker", listener->last_blocker_nick); +} + +TEST_F(HangoutPubSubClientTest, TestRequestError) { + client->RequestAll(); + std::string result_iq = + "" + " " + " " + " " + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(1, listener->request_error_count); + EXPECT_EQ("google:presenter", listener->request_error_node); +} + +TEST_F(HangoutPubSubClientTest, TestPublish) { + client->PublishPresenterState(true); + std::string expected_presenter_iq = + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(1U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_presenter_iq, + xmpp_client->sent_stanzas()[0]->Str()); + + client->PublishAudioMuteState(true); + std::string expected_audio_mute_iq = + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(2U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_audio_mute_iq, xmpp_client->sent_stanzas()[1]->Str()); + + client->PublishVideoPauseState(true); + std::string expected_video_pause_iq = + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(3U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_video_pause_iq, xmpp_client->sent_stanzas()[2]->Str()); + + client->PublishRecordingState(true); + std::string expected_recording_iq = + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(4U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_recording_iq, xmpp_client->sent_stanzas()[3]->Str()); + + client->RemoteMute("mutee"); + std::string expected_remote_mute_iq = + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(5U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_remote_mute_iq, xmpp_client->sent_stanzas()[4]->Str()); + + client->PublishPresenterState(false); + std::string expected_presenter_retract_iq = + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(6U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_presenter_retract_iq, + xmpp_client->sent_stanzas()[5]->Str()); + + client->PublishAudioMuteState(false); + std::string expected_audio_mute_retract_iq = + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(7U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_audio_mute_retract_iq, + xmpp_client->sent_stanzas()[6]->Str()); + + client->PublishVideoPauseState(false); + std::string expected_video_pause_retract_iq = + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(8U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_video_pause_retract_iq, + xmpp_client->sent_stanzas()[7]->Str()); + + client->BlockMedia("blockee"); + std::string expected_media_block_iq = + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(9U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_media_block_iq, xmpp_client->sent_stanzas()[8]->Str()); +} + +TEST_F(HangoutPubSubClientTest, TestPublishPresenterError) { + std::string result_iq = + ""; + + client->PublishPresenterState(true); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(1, listener->publish_presenter_error_count); + EXPECT_EQ("0", listener->error_task_id); +} + + +TEST_F(HangoutPubSubClientTest, TestPublishAudioMuteError) { + std::string result_iq = + ""; + + client->PublishAudioMuteState(true); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(1, listener->publish_audio_mute_error_count); + EXPECT_EQ("0", listener->error_task_id); +} + +TEST_F(HangoutPubSubClientTest, TestPublishVideoPauseError) { + std::string result_iq = + ""; + + client->PublishVideoPauseState(true); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(1, listener->publish_video_pause_error_count); + EXPECT_EQ("0", listener->error_task_id); +} + +TEST_F(HangoutPubSubClientTest, TestPublishRecordingError) { + std::string result_iq = + ""; + + client->PublishRecordingState(true); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(1, listener->publish_recording_error_count); + EXPECT_EQ("0", listener->error_task_id); +} + +TEST_F(HangoutPubSubClientTest, TestPublishRemoteMuteResult) { + std::string result_iq = + ""; + + client->RemoteMute("joe"); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ("joe", listener->remote_mute_mutee_nick); + EXPECT_EQ("0", listener->result_task_id); +} + +TEST_F(HangoutPubSubClientTest, TestRemoteMuteError) { + std::string result_iq = + ""; + + client->RemoteMute("joe"); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(1, listener->remote_mute_error_count); + EXPECT_EQ("joe", listener->remote_mute_mutee_nick); + EXPECT_EQ("0", listener->error_task_id); +} + +TEST_F(HangoutPubSubClientTest, TestPublishMediaBlockResult) { + std::string result_iq = + ""; + + client->BlockMedia("joe"); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ("joe", listener->media_blockee_nick); + EXPECT_EQ("0", listener->result_task_id); +} + +TEST_F(HangoutPubSubClientTest, TestMediaBlockError) { + std::string result_iq = + ""; + + client->BlockMedia("joe"); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(1, listener->remote_mute_error_count); + EXPECT_EQ("joe", listener->media_blockee_nick); + EXPECT_EQ("0", listener->error_task_id); +} diff --git a/webrtc/libjingle/xmpp/iqtask.cc b/webrtc/libjingle/xmpp/iqtask.cc new file mode 100644 index 000000000..a7a41ee51 --- /dev/null +++ b/webrtc/libjingle/xmpp/iqtask.cc @@ -0,0 +1,69 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/iqtask.h" + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/xmppclient.h" + +namespace buzz { + +static const int kDefaultIqTimeoutSecs = 15; + +IqTask::IqTask(XmppTaskParentInterface* parent, + const std::string& verb, + const buzz::Jid& to, + buzz::XmlElement* el) + : buzz::XmppTask(parent, buzz::XmppEngine::HL_SINGLE), + to_(to), + stanza_(MakeIq(verb, to_, task_id())) { + stanza_->AddElement(el); + set_timeout_seconds(kDefaultIqTimeoutSecs); +} + +int IqTask::ProcessStart() { + buzz::XmppReturnStatus ret = SendStanza(stanza_.get()); + // TODO: HandleError(NULL) if SendStanza fails? + return (ret == buzz::XMPP_RETURN_OK) ? STATE_RESPONSE : STATE_ERROR; +} + +bool IqTask::HandleStanza(const buzz::XmlElement* stanza) { + if (!MatchResponseIq(stanza, to_, task_id())) + return false; + + if (stanza->Attr(buzz::QN_TYPE) != buzz::STR_RESULT && + stanza->Attr(buzz::QN_TYPE) != buzz::STR_ERROR) { + return false; + } + + QueueStanza(stanza); + return true; +} + +int IqTask::ProcessResponse() { + const buzz::XmlElement* stanza = NextStanza(); + if (stanza == NULL) + return STATE_BLOCKED; + + bool success = (stanza->Attr(buzz::QN_TYPE) == buzz::STR_RESULT); + if (success) { + HandleResult(stanza); + } else { + SignalError(this, stanza->FirstNamed(QN_ERROR)); + } + return STATE_DONE; +} + +int IqTask::OnTimeout() { + SignalError(this, NULL); + return XmppTask::OnTimeout(); +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/iqtask.h b/webrtc/libjingle/xmpp/iqtask.h new file mode 100644 index 000000000..1d50c3839 --- /dev/null +++ b/webrtc/libjingle/xmpp/iqtask.h @@ -0,0 +1,48 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_IQTASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_IQTASK_H_ + +#include + +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" + +namespace buzz { + +class IqTask : public XmppTask { + public: + IqTask(XmppTaskParentInterface* parent, + const std::string& verb, const Jid& to, + XmlElement* el); + virtual ~IqTask() {} + + const XmlElement* stanza() const { return stanza_.get(); } + + sigslot::signal2 SignalError; + + protected: + virtual void HandleResult(const XmlElement* element) = 0; + + private: + virtual int ProcessStart(); + virtual bool HandleStanza(const XmlElement* stanza); + virtual int ProcessResponse(); + virtual int OnTimeout(); + + Jid to_; + rtc::scoped_ptr stanza_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_IQTASK_H_ diff --git a/webrtc/libjingle/xmpp/jid.cc b/webrtc/libjingle/xmpp/jid.cc new file mode 100644 index 000000000..ad0538056 --- /dev/null +++ b/webrtc/libjingle/xmpp/jid.cc @@ -0,0 +1,379 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/jid.h" + +#include + +#include +#include + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" + +namespace buzz { + +Jid::Jid() { +} + +Jid::Jid(const std::string& jid_string) { + if (jid_string.empty()) + return; + + // First find the slash and slice off that part + size_t slash = jid_string.find('/'); + resource_name_ = (slash == std::string::npos ? STR_EMPTY : + jid_string.substr(slash + 1)); + + // Now look for the node + 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); + domain_name_ = jid_string.substr(domain_begin, domain_length); + + ValidateOrReset(); +} + +Jid::Jid(const std::string& node_name, + const std::string& domain_name, + const std::string& resource_name) + : node_name_(node_name), + domain_name_(domain_name), + resource_name_(resource_name) { + ValidateOrReset(); +} + +void Jid::ValidateOrReset() { + bool valid_node; + bool valid_domain; + bool valid_resource; + + node_name_ = PrepNode(node_name_, &valid_node); + domain_name_ = PrepDomain(domain_name_, &valid_domain); + resource_name_ = PrepResource(resource_name_, &valid_resource); + + if (!valid_node || !valid_domain || !valid_resource) { + node_name_.clear(); + domain_name_.clear(); + resource_name_.clear(); + } +} + +std::string Jid::Str() const { + if (!IsValid()) + return STR_EMPTY; + + std::string ret; + + if (!node_name_.empty()) + ret = node_name_ + "@"; + + ASSERT(domain_name_ != STR_EMPTY); + ret += domain_name_; + + if (!resource_name_.empty()) + ret += "/" + resource_name_; + + return ret; +} + +Jid::~Jid() { +} + +bool Jid::IsEmpty() const { + return (node_name_.empty() && domain_name_.empty() && + resource_name_.empty()); +} + +bool Jid::IsValid() const { + return !domain_name_.empty(); +} + +bool Jid::IsBare() const { + if (IsEmpty()) { + LOG(LS_VERBOSE) << "Warning: Calling IsBare() on the empty jid."; + return true; + } + return IsValid() && resource_name_.empty(); +} + +bool Jid::IsFull() const { + return IsValid() && !resource_name_.empty(); +} + +Jid Jid::BareJid() const { + if (!IsValid()) + return Jid(); + if (!IsFull()) + return *this; + return Jid(node_name_, domain_name_, STR_EMPTY); +} + +bool Jid::BareEquals(const Jid& other) const { + return other.node_name_ == node_name_ && + other.domain_name_ == domain_name_; +} + +void Jid::CopyFrom(const Jid& jid) { + this->node_name_ = jid.node_name_; + this->domain_name_ = jid.domain_name_; + this->resource_name_ = jid.resource_name_; +} + +bool Jid::operator==(const Jid& other) const { + return other.node_name_ == node_name_ && + other.domain_name_ == domain_name_ && + other.resource_name_ == resource_name_; +} + +int Jid::Compare(const Jid& other) const { + int compare_result; + compare_result = node_name_.compare(other.node_name_); + if (0 != compare_result) + return compare_result; + compare_result = domain_name_.compare(other.domain_name_); + if (0 != compare_result) + return compare_result; + compare_result = resource_name_.compare(other.resource_name_); + return compare_result; +} + +// --- JID parsing code: --- + +// Checks and normalizes the node part of a JID. +std::string Jid::PrepNode(const std::string& node, bool* valid) { + *valid = false; + std::string result; + + for (std::string::const_iterator i = node.begin(); i < node.end(); ++i) { + bool char_valid = true; + unsigned 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& resource, bool* valid) { + *valid = false; + std::string result; + + for (std::string::const_iterator i = resource.begin(); + i < resource.end(); ++i) { + bool char_valid = true; + unsigned 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& domain, 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(domain, &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& domain, std::string* buf, bool* valid) { + *valid = false; + std::string::const_iterator last = domain.begin(); + for (std::string::const_iterator i = domain.begin(); i < domain.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(last, i, buf, &label_valid); + *buf += '.'; + last = i + 1; + break; + } + if (!label_valid) { + return; + } + } + PrepDomainLabel(last, domain.end(), buf, valid); +} + +// Checks and normalizes a domain label. +void Jid::PrepDomainLabel( + std::string::const_iterator start, std::string::const_iterator end, + std::string* buf, bool* valid) { + *valid = false; + + int start_len = static_cast(buf->length()); + for (std::string::const_iterator i = start; i < end; ++i) { + bool char_valid = true; + unsigned 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 = static_cast(buf->length() - start_len); + if (count == 0) { + return; + } + else if (count > 63) { + return; + } + + // Is this check needed? See comment in PrepDomainLabelAscii. + if ((*buf)[start_len] == '-') { + 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; + } +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/jid.h b/webrtc/libjingle/xmpp/jid.h new file mode 100644 index 000000000..94ad7c833 --- /dev/null +++ b/webrtc/libjingle/xmpp/jid.h @@ -0,0 +1,81 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_JID_H_ +#define WEBRTC_LIBJINGLE_XMPP_JID_H_ + +#include +#include "webrtc/libjingle/xmllite/xmlconstants.h" +#include "webrtc/base/basictypes.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, e.g.: +// +// 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); + ~Jid(); + + const std::string & node() const { return node_name_; } + const std::string & domain() const { return domain_name_; } + const std::string & resource() const { return resource_name_; } + + std::string Str() const; + Jid BareJid() const; + + bool IsEmpty() const; + bool IsValid() const; + bool IsBare() const; + bool IsFull() const; + + bool BareEquals(const Jid& other) const; + void CopyFrom(const Jid& jid); + 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: + void ValidateOrReset(); + + static std::string PrepNode(const std::string& node, bool* valid); + static char PrepNodeAscii(char ch, bool* valid); + static std::string PrepResource(const std::string& start, bool* valid); + static char PrepResourceAscii(char ch, bool* valid); + static std::string PrepDomain(const std::string& domain, bool* valid); + static void PrepDomain(const std::string& domain, + std::string* buf, bool* valid); + static void PrepDomainLabel( + std::string::const_iterator start, std::string::const_iterator end, + std::string* buf, bool* valid); + static char PrepDomainLabelAscii(char ch, bool *valid); + + std::string node_name_; + std::string domain_name_; + std::string resource_name_; +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_JID_H_ diff --git a/webrtc/libjingle/xmpp/jid_unittest.cc b/webrtc/libjingle/xmpp/jid_unittest.cc new file mode 100644 index 000000000..e22f6a264 --- /dev/null +++ b/webrtc/libjingle/xmpp/jid_unittest.cc @@ -0,0 +1,122 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/base/gunit.h" + +using buzz::Jid; + +TEST(JidTest, TestDomain) { + Jid jid("dude"); + EXPECT_EQ("", jid.node()); + EXPECT_EQ("dude", jid.domain()); + EXPECT_EQ("", jid.resource()); + EXPECT_EQ("dude", jid.Str()); + EXPECT_EQ("dude", jid.BareJid().Str()); + EXPECT_TRUE(jid.IsValid()); + EXPECT_TRUE(jid.IsBare()); + EXPECT_FALSE(jid.IsFull()); +} + +TEST(JidTest, TestNodeDomain) { + Jid jid("walter@dude"); + EXPECT_EQ("walter", jid.node()); + EXPECT_EQ("dude", jid.domain()); + EXPECT_EQ("", jid.resource()); + EXPECT_EQ("walter@dude", jid.Str()); + EXPECT_EQ("walter@dude", jid.BareJid().Str()); + EXPECT_TRUE(jid.IsValid()); + EXPECT_TRUE(jid.IsBare()); + EXPECT_FALSE(jid.IsFull()); +} + +TEST(JidTest, TestDomainResource) { + Jid jid("dude/bowlingalley"); + EXPECT_EQ("", jid.node()); + EXPECT_EQ("dude", jid.domain()); + EXPECT_EQ("bowlingalley", jid.resource()); + EXPECT_EQ("dude/bowlingalley", jid.Str()); + EXPECT_EQ("dude", jid.BareJid().Str()); + EXPECT_TRUE(jid.IsValid()); + EXPECT_FALSE(jid.IsBare()); + EXPECT_TRUE(jid.IsFull()); +} + +TEST(JidTest, TestNodeDomainResource) { + Jid jid("walter@dude/bowlingalley"); + EXPECT_EQ("walter", jid.node()); + EXPECT_EQ("dude", jid.domain()); + EXPECT_EQ("bowlingalley", jid.resource()); + EXPECT_EQ("walter@dude/bowlingalley", jid.Str()); + EXPECT_EQ("walter@dude", jid.BareJid().Str()); + EXPECT_TRUE(jid.IsValid()); + EXPECT_FALSE(jid.IsBare()); + EXPECT_TRUE(jid.IsFull()); +} + +TEST(JidTest, TestNode) { + Jid jid("walter@"); + EXPECT_EQ("", jid.node()); + EXPECT_EQ("", jid.domain()); + EXPECT_EQ("", jid.resource()); + EXPECT_EQ("", jid.Str()); + EXPECT_EQ("", jid.BareJid().Str()); + EXPECT_FALSE(jid.IsValid()); + EXPECT_TRUE(jid.IsBare()); + EXPECT_FALSE(jid.IsFull()); +} + +TEST(JidTest, TestResource) { + Jid jid("/bowlingalley"); + EXPECT_EQ("", jid.node()); + EXPECT_EQ("", jid.domain()); + EXPECT_EQ("", jid.resource()); + EXPECT_EQ("", jid.Str()); + EXPECT_EQ("", jid.BareJid().Str()); + EXPECT_FALSE(jid.IsValid()); + EXPECT_TRUE(jid.IsBare()); + EXPECT_FALSE(jid.IsFull()); +} + +TEST(JidTest, TestNodeResource) { + Jid jid("walter@/bowlingalley"); + EXPECT_EQ("", jid.node()); + EXPECT_EQ("", jid.domain()); + EXPECT_EQ("", jid.resource()); + EXPECT_EQ("", jid.Str()); + EXPECT_EQ("", jid.BareJid().Str()); + EXPECT_FALSE(jid.IsValid()); + EXPECT_TRUE(jid.IsBare()); + EXPECT_FALSE(jid.IsFull()); +} + +TEST(JidTest, TestFunky) { + Jid jid("bowling@muchat/walter@dude"); + EXPECT_EQ("bowling", jid.node()); + EXPECT_EQ("muchat", jid.domain()); + EXPECT_EQ("walter@dude", jid.resource()); + EXPECT_EQ("bowling@muchat/walter@dude", jid.Str()); + EXPECT_EQ("bowling@muchat", jid.BareJid().Str()); + EXPECT_TRUE(jid.IsValid()); + EXPECT_FALSE(jid.IsBare()); + EXPECT_TRUE(jid.IsFull()); +} + +TEST(JidTest, TestFunky2) { + Jid jid("muchat/walter@dude"); + EXPECT_EQ("", jid.node()); + EXPECT_EQ("muchat", jid.domain()); + EXPECT_EQ("walter@dude", jid.resource()); + EXPECT_EQ("muchat/walter@dude", jid.Str()); + EXPECT_EQ("muchat", jid.BareJid().Str()); + EXPECT_TRUE(jid.IsValid()); + EXPECT_FALSE(jid.IsBare()); + EXPECT_TRUE(jid.IsFull()); +} diff --git a/webrtc/libjingle/xmpp/jingleinfotask.cc b/webrtc/libjingle/xmpp/jingleinfotask.cc new file mode 100644 index 000000000..a5a07121b --- /dev/null +++ b/webrtc/libjingle/xmpp/jingleinfotask.cc @@ -0,0 +1,121 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/jingleinfotask.h" + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/xmppclient.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" +#include "webrtc/base/socketaddress.h" + +namespace buzz { + +class JingleInfoTask::JingleInfoGetTask : public XmppTask { + public: + explicit JingleInfoGetTask(XmppTaskParentInterface* parent) + : XmppTask(parent, XmppEngine::HL_SINGLE), + done_(false) {} + + virtual int ProcessStart() { + rtc::scoped_ptr get( + MakeIq(STR_GET, Jid(), task_id())); + get->AddElement(new XmlElement(QN_JINGLE_INFO_QUERY, true)); + if (SendStanza(get.get()) != XMPP_RETURN_OK) { + return STATE_ERROR; + } + return STATE_RESPONSE; + } + virtual int ProcessResponse() { + if (done_) + return STATE_DONE; + return STATE_BLOCKED; + } + + protected: + virtual bool HandleStanza(const XmlElement * stanza) { + if (!MatchResponseIq(stanza, Jid(), task_id())) + return false; + + if (stanza->Attr(QN_TYPE) != STR_RESULT) + return false; + + // Queue the stanza with the parent so these don't get handled out of order + JingleInfoTask* parent = static_cast(GetParent()); + parent->QueueStanza(stanza); + + // Wake ourselves so we can go into the done state + done_ = true; + Wake(); + return true; + } + + bool done_; +}; + + +void JingleInfoTask::RefreshJingleInfoNow() { + JingleInfoGetTask* get_task = new JingleInfoGetTask(this); + get_task->Start(); +} + +bool +JingleInfoTask::HandleStanza(const XmlElement * stanza) { + if (!MatchRequestIq(stanza, "set", QN_JINGLE_INFO_QUERY)) + return false; + + // only respect relay push from the server + Jid from(stanza->Attr(QN_FROM)); + if (!from.IsEmpty() && + !from.BareEquals(GetClient()->jid()) && + from != Jid(GetClient()->jid().domain())) + return false; + + QueueStanza(stanza); + return true; +} + +int +JingleInfoTask::ProcessStart() { + std::vector relay_hosts; + std::vector stun_hosts; + std::string relay_token; + const XmlElement * stanza = NextStanza(); + if (stanza == NULL) + return STATE_BLOCKED; + const XmlElement * query = stanza->FirstNamed(QN_JINGLE_INFO_QUERY); + if (query == NULL) + return STATE_START; + const XmlElement *stun = query->FirstNamed(QN_JINGLE_INFO_STUN); + if (stun) { + for (const XmlElement *server = stun->FirstNamed(QN_JINGLE_INFO_SERVER); + server != NULL; server = server->NextNamed(QN_JINGLE_INFO_SERVER)) { + std::string host = server->Attr(QN_JINGLE_INFO_HOST); + std::string port = server->Attr(QN_JINGLE_INFO_UDP); + if (host != STR_EMPTY && host != STR_EMPTY) { + stun_hosts.push_back(rtc::SocketAddress(host, atoi(port.c_str()))); + } + } + } + + const XmlElement *relay = query->FirstNamed(QN_JINGLE_INFO_RELAY); + if (relay) { + relay_token = relay->TextNamed(QN_JINGLE_INFO_TOKEN); + for (const XmlElement *server = relay->FirstNamed(QN_JINGLE_INFO_SERVER); + server != NULL; server = server->NextNamed(QN_JINGLE_INFO_SERVER)) { + std::string host = server->Attr(QN_JINGLE_INFO_HOST); + if (host != STR_EMPTY) { + relay_hosts.push_back(host); + } + } + } + SignalJingleInfo(relay_token, relay_hosts, stun_hosts); + return STATE_START; +} +} diff --git a/webrtc/libjingle/xmpp/jingleinfotask.h b/webrtc/libjingle/xmpp/jingleinfotask.h new file mode 100644 index 000000000..6ed701de2 --- /dev/null +++ b/webrtc/libjingle/xmpp/jingleinfotask.h @@ -0,0 +1,44 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_JINGLEINFOTASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_JINGLEINFOTASK_H_ + +#include + +#include "webrtc/p2p/client/httpportallocator.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" +#include "webrtc/base/sigslot.h" + +namespace buzz { + +class JingleInfoTask : public XmppTask { + public: + explicit JingleInfoTask(XmppTaskParentInterface* parent) : + XmppTask(parent, XmppEngine::HL_TYPE) {} + + virtual int ProcessStart(); + void RefreshJingleInfoNow(); + + sigslot::signal3 &, + const std::vector &> + SignalJingleInfo; + + protected: + class JingleInfoGetTask; + friend class JingleInfoGetTask; + + virtual bool HandleStanza(const XmlElement * stanza); +}; +} + +#endif // WEBRTC_LIBJINGLE_XMPP_JINGLEINFOTASK_H_ diff --git a/webrtc/libjingle/xmpp/module.h b/webrtc/libjingle/xmpp/module.h new file mode 100644 index 000000000..fa26df34c --- /dev/null +++ b/webrtc/libjingle/xmpp/module.h @@ -0,0 +1,35 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_MODULE_H_ +#define WEBRTC_LIBJINGLE_XMPP_MODULE_H_ + +#include "webrtc/libjingle/xmpp/xmppengine.h" + +namespace buzz { + +class XmppEngine; + +//! This is the base class for extension modules. +//! An engine is registered with the module and the module then hooks the +//! appropriate parts of the engine to implement that set of features. It is +//! important to unregister modules before destructing the engine. +class XmppModule { +public: + virtual ~XmppModule() {} + + //! Register the engine with the module. Only one engine can be associated + //! with a module at a time. This method will return an error if there is + //! already an engine registered. + virtual XmppReturnStatus RegisterEngine(XmppEngine* engine) = 0; +}; + +} +#endif // WEBRTC_LIBJINGLE_XMPP_MODULE_H_ diff --git a/webrtc/libjingle/xmpp/moduleimpl.cc b/webrtc/libjingle/xmpp/moduleimpl.cc new file mode 100644 index 000000000..b5337a642 --- /dev/null +++ b/webrtc/libjingle/xmpp/moduleimpl.cc @@ -0,0 +1,48 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/moduleimpl.h" +#include "webrtc/base/common.h" + +namespace buzz { + +XmppModuleImpl::XmppModuleImpl() : + engine_(NULL), + stanza_handler_(this) { +} + +XmppModuleImpl::~XmppModuleImpl() +{ + if (engine_ != NULL) { + engine_->RemoveStanzaHandler(&stanza_handler_); + engine_ = NULL; + } +} + +XmppReturnStatus +XmppModuleImpl::RegisterEngine(XmppEngine* engine) +{ + if (NULL == engine || NULL != engine_) + return XMPP_RETURN_BADARGUMENT; + + engine->AddStanzaHandler(&stanza_handler_); + engine_ = engine; + + return XMPP_RETURN_OK; +} + +XmppEngine* +XmppModuleImpl::engine() { + ASSERT(NULL != engine_); + return engine_; +} + +} + diff --git a/webrtc/libjingle/xmpp/moduleimpl.h b/webrtc/libjingle/xmpp/moduleimpl.h new file mode 100644 index 000000000..5a7c6e360 --- /dev/null +++ b/webrtc/libjingle/xmpp/moduleimpl.h @@ -0,0 +1,76 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_MODULEIMPL_H_ +#define WEBRTC_LIBJINGLE_XMPP_MODULEIMPL_H_ + +#include "webrtc/libjingle/xmpp/module.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" + +namespace buzz { + +//! This is the base implementation class for extension modules. +//! An engine is registered with the module and the module then hooks the +//! appropriate parts of the engine to implement that set of features. It is +//! important to unregister modules before destructing the engine. +class XmppModuleImpl { +protected: + XmppModuleImpl(); + virtual ~XmppModuleImpl(); + + //! Register the engine with the module. Only one engine can be associated + //! with a module at a time. This method will return an error if there is + //! already an engine registered. + XmppReturnStatus RegisterEngine(XmppEngine* engine); + + //! Gets the engine that this module is attached to. + XmppEngine* engine(); + + //! Process the given stanza. + //! The module 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 *) { return false; }; + +private: + + //! The ModuleSessionHelper nested class allows the Module + //! to hook into and get stanzas and events from the engine. + class ModuleStanzaHandler : public XmppStanzaHandler { + friend class XmppModuleImpl; + + ModuleStanzaHandler(XmppModuleImpl* module) : + module_(module) { + } + + bool HandleStanza(const XmlElement* stanza) { + return module_->HandleStanza(stanza); + } + + XmppModuleImpl* module_; + }; + + friend class ModuleStanzaHandler; + + XmppEngine* engine_; + ModuleStanzaHandler stanza_handler_; +}; + + +// This macro will implement the XmppModule interface for a class +// that derives from both XmppModuleImpl and XmppModule +#define IMPLEMENT_XMPPMODULE \ + XmppReturnStatus RegisterEngine(XmppEngine* engine) { \ + return XmppModuleImpl::RegisterEngine(engine); \ + } + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_MODULEIMPL_H_ diff --git a/webrtc/libjingle/xmpp/mucroomconfigtask.cc b/webrtc/libjingle/xmpp/mucroomconfigtask.cc new file mode 100644 index 000000000..08b10650a --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomconfigtask.cc @@ -0,0 +1,74 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "webrtc/libjingle/xmpp/mucroomconfigtask.h" + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/base/scoped_ptr.h" + +namespace buzz { + +MucRoomConfigTask::MucRoomConfigTask( + XmppTaskParentInterface* parent, + const Jid& room_jid, + const std::string& room_name, + const std::vector& room_features) + : IqTask(parent, STR_SET, room_jid, + MakeRequest(room_name, room_features)), + room_jid_(room_jid) { +} + +XmlElement* MucRoomConfigTask::MakeRequest( + const std::string& room_name, + const std::vector& room_features) { + buzz::XmlElement* owner_query = new + buzz::XmlElement(buzz::QN_MUC_OWNER_QUERY, true); + + buzz::XmlElement* x_form = new buzz::XmlElement(buzz::QN_XDATA_X, true); + x_form->SetAttr(buzz::QN_TYPE, buzz::STR_FORM); + + buzz::XmlElement* roomname_field = + new buzz::XmlElement(buzz::QN_XDATA_FIELD, false); + roomname_field->SetAttr(buzz::QN_VAR, buzz::STR_MUC_ROOMCONFIG_ROOMNAME); + roomname_field->SetAttr(buzz::QN_TYPE, buzz::STR_TEXT_SINGLE); + + buzz::XmlElement* roomname_value = + new buzz::XmlElement(buzz::QN_XDATA_VALUE, false); + roomname_value->SetBodyText(room_name); + + roomname_field->AddElement(roomname_value); + x_form->AddElement(roomname_field); + + buzz::XmlElement* features_field = + new buzz::XmlElement(buzz::QN_XDATA_FIELD, false); + features_field->SetAttr(buzz::QN_VAR, buzz::STR_MUC_ROOMCONFIG_FEATURES); + features_field->SetAttr(buzz::QN_TYPE, buzz::STR_LIST_MULTI); + + for (std::vector::const_iterator feature = room_features.begin(); + feature != room_features.end(); ++feature) { + buzz::XmlElement* features_value = + new buzz::XmlElement(buzz::QN_XDATA_VALUE, false); + features_value->SetBodyText(*feature); + features_field->AddElement(features_value); + } + + x_form->AddElement(features_field); + owner_query->AddElement(x_form); + return owner_query; +} + +void MucRoomConfigTask::HandleResult(const XmlElement* element) { + SignalResult(this); +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/mucroomconfigtask.h b/webrtc/libjingle/xmpp/mucroomconfigtask.h new file mode 100644 index 000000000..d297d027f --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomconfigtask.h @@ -0,0 +1,47 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_MUCROOMCONFIGTASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_MUCROOMCONFIGTASK_H_ + +#include +#include "webrtc/libjingle/xmpp/iqtask.h" + +namespace buzz { + +// This task configures the muc room for document sharing and other enterprise +// specific goodies. +class MucRoomConfigTask : public IqTask { + public: + MucRoomConfigTask(XmppTaskParentInterface* parent, + const Jid& room_jid, + const std::string& room_name, + const std::vector& room_features); + + // Room configuration does not return any reasonable error + // values. The First config request configures the room, subseqent + // ones are just ignored by server and server returns empty + // response. + sigslot::signal1 SignalResult; + + const Jid& room_jid() const { return room_jid_; } + + protected: + virtual void HandleResult(const XmlElement* stanza); + + private: + static XmlElement* MakeRequest(const std::string& room_name, + const std::vector& room_features); + Jid room_jid_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_MUCROOMCONFIGTASK_H_ diff --git a/webrtc/libjingle/xmpp/mucroomconfigtask_unittest.cc b/webrtc/libjingle/xmpp/mucroomconfigtask_unittest.cc new file mode 100644 index 000000000..a86dd14f5 --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomconfigtask_unittest.cc @@ -0,0 +1,127 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/mucroomconfigtask.h" +#include "webrtc/base/faketaskrunner.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/sigslot.h" + +class MucRoomConfigListener : public sigslot::has_slots<> { + public: + MucRoomConfigListener() : result_count(0), error_count(0) {} + + void OnResult(buzz::MucRoomConfigTask*) { + ++result_count; + } + + void OnError(buzz::IqTask* task, + const buzz::XmlElement* error) { + ++error_count; + } + + int result_count; + int error_count; +}; + +class MucRoomConfigTaskTest : public testing::Test { + public: + MucRoomConfigTaskTest() : + room_jid("muc-jid-ponies@domain.com"), + room_name("ponies") { + } + + virtual void SetUp() { + runner = new rtc::FakeTaskRunner(); + xmpp_client = new buzz::FakeXmppClient(runner); + listener = new MucRoomConfigListener(); + } + + virtual void TearDown() { + delete listener; + // delete xmpp_client; Deleted by deleting runner. + delete runner; + } + + rtc::FakeTaskRunner* runner; + buzz::FakeXmppClient* xmpp_client; + MucRoomConfigListener* listener; + buzz::Jid room_jid; + std::string room_name; +}; + +TEST_F(MucRoomConfigTaskTest, TestConfigEnterprise) { + ASSERT_EQ(0U, xmpp_client->sent_stanzas().size()); + + std::vector room_features; + room_features.push_back("feature1"); + room_features.push_back("feature2"); + buzz::MucRoomConfigTask* task = new buzz::MucRoomConfigTask( + xmpp_client, room_jid, "ponies", room_features); + EXPECT_EQ(room_jid, task->room_jid()); + + task->SignalResult.connect(listener, &MucRoomConfigListener::OnResult); + task->Start(); + + std::string expected_iq = + "" + "" + "" + "" + "ponies" + "" + "" + "feature1" + "feature2" + "" + "" + "" + ""; + + ASSERT_EQ(1U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str()); + + EXPECT_EQ(0, listener->result_count); + EXPECT_EQ(0, listener->error_count); + + std::string response_iq = + "" + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq)); + + EXPECT_EQ(1, listener->result_count); + EXPECT_EQ(0, listener->error_count); +} + +TEST_F(MucRoomConfigTaskTest, TestError) { + std::vector room_features; + buzz::MucRoomConfigTask* task = new buzz::MucRoomConfigTask( + xmpp_client, room_jid, "ponies", room_features); + task->SignalError.connect(listener, &MucRoomConfigListener::OnError); + task->Start(); + + std::string error_iq = + "" + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(error_iq)); + + EXPECT_EQ(0, listener->result_count); + EXPECT_EQ(1, listener->error_count); +} diff --git a/webrtc/libjingle/xmpp/mucroomdiscoverytask.cc b/webrtc/libjingle/xmpp/mucroomdiscoverytask.cc new file mode 100644 index 000000000..05a56716b --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomdiscoverytask.cc @@ -0,0 +1,66 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/mucroomdiscoverytask.h" + +#include "webrtc/libjingle/xmpp/constants.h" + +namespace buzz { + +MucRoomDiscoveryTask::MucRoomDiscoveryTask( + XmppTaskParentInterface* parent, + const Jid& room_jid) + : IqTask(parent, STR_GET, room_jid, + new buzz::XmlElement(buzz::QN_DISCO_INFO_QUERY)) { +} + +void MucRoomDiscoveryTask::HandleResult(const XmlElement* stanza) { + const XmlElement* query = stanza->FirstNamed(QN_DISCO_INFO_QUERY); + if (query == NULL) { + SignalError(this, NULL); + return; + } + + std::set features; + std::map extended_info; + const XmlElement* identity = query->FirstNamed(QN_DISCO_IDENTITY); + if (identity == NULL || !identity->HasAttr(QN_NAME)) { + SignalResult(this, false, "", "", features, extended_info); + return; + } + + const std::string name(identity->Attr(QN_NAME)); + + // Get the conversation id + const XmlElement* conversation = + identity->FirstNamed(QN_GOOGLE_MUC_HANGOUT_CONVERSATION_ID); + std::string conversation_id; + if (conversation != NULL) { + conversation_id = conversation->BodyText(); + } + + for (const XmlElement* feature = query->FirstNamed(QN_DISCO_FEATURE); + feature != NULL; feature = feature->NextNamed(QN_DISCO_FEATURE)) { + features.insert(feature->Attr(QN_VAR)); + } + + const XmlElement* data_x = query->FirstNamed(QN_XDATA_X); + if (data_x != NULL) { + for (const XmlElement* field = data_x->FirstNamed(QN_XDATA_FIELD); + field != NULL; field = field->NextNamed(QN_XDATA_FIELD)) { + const std::string key(field->Attr(QN_VAR)); + extended_info[key] = field->Attr(QN_XDATA_VALUE); + } + } + + SignalResult(this, true, name, conversation_id, features, extended_info); +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/mucroomdiscoverytask.h b/webrtc/libjingle/xmpp/mucroomdiscoverytask.h new file mode 100644 index 000000000..3e332bd29 --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomdiscoverytask.h @@ -0,0 +1,41 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_MUCROOMDISCOVERYTASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_MUCROOMDISCOVERYTASK_H_ + +#include +#include +#include "webrtc/libjingle/xmpp/iqtask.h" + +namespace buzz { + +// This task requests the feature capabilities of the room. It is based on +// XEP-0030, and extended using XEP-0004. +class MucRoomDiscoveryTask : public IqTask { + public: + MucRoomDiscoveryTask(XmppTaskParentInterface* parent, + const Jid& room_jid); + + // Signal (exists, name, conversationId, features, extended_info) + sigslot::signal6&, + const std::map& > SignalResult; + + protected: + virtual void HandleResult(const XmlElement* stanza); +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_MUCROOMDISCOVERYTASK_H_ diff --git a/webrtc/libjingle/xmpp/mucroomdiscoverytask_unittest.cc b/webrtc/libjingle/xmpp/mucroomdiscoverytask_unittest.cc new file mode 100644 index 000000000..cdb50c21b --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomdiscoverytask_unittest.cc @@ -0,0 +1,145 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/mucroomdiscoverytask.h" +#include "webrtc/base/faketaskrunner.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/sigslot.h" + +class MucRoomDiscoveryListener : public sigslot::has_slots<> { + public: + MucRoomDiscoveryListener() : error_count(0) {} + + void OnResult(buzz::MucRoomDiscoveryTask* task, + bool exists, + const std::string& name, + const std::string& conversation_id, + const std::set& features, + const std::map& extended_info) { + last_exists = exists; + last_name = name; + last_conversation_id = conversation_id; + last_features = features; + last_extended_info = extended_info; + } + + void OnError(buzz::IqTask* task, + const buzz::XmlElement* error) { + ++error_count; + } + + bool last_exists; + std::string last_name; + std::string last_conversation_id; + std::set last_features; + std::map last_extended_info; + int error_count; +}; + +class MucRoomDiscoveryTaskTest : public testing::Test { + public: + MucRoomDiscoveryTaskTest() : + room_jid("muc-jid-ponies@domain.com"), + room_name("ponies"), + conversation_id("test_conversation_id") { + } + + virtual void SetUp() { + runner = new rtc::FakeTaskRunner(); + xmpp_client = new buzz::FakeXmppClient(runner); + listener = new MucRoomDiscoveryListener(); + } + + virtual void TearDown() { + delete listener; + // delete xmpp_client; Deleted by deleting runner. + delete runner; + } + + rtc::FakeTaskRunner* runner; + buzz::FakeXmppClient* xmpp_client; + MucRoomDiscoveryListener* listener; + buzz::Jid room_jid; + std::string room_name; + std::string conversation_id; +}; + +TEST_F(MucRoomDiscoveryTaskTest, TestDiscovery) { + ASSERT_EQ(0U, xmpp_client->sent_stanzas().size()); + + buzz::MucRoomDiscoveryTask* task = new buzz::MucRoomDiscoveryTask( + xmpp_client, room_jid); + task->SignalResult.connect(listener, &MucRoomDiscoveryListener::OnResult); + task->Start(); + + std::string expected_iq = + "" + "" + ""; + + ASSERT_EQ(1U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str()); + + EXPECT_EQ("", listener->last_name); + EXPECT_EQ("", listener->last_conversation_id); + + std::string response_iq = + "" + " " + " " + " " + "test_conversation_id" + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq)); + + EXPECT_EQ(true, listener->last_exists); + EXPECT_EQ(room_name, listener->last_name); + EXPECT_EQ(conversation_id, listener->last_conversation_id); + EXPECT_EQ(2U, listener->last_features.size()); + EXPECT_EQ(1U, listener->last_features.count("feature1")); + EXPECT_EQ(2U, listener->last_extended_info.size()); + EXPECT_EQ("value1", listener->last_extended_info["var1"]); + EXPECT_EQ(0, listener->error_count); +} + +TEST_F(MucRoomDiscoveryTaskTest, TestMissingName) { + buzz::MucRoomDiscoveryTask* task = new buzz::MucRoomDiscoveryTask( + xmpp_client, room_jid); + task->SignalError.connect(listener, &MucRoomDiscoveryListener::OnError); + task->Start(); + + std::string error_iq = + "" + " " + " " + " " + ""; + EXPECT_EQ(0, listener->error_count); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(error_iq)); + EXPECT_EQ(0, listener->error_count); +} diff --git a/webrtc/libjingle/xmpp/mucroomlookuptask.cc b/webrtc/libjingle/xmpp/mucroomlookuptask.cc new file mode 100644 index 000000000..8c0a4d785 --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomlookuptask.cc @@ -0,0 +1,159 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/mucroomlookuptask.h" + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" + + +namespace buzz { + +MucRoomLookupTask* +MucRoomLookupTask::CreateLookupTaskForRoomName(XmppTaskParentInterface* parent, + const Jid& lookup_server_jid, + const std::string& room_name, + const std::string& room_domain) { + return new MucRoomLookupTask(parent, lookup_server_jid, + MakeNameQuery(room_name, room_domain)); +} + +MucRoomLookupTask* +MucRoomLookupTask::CreateLookupTaskForRoomJid(XmppTaskParentInterface* parent, + const Jid& lookup_server_jid, + const Jid& room_jid) { + return new MucRoomLookupTask(parent, lookup_server_jid, + MakeJidQuery(room_jid)); +} + +MucRoomLookupTask* +MucRoomLookupTask::CreateLookupTaskForHangoutId(XmppTaskParentInterface* parent, + const Jid& lookup_server_jid, + const std::string& hangout_id) { + return new MucRoomLookupTask(parent, lookup_server_jid, + MakeHangoutIdQuery(hangout_id)); +} + +MucRoomLookupTask* +MucRoomLookupTask::CreateLookupTaskForExternalId( + XmppTaskParentInterface* parent, + const Jid& lookup_server_jid, + const std::string& external_id, + const std::string& type) { + return new MucRoomLookupTask(parent, lookup_server_jid, + MakeExternalIdQuery(external_id, type)); +} + +MucRoomLookupTask::MucRoomLookupTask(XmppTaskParentInterface* parent, + const Jid& lookup_server_jid, + XmlElement* query) + : IqTask(parent, STR_SET, lookup_server_jid, query) { +} + +XmlElement* MucRoomLookupTask::MakeNameQuery( + const std::string& room_name, const std::string& room_domain) { + XmlElement* name_elem = new XmlElement(QN_SEARCH_ROOM_NAME, false); + name_elem->SetBodyText(room_name); + + XmlElement* domain_elem = new XmlElement(QN_SEARCH_ROOM_DOMAIN, false); + domain_elem->SetBodyText(room_domain); + + XmlElement* query = new XmlElement(QN_SEARCH_QUERY, true); + query->AddElement(name_elem); + query->AddElement(domain_elem); + return query; +} + +XmlElement* MucRoomLookupTask::MakeJidQuery(const Jid& room_jid) { + XmlElement* jid_elem = new XmlElement(QN_SEARCH_ROOM_JID); + jid_elem->SetBodyText(room_jid.Str()); + + XmlElement* query = new XmlElement(QN_SEARCH_QUERY); + query->AddElement(jid_elem); + return query; +} + +XmlElement* MucRoomLookupTask::MakeExternalIdQuery( + const std::string& external_id, const std::string& type) { + XmlElement* external_id_elem = new XmlElement(QN_SEARCH_EXTERNAL_ID); + external_id_elem->SetAttr(QN_TYPE, type); + external_id_elem->SetBodyText(external_id); + + XmlElement* query = new XmlElement(QN_SEARCH_QUERY); + query->AddElement(external_id_elem); + return query; +} + +// Construct a stanza to lookup the muc jid for a given hangout id. eg: +// +// +// 0b48ad092c893a53b7bfc87422caf38e93978798e +// +XmlElement* MucRoomLookupTask::MakeHangoutIdQuery( + const std::string& hangout_id) { + XmlElement* hangout_id_elem = new XmlElement(QN_SEARCH_HANGOUT_ID, false); + hangout_id_elem->SetBodyText(hangout_id); + + XmlElement* query = new XmlElement(QN_SEARCH_QUERY, true); + query->AddElement(hangout_id_elem); + return query; +} + +// Handle a response like the following: +// +// +// +// 0b48ad092c893a53b7bfc87422caf38e93978798e +// hangout.google.com +// +// +void MucRoomLookupTask::HandleResult(const XmlElement* stanza) { + const XmlElement* query_elem = stanza->FirstNamed(QN_SEARCH_QUERY); + if (query_elem == NULL) { + SignalError(this, stanza); + return; + } + + const XmlElement* item_elem = query_elem->FirstNamed(QN_SEARCH_ITEM); + if (item_elem == NULL) { + SignalError(this, stanza); + return; + } + + MucRoomInfo room; + room.jid = Jid(item_elem->Attr(buzz::QN_JID)); + if (!room.jid.IsValid()) { + SignalError(this, stanza); + return; + } + + const XmlElement* room_name_elem = + item_elem->FirstNamed(QN_SEARCH_ROOM_NAME); + if (room_name_elem != NULL) { + room.name = room_name_elem->BodyText(); + } + + const XmlElement* room_domain_elem = + item_elem->FirstNamed(QN_SEARCH_ROOM_DOMAIN); + if (room_domain_elem != NULL) { + room.domain = room_domain_elem->BodyText(); + } + + const XmlElement* hangout_id_elem = + item_elem->FirstNamed(QN_SEARCH_HANGOUT_ID); + if (hangout_id_elem != NULL) { + room.hangout_id = hangout_id_elem->BodyText(); + } + + SignalResult(this, room); +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/mucroomlookuptask.h b/webrtc/libjingle/xmpp/mucroomlookuptask.h new file mode 100644 index 000000000..d87b3da20 --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomlookuptask.h @@ -0,0 +1,76 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_MUCROOMLOOKUPTASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_MUCROOMLOOKUPTASK_H_ + +#include +#include "webrtc/libjingle/xmpp/iqtask.h" + +namespace buzz { + +struct MucRoomInfo { + Jid jid; + std::string name; + std::string domain; + std::string hangout_id; + + std::string full_name() const { + return name + "@" + domain; + } +}; + +class MucRoomLookupTask : public IqTask { + public: + enum IdType { + ID_TYPE_CONVERSATION, + ID_TYPE_HANGOUT + }; + + static MucRoomLookupTask* + CreateLookupTaskForRoomName(XmppTaskParentInterface* parent, + const Jid& lookup_server_jid, + const std::string& room_name, + const std::string& room_domain); + static MucRoomLookupTask* + CreateLookupTaskForRoomJid(XmppTaskParentInterface* parent, + const Jid& lookup_server_jid, + const Jid& room_jid); + static MucRoomLookupTask* + CreateLookupTaskForHangoutId(XmppTaskParentInterface* parent, + const Jid& lookup_server_jid, + const std::string& hangout_id); + static MucRoomLookupTask* + CreateLookupTaskForExternalId(XmppTaskParentInterface* parent, + const Jid& lookup_server_jid, + const std::string& external_id, + const std::string& type); + + sigslot::signal2 SignalResult; + + protected: + virtual void HandleResult(const XmlElement* element); + + private: + MucRoomLookupTask(XmppTaskParentInterface* parent, + const Jid& lookup_server_jid, + XmlElement* query); + static XmlElement* MakeNameQuery(const std::string& room_name, + const std::string& room_domain); + static XmlElement* MakeJidQuery(const Jid& room_jid); + static XmlElement* MakeHangoutIdQuery(const std::string& hangout_id); + static XmlElement* MakeExternalIdQuery(const std::string& external_id, + const std::string& type); +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_MUCROOMLOOKUPTASK_H_ diff --git a/webrtc/libjingle/xmpp/mucroomlookuptask_unittest.cc b/webrtc/libjingle/xmpp/mucroomlookuptask_unittest.cc new file mode 100644 index 000000000..da5b1458e --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomlookuptask_unittest.cc @@ -0,0 +1,187 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/mucroomlookuptask.h" +#include "webrtc/base/faketaskrunner.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/sigslot.h" + +class MucRoomLookupListener : public sigslot::has_slots<> { + public: + MucRoomLookupListener() : error_count(0) {} + + void OnResult(buzz::MucRoomLookupTask* task, + const buzz::MucRoomInfo& room) { + last_room = room; + } + + void OnError(buzz::IqTask* task, + const buzz::XmlElement* error) { + ++error_count; + } + + buzz::MucRoomInfo last_room; + int error_count; +}; + +class MucRoomLookupTaskTest : public testing::Test { + public: + MucRoomLookupTaskTest() : + lookup_server_jid("lookup@domain.com"), + room_jid("muc-jid-ponies@domain.com"), + room_name("ponies"), + room_domain("domain.com"), + room_full_name("ponies@domain.com"), + hangout_id("some_hangout_id") { + } + + virtual void SetUp() { + runner = new rtc::FakeTaskRunner(); + xmpp_client = new buzz::FakeXmppClient(runner); + listener = new MucRoomLookupListener(); + } + + virtual void TearDown() { + delete listener; + // delete xmpp_client; Deleted by deleting runner. + delete runner; + } + + rtc::FakeTaskRunner* runner; + buzz::FakeXmppClient* xmpp_client; + MucRoomLookupListener* listener; + buzz::Jid lookup_server_jid; + buzz::Jid room_jid; + std::string room_name; + std::string room_domain; + std::string room_full_name; + std::string hangout_id; +}; + +TEST_F(MucRoomLookupTaskTest, TestLookupName) { + ASSERT_EQ(0U, xmpp_client->sent_stanzas().size()); + + buzz::MucRoomLookupTask* task = + buzz::MucRoomLookupTask::CreateLookupTaskForRoomName( + xmpp_client, lookup_server_jid, room_name, room_domain); + task->SignalResult.connect(listener, &MucRoomLookupListener::OnResult); + task->Start(); + + std::string expected_iq = + "" + "" + "ponies" + "domain.com" + "" + ""; + + ASSERT_EQ(1U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str()); + + EXPECT_EQ("", listener->last_room.name); + + std::string response_iq = + "" + " " + " " + " ponies" + " domain.com" + " " + " " + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq)); + + EXPECT_EQ(room_name, listener->last_room.name); + EXPECT_EQ(room_domain, listener->last_room.domain); + EXPECT_EQ(room_jid, listener->last_room.jid); + EXPECT_EQ(room_full_name, listener->last_room.full_name()); + EXPECT_EQ(0, listener->error_count); +} + +TEST_F(MucRoomLookupTaskTest, TestLookupHangoutId) { + ASSERT_EQ(0U, xmpp_client->sent_stanzas().size()); + + buzz::MucRoomLookupTask* task = buzz::MucRoomLookupTask::CreateLookupTaskForHangoutId( + xmpp_client, lookup_server_jid, hangout_id); + task->SignalResult.connect(listener, &MucRoomLookupListener::OnResult); + task->Start(); + + std::string expected_iq = + "" + "" + "some_hangout_id" + "" + ""; + + ASSERT_EQ(1U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str()); + + EXPECT_EQ("", listener->last_room.name); + + std::string response_iq = + "" + " " + " " + " some_hangout_id" + " domain.com" + " " + " " + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq)); + + EXPECT_EQ(hangout_id, listener->last_room.name); + EXPECT_EQ(room_domain, listener->last_room.domain); + EXPECT_EQ(room_jid, listener->last_room.jid); + EXPECT_EQ(0, listener->error_count); +} + +TEST_F(MucRoomLookupTaskTest, TestError) { + buzz::MucRoomLookupTask* task = buzz::MucRoomLookupTask::CreateLookupTaskForRoomName( + xmpp_client, lookup_server_jid, room_name, room_domain); + task->SignalError.connect(listener, &MucRoomLookupListener::OnError); + task->Start(); + + std::string error_iq = + "" + ""; + + EXPECT_EQ(0, listener->error_count); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(error_iq)); + EXPECT_EQ(1, listener->error_count); +} + +TEST_F(MucRoomLookupTaskTest, TestBadJid) { + buzz::MucRoomLookupTask* task = buzz::MucRoomLookupTask::CreateLookupTaskForRoomName( + xmpp_client, lookup_server_jid, room_name, room_domain); + task->SignalError.connect(listener, &MucRoomLookupListener::OnError); + task->Start(); + + std::string response_iq = + "" + " " + " " + " " + ""; + + EXPECT_EQ(0, listener->error_count); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq)); + EXPECT_EQ(1, listener->error_count); +} diff --git a/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.cc b/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.cc new file mode 100644 index 000000000..79ccc29f5 --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.cc @@ -0,0 +1,51 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.h" + +#include "webrtc/libjingle/xmpp/constants.h" + +namespace buzz { + +MucRoomUniqueHangoutIdTask::MucRoomUniqueHangoutIdTask(XmppTaskParentInterface* parent, + const Jid& lookup_server_jid) + : IqTask(parent, STR_GET, lookup_server_jid, MakeUniqueRequestXml()) { +} + +// Construct a stanza to request a unique room id. eg: +// +// +XmlElement* MucRoomUniqueHangoutIdTask::MakeUniqueRequestXml() { + XmlElement* xml = new XmlElement(QN_MUC_UNIQUE_QUERY, false); + xml->SetAttr(QN_HANGOUT_ID, STR_TRUE); + return xml; +} + +// Handle a response like the following: +// +// +// muvc-private-chat-guid@groupchat.google.com +// +void MucRoomUniqueHangoutIdTask::HandleResult(const XmlElement* stanza) { + + const XmlElement* unique_elem = stanza->FirstNamed(QN_MUC_UNIQUE_QUERY); + if (unique_elem == NULL || + !unique_elem->HasAttr(QN_HANGOUT_ID)) { + SignalError(this, stanza); + return; + } + + std::string hangout_id = unique_elem->Attr(QN_HANGOUT_ID); + + SignalResult(this, hangout_id); +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.h b/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.h new file mode 100644 index 000000000..ac662503f --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.h @@ -0,0 +1,38 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_MUCROOMUNIQUEHANGOUTIDTASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_MUCROOMUNIQUEHANGOUTIDTASK_H_ + +#include "webrtc/libjingle/xmpp/iqtask.h" + +namespace buzz { + +// Task to request a unique hangout id to be used when starting a hangout. +// The protocol is described in https://docs.google.com/a/google.com/ +// document/d/1EFLT6rCYPDVdqQXSQliXwqB3iUkpZJ9B_MNFeOZgN7g/edit +class MucRoomUniqueHangoutIdTask : public buzz::IqTask { + public: + MucRoomUniqueHangoutIdTask(buzz::XmppTaskParentInterface* parent, + const Jid& lookup_server_jid); + // signal(task, hangout_id) + sigslot::signal2 SignalResult; + + protected: + virtual void HandleResult(const buzz::XmlElement* stanza); + + private: + static buzz::XmlElement* MakeUniqueRequestXml(); + +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_MUCROOMUNIQUEHANGOUTIDTASK_H_ diff --git a/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask_unittest.cc b/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask_unittest.cc new file mode 100644 index 000000000..2480528b4 --- /dev/null +++ b/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask_unittest.cc @@ -0,0 +1,99 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.h" +#include "webrtc/base/faketaskrunner.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/sigslot.h" + +class MucRoomUniqueHangoutIdListener : public sigslot::has_slots<> { + public: + MucRoomUniqueHangoutIdListener() : error_count(0) {} + + void OnResult(buzz::MucRoomUniqueHangoutIdTask* task, + const std::string& hangout_id) { + last_hangout_id = hangout_id; + } + + void OnError(buzz::IqTask* task, + const buzz::XmlElement* error) { + ++error_count; + } + + std::string last_hangout_id; + int error_count; +}; + +class MucRoomUniqueHangoutIdTaskTest : public testing::Test { + public: + MucRoomUniqueHangoutIdTaskTest() : + lookup_server_jid("lookup@domain.com"), + hangout_id("some_hangout_id") { + } + + virtual void SetUp() { + runner = new rtc::FakeTaskRunner(); + xmpp_client = new buzz::FakeXmppClient(runner); + listener = new MucRoomUniqueHangoutIdListener(); + } + + virtual void TearDown() { + delete listener; + // delete xmpp_client; Deleted by deleting runner. + delete runner; + } + + rtc::FakeTaskRunner* runner; + buzz::FakeXmppClient* xmpp_client; + MucRoomUniqueHangoutIdListener* listener; + buzz::Jid lookup_server_jid; + std::string hangout_id; +}; + +TEST_F(MucRoomUniqueHangoutIdTaskTest, Test) { + ASSERT_EQ(0U, xmpp_client->sent_stanzas().size()); + + buzz::MucRoomUniqueHangoutIdTask* task = new buzz::MucRoomUniqueHangoutIdTask( + xmpp_client, lookup_server_jid); + task->SignalResult.connect(listener, &MucRoomUniqueHangoutIdListener::OnResult); + task->Start(); + + std::string expected_iq = + "" + "" + ""; + + ASSERT_EQ(1U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str()); + + EXPECT_EQ("", listener->last_hangout_id); + + std::string response_iq = + "" + "" + "muvc-private-chat-00001234-5678-9abc-def0-123456789abc" + "" + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq)); + + EXPECT_EQ(hangout_id, listener->last_hangout_id); + EXPECT_EQ(0, listener->error_count); +} + diff --git a/webrtc/libjingle/xmpp/pingtask.cc b/webrtc/libjingle/xmpp/pingtask.cc new file mode 100644 index 000000000..d44a6d1d8 --- /dev/null +++ b/webrtc/libjingle/xmpp/pingtask.cc @@ -0,0 +1,92 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/pingtask.h" + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" + +namespace buzz { + +PingTask::PingTask(buzz::XmppTaskParentInterface* parent, + rtc::MessageQueue* message_queue, + uint32 ping_period_millis, + uint32 ping_timeout_millis) + : buzz::XmppTask(parent, buzz::XmppEngine::HL_SINGLE), + message_queue_(message_queue), + ping_period_millis_(ping_period_millis), + ping_timeout_millis_(ping_timeout_millis), + next_ping_time_(0), + ping_response_deadline_(0) { + ASSERT(ping_period_millis >= ping_timeout_millis); +} + +bool PingTask::HandleStanza(const buzz::XmlElement* stanza) { + if (!MatchResponseIq(stanza, Jid(STR_EMPTY), task_id())) { + return false; + } + + if (stanza->Attr(buzz::QN_TYPE) != buzz::STR_RESULT && + stanza->Attr(buzz::QN_TYPE) != buzz::STR_ERROR) { + return false; + } + + QueueStanza(stanza); + return true; +} + +// This task runs indefinitely and remains in either the start or blocked +// states. +int PingTask::ProcessStart() { + if (ping_period_millis_ < ping_timeout_millis_) { + LOG(LS_ERROR) << "ping_period_millis should be >= ping_timeout_millis"; + return STATE_ERROR; + } + const buzz::XmlElement* stanza = NextStanza(); + if (stanza != NULL) { + // Received a ping response of some sort (don't care what it is). + ping_response_deadline_ = 0; + } + + uint32 now = rtc::Time(); + + // If the ping timed out, signal. + if (ping_response_deadline_ != 0 && now >= ping_response_deadline_) { + SignalTimeout(); + return STATE_ERROR; + } + + // Send a ping if it's time. + if (now >= next_ping_time_) { + rtc::scoped_ptr stanza( + MakeIq(buzz::STR_GET, Jid(STR_EMPTY), task_id())); + stanza->AddElement(new buzz::XmlElement(QN_PING)); + SendStanza(stanza.get()); + + ping_response_deadline_ = now + ping_timeout_millis_; + next_ping_time_ = now + ping_period_millis_; + + // Wake ourselves up when it's time to send another ping or when the ping + // times out (so we can fire a signal). + message_queue_->PostDelayed(ping_timeout_millis_, this); + message_queue_->PostDelayed(ping_period_millis_, this); + } + + return STATE_BLOCKED; +} + +void PingTask::OnMessage(rtc::Message* msg) { + // Get the task manager to run this task so we can send a ping or signal or + // process a ping response. + Wake(); +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/pingtask.h b/webrtc/libjingle/xmpp/pingtask.h new file mode 100644 index 000000000..9ea905b08 --- /dev/null +++ b/webrtc/libjingle/xmpp/pingtask.h @@ -0,0 +1,54 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_PINGTASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_PINGTASK_H_ + +#include "webrtc/libjingle/xmpp/xmpptask.h" +#include "webrtc/base/messagehandler.h" +#include "webrtc/base/messagequeue.h" + +namespace buzz { + +// Task to periodically send pings to the server to ensure that the network +// connection is valid, implementing XEP-0199. +// +// This is especially useful on cellular networks because: +// 1. It keeps the connections alive through the cellular network's NATs or +// proxies. +// 2. It detects when the server has crashed or any other case in which the +// connection has broken without a fin or reset packet being sent to us. +class PingTask : public buzz::XmppTask, private rtc::MessageHandler { + public: + PingTask(buzz::XmppTaskParentInterface* parent, + rtc::MessageQueue* message_queue, uint32 ping_period_millis, + uint32 ping_timeout_millis); + + virtual bool HandleStanza(const buzz::XmlElement* stanza); + virtual int ProcessStart(); + + // Raised if there is no response to a ping within ping_timeout_millis. + // The task is automatically aborted after a timeout. + sigslot::signal0<> SignalTimeout; + + private: + // Implementation of MessageHandler. + virtual void OnMessage(rtc::Message* msg); + + rtc::MessageQueue* message_queue_; + uint32 ping_period_millis_; + uint32 ping_timeout_millis_; + uint32 next_ping_time_; + uint32 ping_response_deadline_; // 0 if the response has been received +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_PINGTASK_H_ diff --git a/webrtc/libjingle/xmpp/pingtask_unittest.cc b/webrtc/libjingle/xmpp/pingtask_unittest.cc new file mode 100644 index 000000000..08a5770cd --- /dev/null +++ b/webrtc/libjingle/xmpp/pingtask_unittest.cc @@ -0,0 +1,101 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/pingtask.h" +#include "webrtc/base/faketaskrunner.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/sigslot.h" + +class PingTaskTest; + +class PingXmppClient : public buzz::FakeXmppClient { + public: + PingXmppClient(rtc::TaskParent* parent, PingTaskTest* tst) : + FakeXmppClient(parent), test(tst) { + } + + buzz::XmppReturnStatus SendStanza(const buzz::XmlElement* stanza); + + private: + PingTaskTest* test; +}; + +class PingTaskTest : public testing::Test, public sigslot::has_slots<> { + public: + PingTaskTest() : respond_to_pings(true), timed_out(false) { + } + + virtual void SetUp() { + runner = new rtc::FakeTaskRunner(); + xmpp_client = new PingXmppClient(runner, this); + } + + virtual void TearDown() { + // delete xmpp_client; Deleted by deleting runner. + delete runner; + } + + void ConnectTimeoutSignal(buzz::PingTask* task) { + task->SignalTimeout.connect(this, &PingTaskTest::OnPingTimeout); + } + + void OnPingTimeout() { + timed_out = true; + } + + rtc::FakeTaskRunner* runner; + PingXmppClient* xmpp_client; + bool respond_to_pings; + bool timed_out; +}; + +buzz::XmppReturnStatus PingXmppClient::SendStanza( + const buzz::XmlElement* stanza) { + buzz::XmppReturnStatus result = FakeXmppClient::SendStanza(stanza); + if (test->respond_to_pings && (stanza->FirstNamed(buzz::QN_PING) != NULL)) { + std::string ping_response = + ""; + HandleStanza(buzz::XmlElement::ForStr(ping_response)); + } + return result; +} + +TEST_F(PingTaskTest, TestSuccess) { + uint32 ping_period_millis = 100; + buzz::PingTask* task = new buzz::PingTask(xmpp_client, + rtc::Thread::Current(), + ping_period_millis, ping_period_millis / 10); + ConnectTimeoutSignal(task); + task->Start(); + unsigned int expected_ping_count = 5U; + EXPECT_EQ_WAIT(xmpp_client->sent_stanzas().size(), expected_ping_count, + ping_period_millis * (expected_ping_count + 1)); + EXPECT_FALSE(task->IsDone()); + EXPECT_FALSE(timed_out); +} + +TEST_F(PingTaskTest, TestTimeout) { + respond_to_pings = false; + uint32 ping_timeout_millis = 200; + buzz::PingTask* task = new buzz::PingTask(xmpp_client, + rtc::Thread::Current(), + ping_timeout_millis * 10, ping_timeout_millis); + ConnectTimeoutSignal(task); + task->Start(); + WAIT(false, ping_timeout_millis / 2); + EXPECT_FALSE(timed_out); + EXPECT_TRUE_WAIT(timed_out, ping_timeout_millis * 2); +} diff --git a/webrtc/libjingle/xmpp/plainsaslhandler.h b/webrtc/libjingle/xmpp/plainsaslhandler.h new file mode 100644 index 000000000..aa6a791eb --- /dev/null +++ b/webrtc/libjingle/xmpp/plainsaslhandler.h @@ -0,0 +1,64 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_PLAINSASLHANDLER_H_ +#define WEBRTC_LIBJINGLE_XMPP_PLAINSASLHANDLER_H_ + +#include +#include "webrtc/libjingle/xmpp/saslhandler.h" +#include "webrtc/libjingle/xmpp/saslplainmechanism.h" +#include "webrtc/base/cryptstring.h" + +namespace buzz { + +class PlainSaslHandler : public SaslHandler { +public: + PlainSaslHandler(const Jid & jid, const rtc::CryptString & password, + bool allow_plain) : jid_(jid), password_(password), + allow_plain_(allow_plain) {} + + 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) { + + if (!encrypted && !allow_plain_) { + 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_; + rtc::CryptString password_; + bool allow_plain_; +}; + + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_PLAINSASLHANDLER_H_ diff --git a/webrtc/libjingle/xmpp/presenceouttask.cc b/webrtc/libjingle/xmpp/presenceouttask.cc new file mode 100644 index 000000000..aa19c9dd7 --- /dev/null +++ b/webrtc/libjingle/xmpp/presenceouttask.cc @@ -0,0 +1,140 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/presenceouttask.h" +#include "webrtc/libjingle/xmpp/xmppclient.h" +#include "webrtc/base/stringencode.h" + +namespace buzz { + +XmppReturnStatus +PresenceOutTask::Send(const PresenceStatus & s) { + if (GetState() != STATE_INIT && GetState() != STATE_START) + return XMPP_RETURN_BADSTATE; + + XmlElement * presence = TranslateStatus(s); + QueueStanza(presence); + delete presence; + return XMPP_RETURN_OK; +} + +XmppReturnStatus +PresenceOutTask::SendDirected(const Jid & j, const PresenceStatus & s) { + if (GetState() != STATE_INIT && GetState() != STATE_START) + return XMPP_RETURN_BADSTATE; + + XmlElement * presence = TranslateStatus(s); + presence->AddAttr(QN_TO, j.Str()); + QueueStanza(presence); + delete presence; + return XMPP_RETURN_OK; +} + +XmppReturnStatus PresenceOutTask::SendProbe(const Jid & jid) { + if (GetState() != STATE_INIT && GetState() != STATE_START) + return XMPP_RETURN_BADSTATE; + + XmlElement * presence = new XmlElement(QN_PRESENCE); + presence->AddAttr(QN_TO, jid.Str()); + presence->AddAttr(QN_TYPE, "probe"); + + QueueStanza(presence); + delete presence; + return XMPP_RETURN_OK; +} + +int +PresenceOutTask::ProcessStart() { + const XmlElement * stanza = NextStanza(); + if (stanza == NULL) + return STATE_BLOCKED; + + if (SendStanza(stanza) != XMPP_RETURN_OK) + return STATE_ERROR; + + return STATE_START; +} + +XmlElement * +PresenceOutTask::TranslateStatus(const PresenceStatus & s) { + XmlElement * result = new XmlElement(QN_PRESENCE); + if (!s.available()) { + result->AddAttr(QN_TYPE, STR_UNAVAILABLE); + } + else { + if (s.show() != PresenceStatus::SHOW_ONLINE && + s.show() != PresenceStatus::SHOW_OFFLINE) { + result->AddElement(new XmlElement(QN_SHOW)); + switch (s.show()) { + default: + result->AddText(STR_SHOW_AWAY, 1); + break; + case PresenceStatus::SHOW_XA: + result->AddText(STR_SHOW_XA, 1); + break; + case PresenceStatus::SHOW_DND: + result->AddText(STR_SHOW_DND, 1); + break; + case PresenceStatus::SHOW_CHAT: + result->AddText(STR_SHOW_CHAT, 1); + break; + } + } + + result->AddElement(new XmlElement(QN_STATUS)); + result->AddText(s.status(), 1); + + if (!s.nick().empty()) { + result->AddElement(new XmlElement(QN_NICKNAME)); + result->AddText(s.nick(), 1); + } + + std::string pri; + rtc::ToString(s.priority(), &pri); + + result->AddElement(new XmlElement(QN_PRIORITY)); + result->AddText(pri, 1); + + if (s.know_capabilities()) { + result->AddElement(new XmlElement(QN_CAPS_C, true)); + result->AddAttr(QN_NODE, s.caps_node(), 1); + result->AddAttr(QN_VER, s.version(), 1); + + std::string caps; + caps.append(s.voice_capability() ? "voice-v1" : ""); + caps.append(s.pmuc_capability() ? " pmuc-v1" : ""); + caps.append(s.video_capability() ? " video-v1" : ""); + caps.append(s.camera_capability() ? " camera-v1" : ""); + + result->AddAttr(QN_EXT, caps, 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/webrtc/libjingle/xmpp/presenceouttask.h b/webrtc/libjingle/xmpp/presenceouttask.h new file mode 100644 index 000000000..88869df3c --- /dev/null +++ b/webrtc/libjingle/xmpp/presenceouttask.h @@ -0,0 +1,37 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_PRESENCEOUTTASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_PRESENCEOUTTASK_H_ + +#include "webrtc/libjingle/xmpp/presencestatus.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" + +namespace buzz { + +class PresenceOutTask : public XmppTask { +public: + explicit PresenceOutTask(XmppTaskParentInterface* parent) + : XmppTask(parent) {} + virtual ~PresenceOutTask() {} + + XmppReturnStatus Send(const PresenceStatus & s); + XmppReturnStatus SendDirected(const Jid & j, const PresenceStatus & s); + XmppReturnStatus SendProbe(const Jid& jid); + + virtual int ProcessStart(); +private: + XmlElement * TranslateStatus(const PresenceStatus & s); +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_PRESENCEOUTTASK_H_ diff --git a/webrtc/libjingle/xmpp/presencereceivetask.cc b/webrtc/libjingle/xmpp/presencereceivetask.cc new file mode 100644 index 000000000..3ea7274c7 --- /dev/null +++ b/webrtc/libjingle/xmpp/presencereceivetask.cc @@ -0,0 +1,141 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/presencereceivetask.h" + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/base/stringencode.h" + +namespace buzz { + +static bool IsUtf8FirstByte(int c) { + return (((c)&0x80)==0) || // is single byte + ((unsigned char)((c)-0xc0)<0x3e); // or is lead byte +} + +PresenceReceiveTask::PresenceReceiveTask(XmppTaskParentInterface* parent) + : XmppTask(parent, XmppEngine::HL_TYPE) { +} + +PresenceReceiveTask::~PresenceReceiveTask() { + Stop(); +} + +int PresenceReceiveTask::ProcessStart() { + const XmlElement * stanza = NextStanza(); + if (stanza == NULL) { + return STATE_BLOCKED; + } + + Jid from(stanza->Attr(QN_FROM)); + HandlePresence(from, stanza); + + return STATE_START; +} + +bool PresenceReceiveTask::HandleStanza(const XmlElement * stanza) { + // Verify that this is a presence stanze + if (stanza->Name() != QN_PRESENCE) { + return false; // not sure if this ever happens. + } + + // Queue it up + QueueStanza(stanza); + + return true; +} + +void PresenceReceiveTask::HandlePresence(const Jid& from, + const XmlElement* stanza) { + if (stanza->Attr(QN_TYPE) == STR_ERROR) { + return; + } + + PresenceStatus status; + DecodeStatus(from, stanza, &status); + PresenceUpdate(status); +} + +void PresenceReceiveTask::DecodeStatus(const Jid& from, + const XmlElement* stanza, + PresenceStatus* presence_status) { + presence_status->set_jid(from); + if (stanza->Attr(QN_TYPE) == STR_UNAVAILABLE) { + presence_status->set_available(false); + } else { + presence_status->set_available(true); + const XmlElement * status_elem = stanza->FirstNamed(QN_STATUS); + if (status_elem != NULL) { + presence_status->set_status(status_elem->BodyText()); + + // Truncate status messages longer than 300 bytes + if (presence_status->status().length() > 300) { + size_t len = 300; + + // Be careful not to split legal utf-8 chars in half + while (!IsUtf8FirstByte(presence_status->status()[len]) && len > 0) { + len -= 1; + } + std::string truncated(presence_status->status(), 0, len); + presence_status->set_status(truncated); + } + } + + const XmlElement * priority = stanza->FirstNamed(QN_PRIORITY); + if (priority != NULL) { + int pri; + if (rtc::FromString(priority->BodyText(), &pri)) { + presence_status->set_priority(pri); + } + } + + const XmlElement * show = stanza->FirstNamed(QN_SHOW); + if (show == NULL || show->FirstChild() == NULL) { + presence_status->set_show(PresenceStatus::SHOW_ONLINE); + } else if (show->BodyText() == "away") { + presence_status->set_show(PresenceStatus::SHOW_AWAY); + } else if (show->BodyText() == "xa") { + presence_status->set_show(PresenceStatus::SHOW_XA); + } else if (show->BodyText() == "dnd") { + presence_status->set_show(PresenceStatus::SHOW_DND); + } else if (show->BodyText() == "chat") { + presence_status->set_show(PresenceStatus::SHOW_CHAT); + } else { + presence_status->set_show(PresenceStatus::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); + + presence_status->set_know_capabilities(true); + presence_status->set_caps_node(node); + presence_status->set_version(ver); + } + + 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); + presence_status->set_sent_time(stamp); + } + + const XmlElement* nick = stanza->FirstNamed(QN_NICKNAME); + if (nick) { + presence_status->set_nick(nick->BodyText()); + } + } +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/presencereceivetask.h b/webrtc/libjingle/xmpp/presencereceivetask.h new file mode 100644 index 000000000..20e6c7932 --- /dev/null +++ b/webrtc/libjingle/xmpp/presencereceivetask.h @@ -0,0 +1,56 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef THIRD_PARTY_LIBJINGLE_FILES_WEBRTC_LIBJINGLE_XMPP_PRESENCERECEIVETASK_H_ +#define THIRD_PARTY_LIBJINGLE_FILES_WEBRTC_LIBJINGLE_XMPP_PRESENCERECEIVETASK_H_ + +#include "webrtc/base/sigslot.h" + +#include "webrtc/libjingle/xmpp/presencestatus.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" + +namespace buzz { + +// A task to receive presence status callbacks from the XMPP server. +class PresenceReceiveTask : public XmppTask { + public: + // Arguments: + // parent a reference to task interface associated withe the XMPP client. + explicit PresenceReceiveTask(XmppTaskParentInterface* parent); + + // Shuts down the thread associated with this task. + virtual ~PresenceReceiveTask(); + + // Starts pulling queued status messages and dispatching them to the + // PresenceUpdate() callback. + virtual int ProcessStart(); + + // Slot for presence message callbacks + sigslot::signal1 PresenceUpdate; + + protected: + // Called by the XMPP engine when presence stanzas are received from the + // server. + virtual bool HandleStanza(const XmlElement * stanza); + + private: + // Handles presence stanzas by converting the data to PresenceStatus + // objects and passing those along to the SignalStatusUpadate() callback. + void HandlePresence(const Jid& from, const XmlElement * stanza); + + // Extracts presence information for the presence stanza sent form the + // server. + static void DecodeStatus(const Jid& from, const XmlElement * stanza, + PresenceStatus* status); +}; + +} // namespace buzz + +#endif // THIRD_PARTY_LIBJINGLE_FILES_WEBRTC_LIBJINGLE_XMPP_PRESENCERECEIVETASK_H_ diff --git a/webrtc/libjingle/xmpp/presencestatus.cc b/webrtc/libjingle/xmpp/presencestatus.cc new file mode 100644 index 000000000..da6c64f1a --- /dev/null +++ b/webrtc/libjingle/xmpp/presencestatus.cc @@ -0,0 +1,45 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/presencestatus.h" + +namespace buzz { +PresenceStatus::PresenceStatus() + : pri_(0), + show_(SHOW_NONE), + available_(false), + e_code_(0), + feedback_probation_(false), + know_capabilities_(false), + voice_capability_(false), + pmuc_capability_(false), + video_capability_(false), + camera_capability_(false) { +} + +void PresenceStatus::UpdateWith(const PresenceStatus& new_value) { + if (!new_value.know_capabilities()) { + bool k = know_capabilities(); + bool p = voice_capability(); + std::string node = caps_node(); + std::string v = version(); + + *this = new_value; + + set_know_capabilities(k); + set_caps_node(node); + set_voice_capability(p); + set_version(v); + } else { + *this = new_value; + } +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/presencestatus.h b/webrtc/libjingle/xmpp/presencestatus.h new file mode 100644 index 000000000..0261c72b0 --- /dev/null +++ b/webrtc/libjingle/xmpp/presencestatus.h @@ -0,0 +1,188 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef THIRD_PARTY_LIBJINGLE_FILES_WEBRTC_LIBJINGLE_XMPP_PRESENCESTATUS_H_ +#define THIRD_PARTY_LIBJINGLE_FILES_WEBRTC_LIBJINGLE_XMPP_PRESENCESTATUS_H_ + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/jid.h" + +namespace buzz { + +class PresenceStatus { +public: + PresenceStatus(); + ~PresenceStatus() {} + + // 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_OFFLINE = 1, + SHOW_XA = 2, + SHOW_AWAY = 3, + SHOW_DND = 4, + SHOW_ONLINE = 5, + SHOW_CHAT = 6, + }; + + const Jid& jid() const { return jid_; } + int priority() const { return pri_; } + Show show() const { return show_; } + const std::string& status() const { return status_; } + const std::string& nick() const { return nick_; } + bool available() const { return available_ ; } + int error_code() const { return e_code_; } + const std::string& error_string() const { return e_str_; } + bool know_capabilities() const { return know_capabilities_; } + bool voice_capability() const { return voice_capability_; } + bool pmuc_capability() const { return pmuc_capability_; } + bool video_capability() const { return video_capability_; } + bool camera_capability() const { return camera_capability_; } + const std::string& caps_node() const { return caps_node_; } + 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_nick(const std::string& nick) { nick_ = nick; } + void set_available(bool a) { available_ = a; } + 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_voice_capability(bool f) { voice_capability_ = f; } + void set_pmuc_capability(bool f) { pmuc_capability_ = f; } + void set_video_capability(bool f) { video_capability_ = f; } + void set_camera_capability(bool f) { camera_capability_ = f; } + void set_caps_node(const std::string& f) { caps_node_ = 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 PresenceStatus& 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_; + std::string nick_; + bool available_; + int e_code_; + std::string e_str_; + bool feedback_probation_; + + // capabilities (valid only if know_capabilities_ + bool know_capabilities_; + bool voice_capability_; + bool pmuc_capability_; + bool video_capability_; + bool camera_capability_; + std::string caps_node_; + std::string version_; + + std::string sent_time_; // from the jabber:x:delay element +}; + +class MucPresenceStatus : public PresenceStatus { +}; + +} // namespace buzz + + +#endif // THIRD_PARTY_LIBJINGLE_FILES_WEBRTC_LIBJINGLE_XMPP_PRESENCESTATUS_H_ + diff --git a/webrtc/libjingle/xmpp/prexmppauth.h b/webrtc/libjingle/xmpp/prexmppauth.h new file mode 100644 index 000000000..3a1e61064 --- /dev/null +++ b/webrtc/libjingle/xmpp/prexmppauth.h @@ -0,0 +1,71 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_PREXMPPAUTH_H_ +#define WEBRTC_LIBJINGLE_XMPP_PREXMPPAUTH_H_ + +#include "webrtc/libjingle/xmpp/saslhandler.h" +#include "webrtc/base/cryptstring.h" +#include "webrtc/base/sigslot.h" + +namespace rtc { + 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 rtc::SocketAddress& server, + const rtc::CryptString& pass, + const std::string& auth_mechanism, + const std::string& auth_token) = 0; + + sigslot::signal0<> SignalAuthDone; + + virtual bool IsAuthDone() const = 0; + virtual bool IsAuthorized() const = 0; + virtual bool HadError() const = 0; + virtual int GetError() const = 0; + virtual CaptchaChallenge GetCaptchaChallenge() const = 0; + virtual std::string GetAuthMechanism() const = 0; + virtual std::string GetAuthToken() const = 0; +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_PREXMPPAUTH_H_ diff --git a/webrtc/libjingle/xmpp/pubsub_task.cc b/webrtc/libjingle/xmpp/pubsub_task.cc new file mode 100644 index 000000000..f30c05181 --- /dev/null +++ b/webrtc/libjingle/xmpp/pubsub_task.cc @@ -0,0 +1,200 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/pubsub_task.h" + +#include +#include + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/base/common.h" + +namespace buzz { + +PubsubTask::PubsubTask(XmppTaskParentInterface* parent, + const buzz::Jid& pubsub_node_jid) + : buzz::XmppTask(parent, buzz::XmppEngine::HL_SENDER), + pubsub_node_jid_(pubsub_node_jid) { +} + +PubsubTask::~PubsubTask() { +} + +// Checks for pubsub publish events as well as responses to get IQs. +bool PubsubTask::HandleStanza(const buzz::XmlElement* stanza) { + const buzz::QName& stanza_name(stanza->Name()); + if (stanza_name == buzz::QN_MESSAGE) { + if (MatchStanzaFrom(stanza, pubsub_node_jid_)) { + const buzz::XmlElement* pubsub_event_item = + stanza->FirstNamed(QN_PUBSUB_EVENT); + if (pubsub_event_item != NULL) { + QueueStanza(pubsub_event_item); + return true; + } + } + } else if (stanza_name == buzz::QN_IQ) { + if (MatchResponseIq(stanza, pubsub_node_jid_, task_id())) { + const buzz::XmlElement* pubsub_item = stanza->FirstNamed(QN_PUBSUB); + if (pubsub_item != NULL) { + QueueStanza(pubsub_item); + return true; + } + } + } + return false; +} + +int PubsubTask::ProcessResponse() { + const buzz::XmlElement* stanza = NextStanza(); + if (stanza == NULL) { + return STATE_BLOCKED; + } + + if (stanza->Attr(buzz::QN_TYPE) == buzz::STR_ERROR) { + OnPubsubError(stanza->FirstNamed(buzz::QN_ERROR)); + return STATE_RESPONSE; + } + + const buzz::QName& stanza_name(stanza->Name()); + if (stanza_name == QN_PUBSUB_EVENT) { + HandlePubsubEventMessage(stanza); + } else if (stanza_name == QN_PUBSUB) { + HandlePubsubIqGetResponse(stanza); + } + + return STATE_RESPONSE; +} + +// Registers a function pointer to be called when the value of the pubsub +// node changes. +// Note that this does not actually change the XMPP pubsub +// subscription. All publish events are always received by everyone in the +// MUC. This function just controls whether the handle function will get +// called when the event is received. +bool PubsubTask::SubscribeToNode(const std::string& pubsub_node, + NodeHandler handler) { + subscribed_nodes_[pubsub_node] = handler; + rtc::scoped_ptr get_iq_request( + MakeIq(buzz::STR_GET, pubsub_node_jid_, task_id())); + if (!get_iq_request) { + return false; + } + buzz::XmlElement* pubsub_element = new buzz::XmlElement(QN_PUBSUB, true); + buzz::XmlElement* items_element = new buzz::XmlElement(QN_PUBSUB_ITEMS, true); + + items_element->AddAttr(buzz::QN_NODE, pubsub_node); + pubsub_element->AddElement(items_element); + get_iq_request->AddElement(pubsub_element); + + if (SendStanza(get_iq_request.get()) != buzz::XMPP_RETURN_OK) { + return false; + } + + return true; +} + +void PubsubTask::UnsubscribeFromNode(const std::string& pubsub_node) { + subscribed_nodes_.erase(pubsub_node); +} + +void PubsubTask::OnPubsubError(const buzz::XmlElement* error_stanza) { +} + +// Checks for a pubsub event message like the following: +// +// +// +// +// +// +// +// +// +// +// +// It also checks for retraction event messages like the following: +// +// +// +// +// +// +// +// +void PubsubTask::HandlePubsubEventMessage( + const buzz::XmlElement* pubsub_event) { + ASSERT(pubsub_event->Name() == QN_PUBSUB_EVENT); + for (const buzz::XmlChild* child = pubsub_event->FirstChild(); + child != NULL; + child = child->NextChild()) { + const buzz::XmlElement* child_element = child->AsElement(); + const buzz::QName& child_name(child_element->Name()); + if (child_name == QN_PUBSUB_EVENT_ITEMS) { + HandlePubsubItems(child_element); + } + } +} + +// Checks for a response to an pubsub IQ get like the following: +// +// +// +// +// +// +// +// +// +// +void PubsubTask::HandlePubsubIqGetResponse( + const buzz::XmlElement* pubsub_iq_response) { + ASSERT(pubsub_iq_response->Name() == QN_PUBSUB); + for (const buzz::XmlChild* child = pubsub_iq_response->FirstChild(); + child != NULL; + child = child->NextChild()) { + const buzz::XmlElement* child_element = child->AsElement(); + const buzz::QName& child_name(child_element->Name()); + if (child_name == QN_PUBSUB_ITEMS) { + HandlePubsubItems(child_element); + } + } +} + +// Calls registered handlers in response to pubsub event or response to +// IQ pubsub get. +// 'items' is the child of a pubsub#event:event node or pubsub:pubsub node. +void PubsubTask::HandlePubsubItems(const buzz::XmlElement* items) { + ASSERT(items->HasAttr(QN_NODE)); + const std::string& node_name(items->Attr(QN_NODE)); + NodeSubscriptions::iterator iter = subscribed_nodes_.find(node_name); + if (iter != subscribed_nodes_.end()) { + NodeHandler handler = iter->second; + const buzz::XmlElement* item = items->FirstElement(); + while (item != NULL) { + const buzz::QName& item_name(item->Name()); + if (item_name != QN_PUBSUB_EVENT_ITEM && + item_name != QN_PUBSUB_EVENT_RETRACT && + item_name != QN_PUBSUB_ITEM) { + continue; + } + + (this->*handler)(item); + item = item->NextElement(); + } + return; + } +} + +} diff --git a/webrtc/libjingle/xmpp/pubsub_task.h b/webrtc/libjingle/xmpp/pubsub_task.h new file mode 100644 index 000000000..b1923a0eb --- /dev/null +++ b/webrtc/libjingle/xmpp/pubsub_task.h @@ -0,0 +1,58 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_PUBSUB_TASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_PUBSUB_TASK_H_ + +#include +#include +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" + +namespace buzz { + +// Base class to help write pubsub tasks. +// In ProcessStart call SubscribeNode with namespaces of interest along with +// NodeHandlers. +// When pubsub notifications arrive and matches the namespace, the NodeHandlers +// will be called back. +class PubsubTask : public buzz::XmppTask { + public: + virtual ~PubsubTask(); + + protected: + typedef void (PubsubTask::*NodeHandler)(const buzz::XmlElement* node); + + PubsubTask(XmppTaskParentInterface* parent, const buzz::Jid& pubsub_node_jid); + + virtual bool HandleStanza(const buzz::XmlElement* stanza); + virtual int ProcessResponse(); + + bool SubscribeToNode(const std::string& pubsub_node, NodeHandler handler); + void UnsubscribeFromNode(const std::string& pubsub_node); + + // Called when there is an error. Derived class can do what it needs to. + virtual void OnPubsubError(const buzz::XmlElement* error_stanza); + + private: + typedef std::map NodeSubscriptions; + + void HandlePubsubIqGetResponse(const buzz::XmlElement* pubsub_iq_response); + void HandlePubsubEventMessage(const buzz::XmlElement* pubsub_event_message); + void HandlePubsubItems(const buzz::XmlElement* items); + + buzz::Jid pubsub_node_jid_; + NodeSubscriptions subscribed_nodes_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_PUBSUB_TASK_H_ diff --git a/webrtc/libjingle/xmpp/pubsubclient.cc b/webrtc/libjingle/xmpp/pubsubclient.cc new file mode 100644 index 000000000..41e4e983d --- /dev/null +++ b/webrtc/libjingle/xmpp/pubsubclient.cc @@ -0,0 +1,129 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/pubsubclient.h" + +#include +#include + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/pubsubtasks.h" + +namespace buzz { + +void PubSubClient::RequestItems() { + PubSubRequestTask* request_task = + new PubSubRequestTask(parent_, pubsubjid_, node_); + request_task->SignalResult.connect(this, &PubSubClient::OnRequestResult); + request_task->SignalError.connect(this, &PubSubClient::OnRequestError); + + PubSubReceiveTask* receive_task = + new PubSubReceiveTask(parent_, pubsubjid_, node_); + receive_task->SignalUpdate.connect(this, &PubSubClient::OnReceiveUpdate); + + receive_task->Start(); + request_task->Start(); +} + +void PubSubClient::PublishItem( + const std::string& itemid, XmlElement* payload, std::string* task_id_out) { + std::vector children; + children.push_back(payload); + PublishItem(itemid, children, task_id_out); +} + +void PubSubClient::PublishItem( + const std::string& itemid, const std::vector& children, + std::string* task_id_out) { + PubSubPublishTask* publish_task = + new PubSubPublishTask(parent_, pubsubjid_, node_, itemid, children); + publish_task->SignalError.connect(this, &PubSubClient::OnPublishError); + publish_task->SignalResult.connect(this, &PubSubClient::OnPublishResult); + publish_task->Start(); + if (task_id_out) { + *task_id_out = publish_task->task_id(); + } +} + +void PubSubClient::RetractItem( + const std::string& itemid, std::string* task_id_out) { + PubSubRetractTask* retract_task = + new PubSubRetractTask(parent_, pubsubjid_, node_, itemid); + retract_task->SignalError.connect(this, &PubSubClient::OnRetractError); + retract_task->SignalResult.connect(this, &PubSubClient::OnRetractResult); + retract_task->Start(); + if (task_id_out) { + *task_id_out = retract_task->task_id(); + } +} + +void PubSubClient::OnRequestResult(PubSubRequestTask* task, + const std::vector& items) { + SignalItems(this, items); +} + +void PubSubClient::OnRequestError(IqTask* task, + const XmlElement* stanza) { + SignalRequestError(this, stanza); +} + +void PubSubClient::OnReceiveUpdate(PubSubReceiveTask* task, + const std::vector& items) { + SignalItems(this, items); +} + +const XmlElement* GetItemFromStanza(const XmlElement* stanza) { + if (stanza != NULL) { + const XmlElement* pubsub = stanza->FirstNamed(QN_PUBSUB); + if (pubsub != NULL) { + const XmlElement* publish = pubsub->FirstNamed(QN_PUBSUB_PUBLISH); + if (publish != NULL) { + return publish->FirstNamed(QN_PUBSUB_ITEM); + } + } + } + return NULL; +} + +void PubSubClient::OnPublishResult(PubSubPublishTask* task) { + const XmlElement* item = GetItemFromStanza(task->stanza()); + SignalPublishResult(this, task->task_id(), item); +} + +void PubSubClient::OnPublishError(IqTask* task, + const XmlElement* error_stanza) { + PubSubPublishTask* publish_task = + static_cast(task); + const XmlElement* item = GetItemFromStanza(publish_task->stanza()); + SignalPublishError(this, publish_task->task_id(), item, error_stanza); +} + +void PubSubClient::OnRetractResult(PubSubRetractTask* task) { + SignalRetractResult(this, task->task_id()); +} + +void PubSubClient::OnRetractError(IqTask* task, + const XmlElement* stanza) { + PubSubRetractTask* retract_task = + static_cast(task); + SignalRetractError(this, retract_task->task_id(), stanza); +} + + +const std::string PubSubClient::GetPublisherNickFromPubSubItem( + const XmlElement* item_elem) { + if (item_elem == NULL) { + return ""; + } + + return Jid(item_elem->Attr(QN_ATTR_PUBLISHER)).resource(); +} +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/pubsubclient.h b/webrtc/libjingle/xmpp/pubsubclient.h new file mode 100644 index 000000000..3044b9dc3 --- /dev/null +++ b/webrtc/libjingle/xmpp/pubsubclient.h @@ -0,0 +1,111 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_PUBSUBCLIENT_H_ +#define WEBRTC_LIBJINGLE_XMPP_PUBSUBCLIENT_H_ + +#include +#include + +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/pubsubtasks.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/sigslotrepeater.h" +#include "webrtc/base/task.h" + +// Easy to use clients built on top of the tasks for XEP-0060 +// (http://xmpp.org/extensions/xep-0060.html). + +namespace buzz { + +class Jid; +class XmlElement; +class XmppTaskParentInterface; + +// An easy-to-use pubsub client that handles the three tasks of +// getting, publishing, and listening for updates. Tied to a specific +// pubsub jid and node. All you have to do is RequestItems, listen +// for SignalItems and PublishItems. +class PubSubClient : public sigslot::has_slots<> { + public: + PubSubClient(XmppTaskParentInterface* parent, + const Jid& pubsubjid, + const std::string& node) + : parent_(parent), + pubsubjid_(pubsubjid), + node_(node) {} + + const std::string& node() const { return node_; } + + // Requests the , which will be returned via + // SignalItems, or SignalRequestError if there is a failure. Should + // auto-subscribe. + void RequestItems(); + // Fired when either are returned or when + // are received. + sigslot::signal2&> SignalItems; + // Signal (this, error stanza) + sigslot::signal2 SignalRequestError; + // Signal (this, task_id, item, error stanza) + sigslot::signal4 SignalPublishError; + // Signal (this, task_id, item) + sigslot::signal3 SignalPublishResult; + // Signal (this, task_id, error stanza) + sigslot::signal3 SignalRetractError; + // Signal (this, task_id) + sigslot::signal2 SignalRetractResult; + + // Publish an item. Takes ownership of payload. + void PublishItem(const std::string& itemid, + XmlElement* payload, + std::string* task_id_out); + // Publish an item. Takes ownership of children. + void PublishItem(const std::string& itemid, + const std::vector& children, + std::string* task_id_out); + // Retract (delete) an item. + void RetractItem(const std::string& itemid, + std::string* task_id_out); + + // Get the publisher nick if it exists from the pubsub item. + const std::string GetPublisherNickFromPubSubItem(const XmlElement* item_elem); + + private: + void OnRequestError(IqTask* task, + const XmlElement* stanza); + void OnRequestResult(PubSubRequestTask* task, + const std::vector& items); + void OnReceiveUpdate(PubSubReceiveTask* task, + const std::vector& items); + void OnPublishResult(PubSubPublishTask* task); + void OnPublishError(IqTask* task, + const XmlElement* stanza); + void OnRetractResult(PubSubRetractTask* task); + void OnRetractError(IqTask* task, + const XmlElement* stanza); + + XmppTaskParentInterface* parent_; + Jid pubsubjid_; + std::string node_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_PUBSUBCLIENT_H_ diff --git a/webrtc/libjingle/xmpp/pubsubclient_unittest.cc b/webrtc/libjingle/xmpp/pubsubclient_unittest.cc new file mode 100644 index 000000000..3815ef8a5 --- /dev/null +++ b/webrtc/libjingle/xmpp/pubsubclient_unittest.cc @@ -0,0 +1,278 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/pubsubclient.h" +#include "webrtc/base/faketaskrunner.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/sigslot.h" + +struct HandledPubSubItem { + std::string itemid; + std::string payload; +}; + +class TestPubSubItemsListener : public sigslot::has_slots<> { + public: + TestPubSubItemsListener() : error_count(0) {} + + void OnItems(buzz::PubSubClient*, + const std::vector& items) { + for (std::vector::const_iterator item = items.begin(); + item != items.end(); ++item) { + HandledPubSubItem handled_item; + handled_item.itemid = item->itemid; + if (item->elem->FirstElement() != NULL) { + handled_item.payload = item->elem->FirstElement()->Str(); + } + this->items.push_back(handled_item); + } + } + + void OnRequestError(buzz::PubSubClient* client, + const buzz::XmlElement* stanza) { + error_count++; + } + + void OnPublishResult(buzz::PubSubClient* client, + const std::string& task_id, + const buzz::XmlElement* item) { + result_task_id = task_id; + } + + void OnPublishError(buzz::PubSubClient* client, + const std::string& task_id, + const buzz::XmlElement* item, + const buzz::XmlElement* stanza) { + error_count++; + error_task_id = task_id; + } + + void OnRetractResult(buzz::PubSubClient* client, + const std::string& task_id) { + result_task_id = task_id; + } + + void OnRetractError(buzz::PubSubClient* client, + const std::string& task_id, + const buzz::XmlElement* stanza) { + error_count++; + error_task_id = task_id; + } + + std::vector items; + int error_count; + std::string error_task_id; + std::string result_task_id; +}; + +class PubSubClientTest : public testing::Test { + public: + PubSubClientTest() : + pubsubjid("room@domain.com"), + node("topic"), + itemid("key") { + runner.reset(new rtc::FakeTaskRunner()); + xmpp_client = new buzz::FakeXmppClient(runner.get()); + client.reset(new buzz::PubSubClient(xmpp_client, pubsubjid, node)); + listener.reset(new TestPubSubItemsListener()); + client->SignalItems.connect( + listener.get(), &TestPubSubItemsListener::OnItems); + client->SignalRequestError.connect( + listener.get(), &TestPubSubItemsListener::OnRequestError); + client->SignalPublishResult.connect( + listener.get(), &TestPubSubItemsListener::OnPublishResult); + client->SignalPublishError.connect( + listener.get(), &TestPubSubItemsListener::OnPublishError); + client->SignalRetractResult.connect( + listener.get(), &TestPubSubItemsListener::OnRetractResult); + client->SignalRetractError.connect( + listener.get(), &TestPubSubItemsListener::OnRetractError); + } + + rtc::scoped_ptr runner; + // xmpp_client deleted by deleting runner. + buzz::FakeXmppClient* xmpp_client; + rtc::scoped_ptr client; + rtc::scoped_ptr listener; + buzz::Jid pubsubjid; + std::string node; + std::string itemid; +}; + +TEST_F(PubSubClientTest, TestRequest) { + client->RequestItems(); + + std::string expected_iq = + "" + "" + "" + "" + ""; + + ASSERT_EQ(1U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str()); + + std::string result_iq = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + ASSERT_EQ(2U, listener->items.size()); + EXPECT_EQ("key0", listener->items[0].itemid); + EXPECT_EQ("", + listener->items[0].payload); + EXPECT_EQ("key1", listener->items[1].itemid); + EXPECT_EQ("", + listener->items[1].payload); + + std::string items_message = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(items_message)); + ASSERT_EQ(4U, listener->items.size()); + EXPECT_EQ("key0", listener->items[2].itemid); + EXPECT_EQ("", + listener->items[2].payload); + EXPECT_EQ("key1", listener->items[3].itemid); + EXPECT_EQ("", + listener->items[3].payload); +} + +TEST_F(PubSubClientTest, TestRequestError) { + std::string result_iq = + "" + " " + " " + " " + ""; + + client->RequestItems(); + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(1, listener->error_count); +} + +TEST_F(PubSubClientTest, TestPublish) { + buzz::XmlElement* payload = + new buzz::XmlElement(buzz::QName(buzz::NS_PUBSUB, "value")); + + std::string task_id; + client->PublishItem(itemid, payload, &task_id); + + std::string expected_iq = + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(1U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str()); + + std::string result_iq = + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(task_id, listener->result_task_id); +} + +TEST_F(PubSubClientTest, TestPublishError) { + buzz::XmlElement* payload = + new buzz::XmlElement(buzz::QName(buzz::NS_PUBSUB, "value")); + + std::string task_id; + client->PublishItem(itemid, payload, &task_id); + + std::string result_iq = + "" + " " + " " + " " + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(1, listener->error_count); + EXPECT_EQ(task_id, listener->error_task_id); +} + +TEST_F(PubSubClientTest, TestRetract) { + std::string task_id; + client->RetractItem(itemid, &task_id); + + std::string expected_iq = + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(1U, xmpp_client->sent_stanzas().size()); + EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str()); + + std::string result_iq = + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(task_id, listener->result_task_id); +} + +TEST_F(PubSubClientTest, TestRetractError) { + std::string task_id; + client->RetractItem(itemid, &task_id); + + std::string result_iq = + "" + " " + " " + " " + ""; + + xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + EXPECT_EQ(1, listener->error_count); + EXPECT_EQ(task_id, listener->error_task_id); +} diff --git a/webrtc/libjingle/xmpp/pubsubstateclient.cc b/webrtc/libjingle/xmpp/pubsubstateclient.cc new file mode 100644 index 000000000..1fae25dfb --- /dev/null +++ b/webrtc/libjingle/xmpp/pubsubstateclient.cc @@ -0,0 +1,25 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/pubsubstateclient.h" + +namespace buzz { + +std::string PublishedNickKeySerializer::GetKey( + const std::string& publisher_nick, const std::string& published_nick) { + return published_nick; +} + +std::string PublisherAndPublishedNicksKeySerializer::GetKey( + const std::string& publisher_nick, const std::string& published_nick) { + return publisher_nick + ":" + published_nick; +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/pubsubstateclient.h b/webrtc/libjingle/xmpp/pubsubstateclient.h new file mode 100644 index 000000000..0c53842a0 --- /dev/null +++ b/webrtc/libjingle/xmpp/pubsubstateclient.h @@ -0,0 +1,270 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_PUBSUBSTATECLIENT_H_ +#define WEBRTC_LIBJINGLE_XMPP_PUBSUBSTATECLIENT_H_ + +#include +#include +#include + +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/pubsubclient.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/sigslotrepeater.h" + +namespace buzz { + +// To handle retracts correctly, we need to remember certain details +// about an item. We could just cache the entire XML element, but +// that would take more memory and require re-parsing. +struct StateItemInfo { + std::string published_nick; + std::string publisher_nick; +}; + +// Represents a PubSub state change. Usually, the key is the nick, +// but not always. It's a per-state-type thing. Look below on how keys are +// computed. +template +struct PubSubStateChange { + // The nick of the user changing the state. + std::string publisher_nick; + // The nick of the user whose state is changing. + std::string published_nick; + C old_state; + C new_state; +}; + +// Knows how to handle specific states and XML. +template +class PubSubStateSerializer { + public: + virtual ~PubSubStateSerializer() {} + virtual XmlElement* Write(const QName& state_name, const C& state) = 0; + virtual void Parse(const XmlElement* state_elem, C* state_out) = 0; +}; + +// Knows how to create "keys" for states, which determines their +// uniqueness. Most states are per-nick, but block is +// per-blocker-and-blockee. This is independent of itemid, especially +// in the case of presenter state. +class PubSubStateKeySerializer { + public: + virtual ~PubSubStateKeySerializer() {} + virtual std::string GetKey(const std::string& publisher_nick, + const std::string& published_nick) = 0; +}; + +class PublishedNickKeySerializer : public PubSubStateKeySerializer { + public: + virtual std::string GetKey(const std::string& publisher_nick, + const std::string& published_nick); +}; + +class PublisherAndPublishedNicksKeySerializer + : public PubSubStateKeySerializer { + public: + virtual std::string GetKey(const std::string& publisher_nick, + const std::string& published_nick); +}; + +// Adapts PubSubClient to be specifically suited for pub sub call +// states. Signals state changes and keeps track of keys, which are +// normally nicks. +template +class PubSubStateClient : public sigslot::has_slots<> { + public: + // Gets ownership of the serializers, but not the client. + PubSubStateClient(const std::string& publisher_nick, + PubSubClient* client, + const QName& state_name, + C default_state, + PubSubStateKeySerializer* key_serializer, + PubSubStateSerializer* state_serializer) + : publisher_nick_(publisher_nick), + client_(client), + state_name_(state_name), + default_state_(default_state) { + key_serializer_.reset(key_serializer); + state_serializer_.reset(state_serializer); + client_->SignalItems.connect( + this, &PubSubStateClient::OnItems); + client_->SignalPublishResult.connect( + this, &PubSubStateClient::OnPublishResult); + client_->SignalPublishError.connect( + this, &PubSubStateClient::OnPublishError); + client_->SignalRetractResult.connect( + this, &PubSubStateClient::OnRetractResult); + client_->SignalRetractError.connect( + this, &PubSubStateClient::OnRetractError); + } + + virtual ~PubSubStateClient() {} + + virtual void Publish(const std::string& published_nick, + const C& state, + std::string* task_id_out) { + std::string key = key_serializer_->GetKey(publisher_nick_, published_nick); + std::string itemid = state_name_.LocalPart() + ":" + key; + if (StatesEqual(state, default_state_)) { + client_->RetractItem(itemid, task_id_out); + } else { + XmlElement* state_elem = state_serializer_->Write(state_name_, state); + state_elem->AddAttr(QN_NICK, published_nick); + client_->PublishItem(itemid, state_elem, task_id_out); + } + } + + sigslot::signal1&> SignalStateChange; + // Signal (task_id, item). item is NULL for retract. + sigslot::signal2 SignalPublishResult; + // Signal (task_id, item, error stanza). item is NULL for retract. + sigslot::signal3 SignalPublishError; + + protected: + // return false if retracted item (no info or state given) + virtual bool ParseStateItem(const PubSubItem& item, + StateItemInfo* info_out, + C* state_out) { + const XmlElement* state_elem = item.elem->FirstNamed(state_name_); + if (state_elem == NULL) { + return false; + } + + info_out->publisher_nick = + client_->GetPublisherNickFromPubSubItem(item.elem); + info_out->published_nick = state_elem->Attr(QN_NICK); + state_serializer_->Parse(state_elem, state_out); + return true; + } + + virtual bool StatesEqual(const C& state1, const C& state2) { + return state1 == state2; + } + + PubSubClient* client() { return client_; } + const QName& state_name() { return state_name_; } + + private: + void OnItems(PubSubClient* pub_sub_client, + const std::vector& items) { + for (std::vector::const_iterator item = items.begin(); + item != items.end(); ++item) { + OnItem(*item); + } + } + + void OnItem(const PubSubItem& item) { + const std::string& itemid = item.itemid; + StateItemInfo info; + C new_state; + + bool retracted = !ParseStateItem(item, &info, &new_state); + if (retracted) { + bool known_itemid = + (info_by_itemid_.find(itemid) != info_by_itemid_.end()); + if (!known_itemid) { + // Nothing to retract, and nothing to publish. + // Probably a different state type. + return; + } else { + info = info_by_itemid_[itemid]; + info_by_itemid_.erase(itemid); + new_state = default_state_; + } + } else { + // TODO: Assert new key matches the known key. It + // shouldn't change! + info_by_itemid_[itemid] = info; + } + + std::string key = key_serializer_->GetKey( + info.publisher_nick, info.published_nick); + bool has_old_state = (state_by_key_.find(key) != state_by_key_.end()); + C old_state = has_old_state ? state_by_key_[key] : default_state_; + if ((retracted && !has_old_state) || StatesEqual(new_state, old_state)) { + // Nothing change, so don't bother signalling. + return; + } + + if (retracted || StatesEqual(new_state, default_state_)) { + // We treat a default state similar to a retract. + state_by_key_.erase(key); + } else { + state_by_key_[key] = new_state; + } + + PubSubStateChange change; + if (!retracted) { + // Retracts do not have publisher information. + change.publisher_nick = info.publisher_nick; + } + change.published_nick = info.published_nick; + change.old_state = old_state; + change.new_state = new_state; + SignalStateChange(change); + } + + void OnPublishResult(PubSubClient* pub_sub_client, + const std::string& task_id, + const XmlElement* item) { + SignalPublishResult(task_id, item); + } + + void OnPublishError(PubSubClient* pub_sub_client, + const std::string& task_id, + const buzz::XmlElement* item, + const buzz::XmlElement* stanza) { + SignalPublishError(task_id, item, stanza); + } + + void OnRetractResult(PubSubClient* pub_sub_client, + const std::string& task_id) { + // There's no point in differentiating between publish and retract + // errors, so we simplify by making them both signal a publish + // result. + const XmlElement* item = NULL; + SignalPublishResult(task_id, item); + } + + void OnRetractError(PubSubClient* pub_sub_client, + const std::string& task_id, + const buzz::XmlElement* stanza) { + // There's no point in differentiating between publish and retract + // errors, so we simplify by making them both signal a publish + // error. + const XmlElement* item = NULL; + SignalPublishError(task_id, item, stanza); + } + + std::string publisher_nick_; + PubSubClient* client_; + const QName state_name_; + C default_state_; + rtc::scoped_ptr key_serializer_; + rtc::scoped_ptr > state_serializer_; + // key => state + std::map state_by_key_; + // itemid => StateItemInfo + std::map info_by_itemid_; + + DISALLOW_COPY_AND_ASSIGN(PubSubStateClient); +}; +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_PUBSUBSTATECLIENT_H_ diff --git a/webrtc/libjingle/xmpp/pubsubtasks.cc b/webrtc/libjingle/xmpp/pubsubtasks.cc new file mode 100644 index 000000000..d6532598b --- /dev/null +++ b/webrtc/libjingle/xmpp/pubsubtasks.cc @@ -0,0 +1,204 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/pubsubtasks.h" + +#include +#include + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/receivetask.h" + +// An implementation of the tasks for XEP-0060 +// (http://xmpp.org/extensions/xep-0060.html). + +namespace buzz { + +namespace { + +bool IsPubSubEventItemsElem(const XmlElement* stanza, + const std::string& expected_node) { + if (stanza->Name() != QN_MESSAGE) { + return false; + } + + const XmlElement* event_elem = stanza->FirstNamed(QN_PUBSUB_EVENT); + if (event_elem == NULL) { + return false; + } + + const XmlElement* items_elem = event_elem->FirstNamed(QN_PUBSUB_EVENT_ITEMS); + if (items_elem == NULL) { + return false; + } + + const std::string& actual_node = items_elem->Attr(QN_NODE); + return (actual_node == expected_node); +} + + +// Creates +XmlElement* CreatePubSubItemsElem(const std::string& node) { + XmlElement* items_elem = new XmlElement(QN_PUBSUB_ITEMS, false); + items_elem->AddAttr(QN_NODE, node); + XmlElement* pubsub_elem = new XmlElement(QN_PUBSUB, false); + pubsub_elem->AddElement(items_elem); + return pubsub_elem; +} + +// Creates payload... +// Takes ownership of payload. +XmlElement* CreatePubSubPublishItemElem( + const std::string& node, + const std::string& itemid, + const std::vector& children) { + XmlElement* pubsub_elem = new XmlElement(QN_PUBSUB, true); + XmlElement* publish_elem = new XmlElement(QN_PUBSUB_PUBLISH, false); + publish_elem->AddAttr(QN_NODE, node); + XmlElement* item_elem = new XmlElement(QN_PUBSUB_ITEM, false); + item_elem->AddAttr(QN_ID, itemid); + for (std::vector::const_iterator child = children.begin(); + child != children.end(); ++child) { + item_elem->AddElement(*child); + } + publish_elem->AddElement(item_elem); + pubsub_elem->AddElement(publish_elem); + return pubsub_elem; +} + +// Creates payload... +// Takes ownership of payload. +XmlElement* CreatePubSubRetractItemElem(const std::string& node, + const std::string& itemid) { + XmlElement* pubsub_elem = new XmlElement(QN_PUBSUB, true); + XmlElement* retract_elem = new XmlElement(QN_PUBSUB_RETRACT, false); + retract_elem->AddAttr(QN_NODE, node); + retract_elem->AddAttr(QN_NOTIFY, "true"); + XmlElement* item_elem = new XmlElement(QN_PUBSUB_ITEM, false); + item_elem->AddAttr(QN_ID, itemid); + retract_elem->AddElement(item_elem); + pubsub_elem->AddElement(retract_elem); + return pubsub_elem; +} + +void ParseItem(const XmlElement* item_elem, + std::vector* items) { + PubSubItem item; + item.itemid = item_elem->Attr(QN_ID); + item.elem = item_elem; + items->push_back(item); +} + +// Right now, s are treated the same as items with empty +// payloads. We may want to change it in the future, but right now +// it's sufficient for our needs. +void ParseRetract(const XmlElement* retract_elem, + std::vector* items) { + ParseItem(retract_elem, items); +} + +void ParseEventItemsElem(const XmlElement* stanza, + std::vector* items) { + const XmlElement* event_elem = stanza->FirstNamed(QN_PUBSUB_EVENT); + if (event_elem != NULL) { + const XmlElement* items_elem = + event_elem->FirstNamed(QN_PUBSUB_EVENT_ITEMS); + if (items_elem != NULL) { + for (const XmlElement* item_elem = + items_elem->FirstNamed(QN_PUBSUB_EVENT_ITEM); + item_elem != NULL; + item_elem = item_elem->NextNamed(QN_PUBSUB_EVENT_ITEM)) { + ParseItem(item_elem, items); + } + for (const XmlElement* retract_elem = + items_elem->FirstNamed(QN_PUBSUB_EVENT_RETRACT); + retract_elem != NULL; + retract_elem = retract_elem->NextNamed(QN_PUBSUB_EVENT_RETRACT)) { + ParseRetract(retract_elem, items); + } + } + } +} + +void ParsePubSubItemsElem(const XmlElement* stanza, + std::vector* items) { + const XmlElement* pubsub_elem = stanza->FirstNamed(QN_PUBSUB); + if (pubsub_elem != NULL) { + const XmlElement* items_elem = pubsub_elem->FirstNamed(QN_PUBSUB_ITEMS); + if (items_elem != NULL) { + for (const XmlElement* item_elem = items_elem->FirstNamed(QN_PUBSUB_ITEM); + item_elem != NULL; + item_elem = item_elem->NextNamed(QN_PUBSUB_ITEM)) { + ParseItem(item_elem, items); + } + } + } +} + +} // namespace + +PubSubRequestTask::PubSubRequestTask(XmppTaskParentInterface* parent, + const Jid& pubsubjid, + const std::string& node) + : IqTask(parent, STR_GET, pubsubjid, CreatePubSubItemsElem(node)) { +} + +void PubSubRequestTask::HandleResult(const XmlElement* stanza) { + std::vector items; + ParsePubSubItemsElem(stanza, &items); + SignalResult(this, items); +} + +int PubSubReceiveTask::ProcessStart() { + if (SignalUpdate.is_empty()) { + return STATE_DONE; + } + return ReceiveTask::ProcessStart(); +} + +bool PubSubReceiveTask::WantsStanza(const XmlElement* stanza) { + return MatchStanzaFrom(stanza, pubsubjid_) && + IsPubSubEventItemsElem(stanza, node_) && !SignalUpdate.is_empty(); +} + +void PubSubReceiveTask::ReceiveStanza(const XmlElement* stanza) { + std::vector items; + ParseEventItemsElem(stanza, &items); + SignalUpdate(this, items); +} + +PubSubPublishTask::PubSubPublishTask(XmppTaskParentInterface* parent, + const Jid& pubsubjid, + const std::string& node, + const std::string& itemid, + const std::vector& children) + : IqTask(parent, STR_SET, pubsubjid, + CreatePubSubPublishItemElem(node, itemid, children)), + itemid_(itemid) { +} + +void PubSubPublishTask::HandleResult(const XmlElement* stanza) { + SignalResult(this); +} + +PubSubRetractTask::PubSubRetractTask(XmppTaskParentInterface* parent, + const Jid& pubsubjid, + const std::string& node, + const std::string& itemid) + : IqTask(parent, STR_SET, pubsubjid, + CreatePubSubRetractItemElem(node, itemid)), + itemid_(itemid) { +} + +void PubSubRetractTask::HandleResult(const XmlElement* stanza) { + SignalResult(this); +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/pubsubtasks.h b/webrtc/libjingle/xmpp/pubsubtasks.h new file mode 100644 index 000000000..2f56fa87c --- /dev/null +++ b/webrtc/libjingle/xmpp/pubsubtasks.h @@ -0,0 +1,114 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_PUBSUBTASKS_H_ +#define WEBRTC_LIBJINGLE_XMPP_PUBSUBTASKS_H_ + +#include + +#include "webrtc/libjingle/xmpp/iqtask.h" +#include "webrtc/libjingle/xmpp/receivetask.h" +#include "webrtc/base/sigslot.h" + +namespace buzz { + +// A PubSub itemid + payload. Useful for signaling items. +struct PubSubItem { + std::string itemid; + // The entire , owned by the stanza handler. To keep a + // reference after handling, make a copy. + const XmlElement* elem; +}; + +// An IqTask which gets a for a particular jid and +// node, parses the items in the response and signals the items. +class PubSubRequestTask : public IqTask { + public: + PubSubRequestTask(XmppTaskParentInterface* parent, + const Jid& pubsubjid, + const std::string& node); + + sigslot::signal2&> SignalResult; + // SignalError inherited by IqTask. + private: + virtual void HandleResult(const XmlElement* stanza); +}; + +// A ReceiveTask which listens for of a particular +// pubsub JID and node and then signals them items. +class PubSubReceiveTask : public ReceiveTask { + public: + PubSubReceiveTask(XmppTaskParentInterface* parent, + const Jid& pubsubjid, + const std::string& node) + : ReceiveTask(parent), + pubsubjid_(pubsubjid), + node_(node) { + } + + virtual int ProcessStart(); + sigslot::signal2&> SignalUpdate; + + protected: + virtual bool WantsStanza(const XmlElement* stanza); + virtual void ReceiveStanza(const XmlElement* stanza); + + private: + Jid pubsubjid_; + std::string node_; +}; + +// An IqTask which publishes a to a particular +// pubsub jid and node. +class PubSubPublishTask : public IqTask { + public: + // Takes ownership of children + PubSubPublishTask(XmppTaskParentInterface* parent, + const Jid& pubsubjid, + const std::string& node, + const std::string& itemid, + const std::vector& children); + + const std::string& itemid() const { return itemid_; } + + sigslot::signal1 SignalResult; + + private: + // SignalError inherited by IqTask. + virtual void HandleResult(const XmlElement* stanza); + + std::string itemid_; +}; + +// An IqTask which publishes a to a particular +// pubsub jid and node. +class PubSubRetractTask : public IqTask { + public: + PubSubRetractTask(XmppTaskParentInterface* parent, + const Jid& pubsubjid, + const std::string& node, + const std::string& itemid); + + const std::string& itemid() const { return itemid_; } + + sigslot::signal1 SignalResult; + + private: + // SignalError inherited by IqTask. + virtual void HandleResult(const XmlElement* stanza); + + std::string itemid_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_PUBSUBTASKS_H_ diff --git a/webrtc/libjingle/xmpp/pubsubtasks_unittest.cc b/webrtc/libjingle/xmpp/pubsubtasks_unittest.cc new file mode 100644 index 000000000..8062e58e1 --- /dev/null +++ b/webrtc/libjingle/xmpp/pubsubtasks_unittest.cc @@ -0,0 +1,280 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/fakexmppclient.h" +#include "webrtc/libjingle/xmpp/iqtask.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/pubsubtasks.h" +#include "webrtc/base/faketaskrunner.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/sigslot.h" + +struct HandledPubSubItem { + std::string itemid; + std::string payload; +}; + +class TestPubSubTasksListener : public sigslot::has_slots<> { + public: + TestPubSubTasksListener() : result_count(0), error_count(0) {} + + void OnReceiveUpdate(buzz::PubSubReceiveTask* task, + const std::vector& items) { + OnItems(items); + } + + void OnRequestResult(buzz::PubSubRequestTask* task, + const std::vector& items) { + OnItems(items); + } + + void OnItems(const std::vector& items) { + for (std::vector::const_iterator item = items.begin(); + item != items.end(); ++item) { + HandledPubSubItem handled_item; + handled_item.itemid = item->itemid; + if (item->elem->FirstElement() != NULL) { + handled_item.payload = item->elem->FirstElement()->Str(); + } + this->items.push_back(handled_item); + } + } + + void OnPublishResult(buzz::PubSubPublishTask* task) { + ++result_count; + } + + void OnRetractResult(buzz::PubSubRetractTask* task) { + ++result_count; + } + + void OnError(buzz::IqTask* task, const buzz::XmlElement* stanza) { + ++error_count; + } + + std::vector items; + int result_count; + int error_count; +}; + +class PubSubTasksTest : public testing::Test { + public: + PubSubTasksTest() : + pubsubjid("room@domain.com"), + node("topic"), + itemid("key") { + runner.reset(new rtc::FakeTaskRunner()); + client = new buzz::FakeXmppClient(runner.get()); + listener.reset(new TestPubSubTasksListener()); + } + + rtc::scoped_ptr runner; + // Client deleted by deleting runner. + buzz::FakeXmppClient* client; + rtc::scoped_ptr listener; + buzz::Jid pubsubjid; + std::string node; + std::string itemid; +}; + +TEST_F(PubSubTasksTest, TestRequest) { + buzz::PubSubRequestTask* task = + new buzz::PubSubRequestTask(client, pubsubjid, node); + task->SignalResult.connect( + listener.get(), &TestPubSubTasksListener::OnRequestResult); + task->Start(); + + std::string expected_iq = + "" + "" + "" + "" + ""; + + ASSERT_EQ(1U, client->sent_stanzas().size()); + EXPECT_EQ(expected_iq, client->sent_stanzas()[0]->Str()); + + std::string result_iq = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + + ASSERT_EQ(2U, listener->items.size()); + EXPECT_EQ("key0", listener->items[0].itemid); + EXPECT_EQ("", + listener->items[0].payload); + EXPECT_EQ("key1", listener->items[1].itemid); + EXPECT_EQ("", + listener->items[1].payload); +} + +TEST_F(PubSubTasksTest, TestRequestError) { + std::string result_iq = + "" + " " + " " + " " + ""; + + buzz::PubSubRequestTask* task = + new buzz::PubSubRequestTask(client, pubsubjid, node); + task->SignalResult.connect( + listener.get(), &TestPubSubTasksListener::OnRequestResult); + task->SignalError.connect( + listener.get(), &TestPubSubTasksListener::OnError); + task->Start(); + client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + + EXPECT_EQ(0, listener->result_count); + EXPECT_EQ(1, listener->error_count); +} + +TEST_F(PubSubTasksTest, TestReceive) { + std::string items_message = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + buzz::PubSubReceiveTask* task = + new buzz::PubSubReceiveTask(client, pubsubjid, node); + task->SignalUpdate.connect( + listener.get(), &TestPubSubTasksListener::OnReceiveUpdate); + task->Start(); + client->HandleStanza(buzz::XmlElement::ForStr(items_message)); + + ASSERT_EQ(2U, listener->items.size()); + EXPECT_EQ("key0", listener->items[0].itemid); + EXPECT_EQ( + "", + listener->items[0].payload); + EXPECT_EQ("key1", listener->items[1].itemid); + EXPECT_EQ( + "", + listener->items[1].payload); +} + +TEST_F(PubSubTasksTest, TestPublish) { + buzz::XmlElement* payload = + new buzz::XmlElement(buzz::QName(buzz::NS_PUBSUB, "value")); + std::string expected_iq = + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + std::vector children; + children.push_back(payload); + buzz::PubSubPublishTask* task = + new buzz::PubSubPublishTask(client, pubsubjid, node, itemid, children); + task->SignalResult.connect( + listener.get(), &TestPubSubTasksListener::OnPublishResult); + task->Start(); + + ASSERT_EQ(1U, client->sent_stanzas().size()); + EXPECT_EQ(expected_iq, client->sent_stanzas()[0]->Str()); + + std::string result_iq = + ""; + + client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + + EXPECT_EQ(1, listener->result_count); + EXPECT_EQ(0, listener->error_count); +} + +TEST_F(PubSubTasksTest, TestPublishError) { + buzz::XmlElement* payload = + new buzz::XmlElement(buzz::QName(buzz::NS_PUBSUB, "value")); + + std::vector children; + children.push_back(payload); + buzz::PubSubPublishTask* task = + new buzz::PubSubPublishTask(client, pubsubjid, node, itemid, children); + task->SignalResult.connect( + listener.get(), &TestPubSubTasksListener::OnPublishResult); + task->SignalError.connect( + listener.get(), &TestPubSubTasksListener::OnError); + task->Start(); + + std::string result_iq = + "" + " " + " " + " " + ""; + + client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + + EXPECT_EQ(0, listener->result_count); + EXPECT_EQ(1, listener->error_count); +} + +TEST_F(PubSubTasksTest, TestRetract) { + buzz::PubSubRetractTask* task = + new buzz::PubSubRetractTask(client, pubsubjid, node, itemid); + task->SignalResult.connect( + listener.get(), &TestPubSubTasksListener::OnRetractResult); + task->SignalError.connect( + listener.get(), &TestPubSubTasksListener::OnError); + task->Start(); + + std::string expected_iq = + "" + "" + "" + "" + "" + "" + ""; + + ASSERT_EQ(1U, client->sent_stanzas().size()); + EXPECT_EQ(expected_iq, client->sent_stanzas()[0]->Str()); + + std::string result_iq = + ""; + + client->HandleStanza(buzz::XmlElement::ForStr(result_iq)); + + EXPECT_EQ(1, listener->result_count); + EXPECT_EQ(0, listener->error_count); +} diff --git a/webrtc/libjingle/xmpp/receivetask.cc b/webrtc/libjingle/xmpp/receivetask.cc new file mode 100644 index 000000000..9d4687cf5 --- /dev/null +++ b/webrtc/libjingle/xmpp/receivetask.cc @@ -0,0 +1,34 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/receivetask.h" + +namespace buzz { + +bool ReceiveTask::HandleStanza(const XmlElement* stanza) { + if (WantsStanza(stanza)) { + QueueStanza(stanza); + return true; + } + + return false; +} + +int ReceiveTask::ProcessStart() { + const XmlElement* stanza = NextStanza(); + if (stanza == NULL) + return STATE_BLOCKED; + + ReceiveStanza(stanza); + return STATE_START; +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/receivetask.h b/webrtc/libjingle/xmpp/receivetask.h new file mode 100644 index 000000000..b776746be --- /dev/null +++ b/webrtc/libjingle/xmpp/receivetask.h @@ -0,0 +1,41 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_RECEIVETASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_RECEIVETASK_H_ + +#include "webrtc/libjingle/xmpp/xmpptask.h" + +namespace buzz { + +// A base class for receiving stanzas. Override WantsStanza to +// indicate that a stanza should be received and ReceiveStanza to +// process it. Once started, ReceiveStanza will be called for all +// stanzas that return true when passed to WantsStanza. This saves +// you from having to remember how to setup the queueing and the task +// states, etc. +class ReceiveTask : public XmppTask { + public: + explicit ReceiveTask(XmppTaskParentInterface* parent) : + XmppTask(parent, XmppEngine::HL_TYPE) {} + virtual int ProcessStart(); + + protected: + virtual bool HandleStanza(const XmlElement* stanza); + + // Return true if the stanza should be received. + virtual bool WantsStanza(const XmlElement* stanza) = 0; + // Process the received stanza. + virtual void ReceiveStanza(const XmlElement* stanza) = 0; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_RECEIVETASK_H_ diff --git a/webrtc/libjingle/xmpp/rostermodule.h b/webrtc/libjingle/xmpp/rostermodule.h new file mode 100644 index 000000000..85d5d3440 --- /dev/null +++ b/webrtc/libjingle/xmpp/rostermodule.h @@ -0,0 +1,331 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_ROSTERMODULE_H_ +#define WEBRTC_LIBJINGLE_XMPP_ROSTERMODULE_H_ + +#include "webrtc/libjingle/xmpp/module.h" + +namespace buzz { + +class XmppRosterModule; + +// The main way you initialize and use the module would be like this: +// XmppRosterModule *roster_module = XmppRosterModule::Create(); +// roster_module->RegisterEngine(engine); +// roster_module->BroadcastPresence(); +// roster_module->RequestRosterUpdate(); + +//! This enum captures the valid values for the show attribute in a presence +//! stanza +enum XmppPresenceShow +{ + XMPP_PRESENCE_CHAT = 0, + XMPP_PRESENCE_DEFAULT = 1, + XMPP_PRESENCE_AWAY = 2, + XMPP_PRESENCE_XA = 3, + XMPP_PRESENCE_DND = 4, +}; + +//! These are the valid subscription states in a roster contact. This +//! represents the combination of the subscription and ask attributes +enum XmppSubscriptionState +{ + XMPP_SUBSCRIPTION_NONE = 0, + XMPP_SUBSCRIPTION_NONE_ASKED = 1, + XMPP_SUBSCRIPTION_TO = 2, + XMPP_SUBSCRIPTION_FROM = 3, + XMPP_SUBSCRIPTION_FROM_ASKED = 4, + XMPP_SUBSCRIPTION_BOTH = 5, +}; + +//! These represent the valid types of presence stanzas for managing +//! subscriptions +enum XmppSubscriptionRequestType +{ + XMPP_REQUEST_SUBSCRIBE = 0, + XMPP_REQUEST_UNSUBSCRIBE = 1, + XMPP_REQUEST_SUBSCRIBED = 2, + XMPP_REQUEST_UNSUBSCRIBED = 3, +}; + +enum XmppPresenceAvailable { + XMPP_PRESENCE_UNAVAILABLE = 0, + XMPP_PRESENCE_AVAILABLE = 1, + XMPP_PRESENCE_ERROR = 2, +}; + +enum XmppPresenceConnectionStatus { + XMPP_CONNECTION_STATUS_UNKNOWN = 0, + // Status set by the server while the user is being rung. + XMPP_CONNECTION_STATUS_CONNECTING = 1, + // Status set by the client when the user has accepted the ring but before + // the client has joined the call. + XMPP_CONNECTION_STATUS_JOINING = 2, + // Status set by the client as part of joining the call. + XMPP_CONNECTION_STATUS_CONNECTED = 3, + XMPP_CONNECTION_STATUS_HANGUP = 4, +}; + +//! Presence Information +//! This class stores both presence information for outgoing presence and is +//! returned by methods in XmppRosterModule to represent recieved incoming +//! presence information. When this class is writeable (non-const) then each +//! update to any property will set the inner xml. Setting the raw_xml will +//! rederive all of the other properties. +class XmppPresence { +public: + virtual ~XmppPresence() {} + + //! Create a new Presence + //! This is typically only used when sending a directed presence + static XmppPresence* Create(); + + //! The Jid of for the presence information. + //! Typically this will be a full Jid with resource specified. + virtual const Jid jid() const = 0; + + //! Is the contact available? + virtual XmppPresenceAvailable available() const = 0; + + //! Sets if the user is available or not + virtual XmppReturnStatus set_available(XmppPresenceAvailable available) = 0; + + //! The show value of the presence info + virtual XmppPresenceShow presence_show() const = 0; + + //! Set the presence show value + virtual XmppReturnStatus set_presence_show(XmppPresenceShow show) = 0; + + //! The Priority of the presence info + virtual int priority() const = 0; + + //! Set the priority of the presence + virtual XmppReturnStatus set_priority(int priority) = 0; + + //! The plain text status of the presence info. + //! If there are multiple status because of language, this will either be a + //! status that is not tagged for language or the first available + virtual const std::string status() const = 0; + + //! Sets the status for the presence info. + //! If there is more than one status present already then this will remove + //! them all and replace it with one status element we no specified language + virtual XmppReturnStatus set_status(const std::string& status) = 0; + + //! The connection status + virtual XmppPresenceConnectionStatus connection_status() const = 0; + + //! The focus obfuscated GAIA id + virtual const std::string google_user_id() const = 0; + + //! The nickname in the presence + virtual const std::string nickname() const = 0; + + //! The raw xml of the presence update + virtual const XmlElement* raw_xml() const = 0; + + //! Sets the raw presence stanza for the presence update + //! This will cause all other data items in this structure to be rederived + virtual XmppReturnStatus set_raw_xml(const XmlElement * xml) = 0; +}; + +//! A contact as given by the server +class XmppRosterContact { +public: + virtual ~XmppRosterContact() {} + + //! Create a new roster contact + //! This is typically only used when doing a roster update/add + static XmppRosterContact* Create(); + + //! The jid for the contact. + //! Typically this will be a bare Jid. + virtual const Jid jid() const = 0; + + //! Sets the jid for the roster contact update + virtual XmppReturnStatus set_jid(const Jid& jid) = 0; + + //! The name (nickname) stored for this contact + virtual const std::string name() const = 0; + + //! Sets the name + virtual XmppReturnStatus set_name(const std::string& name) = 0; + + //! The Presence subscription state stored on the server for this contact + //! This is never settable and will be ignored when generating a roster + //! add/update request + virtual XmppSubscriptionState subscription_state() const = 0; + + //! The number of Groups applied to this contact + virtual size_t GetGroupCount() const = 0; + + //! Gets a Group applied to the contact based on index. + //! range + virtual const std::string GetGroup(size_t index) const = 0; + + //! Adds a group to this contact. + //! This will return a bad argument error if the group is already there. + virtual XmppReturnStatus AddGroup(const std::string& group) = 0; + + //! Removes a group from the contact. + //! This will return an error if the group cannot be found in the group list. + virtual XmppReturnStatus RemoveGroup(const std::string& group) = 0; + + //! The raw xml for this roster contact + virtual const XmlElement* raw_xml() const = 0; + + //! Sets the raw presence stanza for the contact update/add + //! This will cause all other data items in this structure to be rederived + virtual XmppReturnStatus set_raw_xml(const XmlElement * xml) = 0; +}; + +//! The XmppRosterHandler is an interface for callbacks from the module +class XmppRosterHandler { +public: + virtual ~XmppRosterHandler() {} + + //! A request for a subscription has come in. + //! Typically, the UI will ask the user if it is okay to let the requester + //! get presence notifications for the user. The response is send back + //! by calling ApproveSubscriber or CancelSubscriber. + virtual void SubscriptionRequest(XmppRosterModule* roster, + const Jid& requesting_jid, + XmppSubscriptionRequestType type, + const XmlElement* raw_xml) = 0; + + //! Some type of presence error has occured + virtual void SubscriptionError(XmppRosterModule* roster, + const Jid& from, + const XmlElement* raw_xml) = 0; + + virtual void RosterError(XmppRosterModule* roster, + const XmlElement* raw_xml) = 0; + + //! New presence information has come in + //! The user is notified with the presence object directly. This info is also + //! added to the store accessable from the engine. + virtual void IncomingPresenceChanged(XmppRosterModule* roster, + const XmppPresence* presence) = 0; + + //! A contact has changed + //! This indicates that the data for a contact may have changed. No + //! contacts have been added or removed. + virtual void ContactChanged(XmppRosterModule* roster, + const XmppRosterContact* old_contact, + size_t index) = 0; + + //! A set of contacts have been added + //! These contacts may have been added in response to the original roster + //! request or due to a "roster push" from the server. + virtual void ContactsAdded(XmppRosterModule* roster, + size_t index, size_t number) = 0; + + //! A contact has been removed + //! This contact has been removed form the list. + virtual void ContactRemoved(XmppRosterModule* roster, + const XmppRosterContact* removed_contact, + size_t index) = 0; + +}; + +//! An XmppModule for handle roster and presence functionality +class XmppRosterModule : public XmppModule { +public: + //! Creates a new XmppRosterModule + static XmppRosterModule * Create(); + virtual ~XmppRosterModule() {} + + //! Sets the roster handler (callbacks) for the module + virtual XmppReturnStatus set_roster_handler(XmppRosterHandler * handler) = 0; + + //! Gets the roster handler for the module + virtual XmppRosterHandler* roster_handler() = 0; + + // USER PRESENCE STATE ------------------------------------------------------- + + //! Gets the aggregate outgoing presence + //! This object is non-const and be edited directly. No update is sent + //! to the server until a Broadcast is sent + virtual XmppPresence* outgoing_presence() = 0; + + //! Broadcasts that the user is available. + //! Nothing with respect to presence is sent until this is called. + virtual XmppReturnStatus BroadcastPresence() = 0; + + //! Sends a directed presence to a Jid + //! Note that the client doesn't store where directed presence notifications + //! have been sent. The server can keep the appropriate state + virtual XmppReturnStatus SendDirectedPresence(const XmppPresence* presence, + const Jid& to_jid) = 0; + + // INCOMING PRESENCE STATUS -------------------------------------------------- + + //! Returns the number of incoming presence data recorded + virtual size_t GetIncomingPresenceCount() = 0; + + //! Returns an incoming presence datum based on index + virtual const XmppPresence* GetIncomingPresence(size_t index) = 0; + + //! Gets the number of presence data for a bare Jid + //! There may be a datum per resource + virtual size_t GetIncomingPresenceForJidCount(const Jid& jid) = 0; + + //! Returns a single presence data for a Jid based on index + virtual const XmppPresence* GetIncomingPresenceForJid(const Jid& jid, + size_t index) = 0; + + // ROSTER MANAGEMENT --------------------------------------------------------- + + //! Requests an update of the roster from the server + //! This must be called to initialize the client side cache of the roster + //! After this is sent the server should keep this module apprised of any + //! changes. + virtual XmppReturnStatus RequestRosterUpdate() = 0; + + //! Returns the number of contacts in the roster + virtual size_t GetRosterContactCount() = 0; + + //! Returns a contact by index + virtual const XmppRosterContact* GetRosterContact(size_t index) = 0; + + //! Finds a contact by Jid + virtual const XmppRosterContact* FindRosterContact(const Jid& jid) = 0; + + //! Send a request to the server to add a contact + //! Note that the contact won't show up in the roster until the server can + //! respond. This happens async when the socket is being serviced + virtual XmppReturnStatus RequestRosterChange( + const XmppRosterContact* contact) = 0; + + //! Request that the server remove a contact + //! The jabber protocol specifies that the server should also cancel any + //! subscriptions when this is done. Like adding, this contact won't be + //! removed until the server responds. + virtual XmppReturnStatus RequestRosterRemove(const Jid& jid) = 0; + + // SUBSCRIPTION MANAGEMENT --------------------------------------------------- + + //! Request a subscription to presence notifications form a Jid + virtual XmppReturnStatus RequestSubscription(const Jid& jid) = 0; + + //! Cancel a subscription to presence notifications from a Jid + virtual XmppReturnStatus CancelSubscription(const Jid& jid) = 0; + + //! Approve a request to deliver presence notifications to a jid + virtual XmppReturnStatus ApproveSubscriber(const Jid& jid) = 0; + + //! Deny or cancel presence notification deliver to a jid + virtual XmppReturnStatus CancelSubscriber(const Jid& jid) = 0; +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_ROSTERMODULE_H_ diff --git a/webrtc/libjingle/xmpp/rostermodule_unittest.cc b/webrtc/libjingle/xmpp/rostermodule_unittest.cc new file mode 100644 index 000000000..1ae6c226a --- /dev/null +++ b/webrtc/libjingle/xmpp/rostermodule_unittest.cc @@ -0,0 +1,832 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include + +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/rostermodule.h" +#include "webrtc/libjingle/xmpp/util_unittest.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/scoped_ptr.h" + +#define TEST_OK(x) EXPECT_EQ((x),XMPP_RETURN_OK) +#define TEST_BADARGUMENT(x) EXPECT_EQ((x),XMPP_RETURN_BADARGUMENT) + + +namespace buzz { + +class RosterModuleTest; + +static void +WriteString(std::ostream& os, const std::string& str) { + os<jid().Str()); + os<<" available:"<available(); + os<<" presence_show:"; + WritePresenceShow(os, presence->presence_show()); + os<<" priority:"<priority(); + os<<" status:"; + WriteString(os, presence->status()); + os<<"]"<raw_xml()->Str(); +} + +static void +WriteContact(std::ostream& os, const XmppRosterContact* contact) { + if (contact == NULL) { + os<<"NULL"; + return; + } + + os<<"[Contact jid:"; + WriteString(os, contact->jid().Str()); + os<<" name:"; + WriteString(os, contact->name()); + os<<" subscription_state:"; + WriteSubscriptionState(os, contact->subscription_state()); + os<<" groups:["; + for(size_t i=0; i < contact->GetGroupCount(); ++i) { + os<<(i==0?"":", "); + WriteString(os, contact->GetGroup(i)); + } + os<<"]]"<raw_xml()->Str(); +} + +//! This session handler saves all calls to a string. These are events and +//! data delivered form the engine to application code. +class XmppTestRosterHandler : public XmppRosterHandler { +public: + XmppTestRosterHandler() {} + virtual ~XmppTestRosterHandler() {} + + virtual void SubscriptionRequest(XmppRosterModule*, + const Jid& requesting_jid, + XmppSubscriptionRequestType type, + const XmlElement* raw_xml) { + ss_<<"[SubscriptionRequest Jid:" << requesting_jid.Str()<<" type:"; + WriteSubscriptionRequestType(ss_, type); + ss_<<"]"<Str(); + } + + //! Some type of presence error has occured + virtual void SubscriptionError(XmppRosterModule*, + const Jid& from, + const XmlElement* raw_xml) { + ss_<<"[SubscriptionError from:"<Str(); + } + + virtual void RosterError(XmppRosterModule*, + const XmlElement* raw_xml) { + ss_<<"[RosterError]"<Str(); + } + + //! New presence information has come in + //! The user is notified with the presence object directly. This info is also + //! added to the store accessable from the engine. + virtual void IncomingPresenceChanged(XmppRosterModule*, + const XmppPresence* presence) { + ss_<<"[IncomingPresenceChanged presence:"; + WritePresence(ss_, presence); + ss_<<"]"; + } + + //! A contact has changed + //! This indicates that the data for a contact may have changed. No + //! contacts have been added or removed. + virtual void ContactChanged(XmppRosterModule* roster, + const XmppRosterContact* old_contact, + size_t index) { + ss_<<"[ContactChanged old_contact:"; + WriteContact(ss_, old_contact); + ss_<<" index:"<GetRosterContact(index)); + ss_<<"]"; + } + + //! A set of contacts have been added + //! These contacts may have been added in response to the original roster + //! request or due to a "roster push" from the server. + virtual void ContactsAdded(XmppRosterModule* roster, + size_t index, size_t number) { + ss_<<"[ContactsAdded index:"<GetRosterContact(index+i)); + } + ss_<<"]"; + } + + //! A contact has been removed + //! This contact has been removed form the list. + virtual void ContactRemoved(XmppRosterModule*, + const XmppRosterContact* removed_contact, + size_t index) { + ss_<<"[ContactRemoved old_contact:"; + WriteContact(ss_, removed_contact); + ss_<<" index:"<AddAttr(QN_STATUS, STR_PSTN_CONFERENCE_STATUS_CONNECTING); + XmlElement presence_xml(QN_PRESENCE); + presence_xml.AddElement(status); + rtc::scoped_ptr presence(XmppPresence::Create()); + presence->set_raw_xml(&presence_xml); + EXPECT_EQ(presence->connection_status(), XMPP_CONNECTION_STATUS_CONNECTING); +} + +TEST_F(RosterModuleTest, TestOutgoingPresence) { + std::stringstream dump; + + rtc::scoped_ptr engine(XmppEngine::Create()); + XmppTestHandler handler(engine.get()); + XmppTestRosterHandler roster_handler; + + rtc::scoped_ptr roster(XmppRosterModule::Create()); + roster->set_roster_handler(&roster_handler); + + // Configure the roster module + roster->RegisterEngine(engine.get()); + + // Set up callbacks + engine->SetOutputHandler(&handler); + engine->AddStanzaHandler(&handler); + engine->SetSessionHandler(&handler); + + // Set up minimal login info + engine->SetUser(Jid("david@my-server")); + // engine->SetPassword("david"); + + // Do the whole login handshake + RunLogin(this, engine.get(), &handler); + EXPECT_EQ("", handler.OutputActivity()); + + // Set some presence and broadcast it + TEST_OK(roster->outgoing_presence()-> + set_available(XMPP_PRESENCE_AVAILABLE)); + TEST_OK(roster->outgoing_presence()->set_priority(-37)); + TEST_OK(roster->outgoing_presence()->set_presence_show(XMPP_PRESENCE_DND)); + TEST_OK(roster->outgoing_presence()-> + set_status("I'm off to the races!<>&")); + TEST_OK(roster->BroadcastPresence()); + + EXPECT_EQ(roster_handler.StrClear(), ""); + EXPECT_EQ(handler.OutputActivity(), + "" + "-37" + "dnd" + "I'm off to the races!<>&" + ""); + EXPECT_EQ(handler.SessionActivity(), ""); + + // Try some more + TEST_OK(roster->outgoing_presence()-> + set_available(XMPP_PRESENCE_UNAVAILABLE)); + TEST_OK(roster->outgoing_presence()->set_priority(0)); + TEST_OK(roster->outgoing_presence()->set_presence_show(XMPP_PRESENCE_XA)); + TEST_OK(roster->outgoing_presence()->set_status("Gone fishin'")); + TEST_OK(roster->BroadcastPresence()); + + EXPECT_EQ(roster_handler.StrClear(), ""); + EXPECT_EQ(handler.OutputActivity(), + "" + "xa" + "Gone fishin'" + ""); + EXPECT_EQ(handler.SessionActivity(), ""); + + // Okay -- we are back on + TEST_OK(roster->outgoing_presence()-> + set_available(XMPP_PRESENCE_AVAILABLE)); + TEST_BADARGUMENT(roster->outgoing_presence()->set_priority(128)); + TEST_OK(roster->outgoing_presence()-> + set_presence_show(XMPP_PRESENCE_DEFAULT)); + TEST_OK(roster->outgoing_presence()->set_status("Cookin' wit gas")); + TEST_OK(roster->BroadcastPresence()); + + EXPECT_EQ(roster_handler.StrClear(), ""); + EXPECT_EQ(handler.OutputActivity(), + "" + "Cookin' wit gas" + ""); + EXPECT_EQ(handler.SessionActivity(), ""); + + // Set it via XML + XmlElement presence_input(QN_PRESENCE); + presence_input.AddAttr(QN_TYPE, "unavailable"); + presence_input.AddElement(new XmlElement(QN_PRIORITY)); + presence_input.AddText("42", 1); + presence_input.AddElement(new XmlElement(QN_STATUS)); + presence_input.AddAttr(QN_XML_LANG, "es", 1); + presence_input.AddText("Hola Amigos!", 1); + presence_input.AddElement(new XmlElement(QN_STATUS)); + presence_input.AddText("Hey there, friend!", 1); + TEST_OK(roster->outgoing_presence()->set_raw_xml(&presence_input)); + TEST_OK(roster->BroadcastPresence()); + + WritePresence(dump, roster->outgoing_presence()); + EXPECT_EQ(dump.str(), + "[Presence jid: available:0 presence_show:[default] " + "priority:42 status:Hey there, friend!]" + "" + "42" + "Hola Amigos!" + "Hey there, friend!" + ""); + dump.str(""); + EXPECT_EQ(roster_handler.StrClear(), ""); + EXPECT_EQ(handler.OutputActivity(), + "" + "42" + "Hola Amigos!" + "Hey there, friend!" + ""); + EXPECT_EQ(handler.SessionActivity(), ""); + + // Construct a directed presence + rtc::scoped_ptr directed_presence(XmppPresence::Create()); + TEST_OK(directed_presence->set_available(XMPP_PRESENCE_AVAILABLE)); + TEST_OK(directed_presence->set_priority(120)); + TEST_OK(directed_presence->set_status("*very* available")); + TEST_OK(roster->SendDirectedPresence(directed_presence.get(), + Jid("myhoney@honey.net"))); + + EXPECT_EQ(roster_handler.StrClear(), ""); + EXPECT_EQ(handler.OutputActivity(), + "" + "120" + "*very* available" + ""); + EXPECT_EQ(handler.SessionActivity(), ""); +} + +TEST_F(RosterModuleTest, TestIncomingPresence) { + rtc::scoped_ptr engine(XmppEngine::Create()); + XmppTestHandler handler(engine.get()); + XmppTestRosterHandler roster_handler; + + rtc::scoped_ptr roster(XmppRosterModule::Create()); + roster->set_roster_handler(&roster_handler); + + // Configure the roster module + roster->RegisterEngine(engine.get()); + + // Set up callbacks + engine->SetOutputHandler(&handler); + engine->AddStanzaHandler(&handler); + engine->SetSessionHandler(&handler); + + // Set up minimal login info + engine->SetUser(Jid("david@my-server")); + // engine->SetPassword("david"); + + // Do the whole login handshake + RunLogin(this, engine.get(), &handler); + EXPECT_EQ("", handler.OutputActivity()); + + // Load up with a bunch of data + std::string input; + input = "" + "" + "-10" + "xa" + "Off bowling" + "" + "" + "20" + "Looking for toes..." + "" + "" + "10" + "Throwing rocks" + ""; + TEST_OK(engine->HandleInput(input.c_str(), input.length())); + + EXPECT_EQ(roster_handler.StrClear(), + "[IncomingPresenceChanged " + "presence:[Presence jid:maude@example.net/studio available:1 " + "presence_show:[default] priority:0 status:]" + "]" + "[IncomingPresenceChanged " + "presence:[Presence jid:walter@example.net/home available:1 " + "presence_show:xa priority:-10 status:Off bowling]" + "" + "-10" + "xa" + "Off bowling" + "]" + "[IncomingPresenceChanged " + "presence:[Presence jid:walter@example.net/alley available:1 " + "presence_show:[default] " + "priority:20 status:Looking for toes...]" + "" + "20" + "Looking for toes..." + "]" + "[IncomingPresenceChanged " + "presence:[Presence jid:donny@example.net/alley available:1 " + "presence_show:[default] priority:10 status:Throwing rocks]" + "" + "10" + "Throwing rocks" + "]"); + EXPECT_EQ(handler.OutputActivity(), ""); + handler.SessionActivity(); // Ignore the session output + + // Now look at the data structure we've built + EXPECT_EQ(roster->GetIncomingPresenceCount(), static_cast(4)); + EXPECT_EQ(roster->GetIncomingPresenceForJidCount(Jid("maude@example.net")), + static_cast(1)); + EXPECT_EQ(roster->GetIncomingPresenceForJidCount(Jid("walter@example.net")), + static_cast(2)); + + const XmppPresence * presence; + presence = roster->GetIncomingPresenceForJid(Jid("walter@example.net"), 1); + + std::stringstream dump; + WritePresence(dump, presence); + EXPECT_EQ(dump.str(), + "[Presence jid:walter@example.net/alley available:1 " + "presence_show:[default] priority:20 status:Looking for toes...]" + "" + "20" + "Looking for toes..." + ""); + dump.str(""); + + // Maude took off... + input = "" + "Stealing my rug back" + "-10" + ""; + TEST_OK(engine->HandleInput(input.c_str(), input.length())); + + EXPECT_EQ(roster_handler.StrClear(), + "[IncomingPresenceChanged " + "presence:[Presence jid:maude@example.net/studio available:0 " + "presence_show:[default] priority:-10 " + "status:Stealing my rug back]" + "" + "Stealing my rug back" + "-10" + "]"); + EXPECT_EQ(handler.OutputActivity(), ""); + handler.SessionActivity(); // Ignore the session output +} + +TEST_F(RosterModuleTest, TestPresenceSubscription) { + rtc::scoped_ptr engine(XmppEngine::Create()); + XmppTestHandler handler(engine.get()); + XmppTestRosterHandler roster_handler; + + rtc::scoped_ptr roster(XmppRosterModule::Create()); + roster->set_roster_handler(&roster_handler); + + // Configure the roster module + roster->RegisterEngine(engine.get()); + + // Set up callbacks + engine->SetOutputHandler(&handler); + engine->AddStanzaHandler(&handler); + engine->SetSessionHandler(&handler); + + // Set up minimal login info + engine->SetUser(Jid("david@my-server")); + // engine->SetPassword("david"); + + // Do the whole login handshake + RunLogin(this, engine.get(), &handler); + EXPECT_EQ("", handler.OutputActivity()); + + // Test incoming requests + std::string input; + input = + "" + "" + "" + ""; + TEST_OK(engine->HandleInput(input.c_str(), input.length())); + + EXPECT_EQ(roster_handler.StrClear(), + "[SubscriptionRequest Jid:maude@example.net type:subscribe]" + "" + "[SubscriptionRequest Jid:maude@example.net type:unsubscribe]" + "" + "[SubscriptionRequest Jid:maude@example.net type:subscribed]" + "" + "[SubscriptionRequest Jid:maude@example.net type:unsubscribe]" + ""); + EXPECT_EQ(handler.OutputActivity(), ""); + handler.SessionActivity(); // Ignore the session output + + TEST_OK(roster->RequestSubscription(Jid("maude@example.net"))); + TEST_OK(roster->CancelSubscription(Jid("maude@example.net"))); + TEST_OK(roster->ApproveSubscriber(Jid("maude@example.net"))); + TEST_OK(roster->CancelSubscriber(Jid("maude@example.net"))); + + EXPECT_EQ(roster_handler.StrClear(), ""); + EXPECT_EQ(handler.OutputActivity(), + "" + "" + "" + ""); + EXPECT_EQ(handler.SessionActivity(), ""); +} + +TEST_F(RosterModuleTest, TestRosterReceive) { + rtc::scoped_ptr engine(XmppEngine::Create()); + XmppTestHandler handler(engine.get()); + XmppTestRosterHandler roster_handler; + + rtc::scoped_ptr roster(XmppRosterModule::Create()); + roster->set_roster_handler(&roster_handler); + + // Configure the roster module + roster->RegisterEngine(engine.get()); + + // Set up callbacks + engine->SetOutputHandler(&handler); + engine->AddStanzaHandler(&handler); + engine->SetSessionHandler(&handler); + + // Set up minimal login info + engine->SetUser(Jid("david@my-server")); + // engine->SetPassword("david"); + + // Do the whole login handshake + RunLogin(this, engine.get(), &handler); + EXPECT_EQ("", handler.OutputActivity()); + + // Request a roster update + TEST_OK(roster->RequestRosterUpdate()); + + EXPECT_EQ(roster_handler.StrClear(),""); + EXPECT_EQ(handler.OutputActivity(), + "" + "" + ""); + EXPECT_EQ(handler.SessionActivity(), ""); + + // Prime the roster with a starting set + std::string input = + "" + "" + "" + "Business Partners" + "" + "" + "Friends" + "Bowling Team" + "Bowling League" + "" + "" + "Friends" + "Bowling Team" + "Bowling League" + "" + "" + "Business Partners" + "" + "" + "Bowling League" + "" + "" + ""; + + TEST_OK(engine->HandleInput(input.c_str(), input.length())); + + EXPECT_EQ(roster_handler.StrClear(), + "[ContactsAdded index:0 number:5 " + "0:[Contact jid:maude@example.net name:Maude Lebowski " + "subscription_state:none_asked " + "groups:[Business Partners]]" + "" + "Business Partners" + " " + "1:[Contact jid:walter@example.net name:Walter Sobchak " + "subscription_state:both " + "groups:[Friends, Bowling Team, Bowling League]]" + "" + "Friends" + "Bowling Team" + "Bowling League" + " " + "2:[Contact jid:donny@example.net name:Donny " + "subscription_state:both " + "groups:[Friends, Bowling Team, Bowling League]]" + "" + "Friends" + "Bowling Team" + "Bowling League" + " " + "3:[Contact jid:jeffrey@example.net name:The Big Lebowski " + "subscription_state:to " + "groups:[Business Partners]]" + "" + "Business Partners" + " " + "4:[Contact jid:jesus@example.net name:Jesus Quintana " + "subscription_state:from groups:[Bowling League]]" + "" + "Bowling League" + "]"); + EXPECT_EQ(handler.OutputActivity(), ""); + EXPECT_EQ(handler.SessionActivity(), ""); + + // Request that someone be added + rtc::scoped_ptr contact(XmppRosterContact::Create()); + TEST_OK(contact->set_jid(Jid("brandt@example.net"))); + TEST_OK(contact->set_name("Brandt")); + TEST_OK(contact->AddGroup("Business Partners")); + TEST_OK(contact->AddGroup("Watchers")); + TEST_OK(contact->AddGroup("Friends")); + TEST_OK(contact->RemoveGroup("Friends")); // Maybe not... + TEST_OK(roster->RequestRosterChange(contact.get())); + + EXPECT_EQ(roster_handler.StrClear(), ""); + EXPECT_EQ(handler.OutputActivity(), + "" + "" + "" + "Business Partners" + "Watchers" + "" + "" + ""); + EXPECT_EQ(handler.SessionActivity(), ""); + + // Get the push from the server + input = + "" + "" + "" + "" + "Business Partners" + "Watchers" + "" + "" + ""; + TEST_OK(engine->HandleInput(input.c_str(), input.length())); + + EXPECT_EQ(roster_handler.StrClear(), + "[ContactsAdded index:5 number:1 " + "5:[Contact jid:brandt@example.net name:Brandt " + "subscription_state:none " + "groups:[Business Partners, Watchers]]" + "" + "Business Partners" + "Watchers" + "]"); + EXPECT_EQ(handler.OutputActivity(), + ""); + EXPECT_EQ(handler.SessionActivity(), ""); + + // Get a contact update + input = + "" + "" + "" + "Friends" + "Bowling Team" + "Bowling League" + "Not wrong, just an..." + "" + "" + ""; + + TEST_OK(engine->HandleInput(input.c_str(), input.length())); + + EXPECT_EQ(roster_handler.StrClear(), + "[ContactChanged " + "old_contact:[Contact jid:walter@example.net name:Walter Sobchak " + "subscription_state:both " + "groups:[Friends, Bowling Team, Bowling League]]" + "" + "Friends" + "Bowling Team" + "Bowling League" + " " + "index:1 " + "new_contact:[Contact jid:walter@example.net name:Walter Sobchak " + "subscription_state:both " + "groups:[Friends, Bowling Team, Bowling League, " + "Not wrong, just an...]]" + "" + "Friends" + "Bowling Team" + "Bowling League" + "Not wrong, just an..." + "]"); + EXPECT_EQ(handler.OutputActivity(), + ""); + EXPECT_EQ(handler.SessionActivity(), ""); + + // Remove a contact + TEST_OK(roster->RequestRosterRemove(Jid("jesus@example.net"))); + + EXPECT_EQ(roster_handler.StrClear(), ""); + EXPECT_EQ(handler.OutputActivity(), + "" + "" + ""); + EXPECT_EQ(handler.SessionActivity(), ""); + + // Response from the server + input = + "" + "" + "" + "" + "" + "" + ""; + TEST_OK(engine->HandleInput(input.c_str(), input.length())); + + EXPECT_EQ(roster_handler.StrClear(), + "[ContactRemoved " + "old_contact:[Contact jid:jesus@example.net name:Jesus Quintana " + "subscription_state:from groups:[Bowling League]]" + "" + "Bowling League" + " index:4]"); + EXPECT_EQ(handler.OutputActivity(), + ""); + EXPECT_EQ(handler.SessionActivity(), ""); +} + +} diff --git a/webrtc/libjingle/xmpp/rostermoduleimpl.cc b/webrtc/libjingle/xmpp/rostermoduleimpl.cc new file mode 100644 index 000000000..b9752896e --- /dev/null +++ b/webrtc/libjingle/xmpp/rostermoduleimpl.cc @@ -0,0 +1,1064 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include +#include +#include +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/rostermoduleimpl.h" +#include "webrtc/base/common.h" +#include "webrtc/base/stringencode.h" + +namespace buzz { + +// enum prase and persist helpers ---------------------------------------------- +static bool +StringToPresenceShow(const std::string& input, XmppPresenceShow* show) { + // If this becomes a perf issue we can use a hash or a map here + if (STR_SHOW_AWAY == input) + *show = XMPP_PRESENCE_AWAY; + else if (STR_SHOW_DND == input) + *show = XMPP_PRESENCE_DND; + else if (STR_SHOW_XA == input) + *show = XMPP_PRESENCE_XA; + else if (STR_SHOW_CHAT == input) + *show = XMPP_PRESENCE_CHAT; + else if (STR_EMPTY == input) + *show = XMPP_PRESENCE_DEFAULT; + else + return false; + + return true; +} + +static bool +PresenceShowToString(XmppPresenceShow show, const char** output) { + switch(show) { + case XMPP_PRESENCE_AWAY: + *output = STR_SHOW_AWAY; + return true; + case XMPP_PRESENCE_CHAT: + *output = STR_SHOW_CHAT; + return true; + case XMPP_PRESENCE_XA: + *output = STR_SHOW_XA; + return true; + case XMPP_PRESENCE_DND: + *output = STR_SHOW_DND; + return true; + case XMPP_PRESENCE_DEFAULT: + *output = STR_EMPTY; + return true; + } + + *output = STR_EMPTY; + return false; +} + +static bool +StringToSubscriptionState(const std::string& subscription, + const std::string& ask, + XmppSubscriptionState* state) +{ + if (ask == "subscribe") + { + if (subscription == "none") { + *state = XMPP_SUBSCRIPTION_NONE_ASKED; + return true; + } + if (subscription == "from") { + *state = XMPP_SUBSCRIPTION_FROM_ASKED; + return true; + } + } else if (ask == STR_EMPTY) + { + if (subscription == "none") { + *state = XMPP_SUBSCRIPTION_NONE; + return true; + } + if (subscription == "from") { + *state = XMPP_SUBSCRIPTION_FROM; + return true; + } + if (subscription == "to") { + *state = XMPP_SUBSCRIPTION_TO; + return true; + } + if (subscription == "both") { + *state = XMPP_SUBSCRIPTION_BOTH; + return true; + } + } + + return false; +} + +static bool +StringToSubscriptionRequestType(const std::string& string, + XmppSubscriptionRequestType* type) +{ + if (string == "subscribe") + *type = XMPP_REQUEST_SUBSCRIBE; + else if (string == "unsubscribe") + *type = XMPP_REQUEST_UNSUBSCRIBE; + else if (string == "subscribed") + *type = XMPP_REQUEST_SUBSCRIBED; + else if (string == "unsubscribed") + *type = XMPP_REQUEST_UNSUBSCRIBED; + else + return false; + return true; +} + +// XmppPresenceImpl class ------------------------------------------------------ +XmppPresence* +XmppPresence::Create() { + return new XmppPresenceImpl(); +} + +XmppPresenceImpl::XmppPresenceImpl() { +} + +const Jid +XmppPresenceImpl::jid() const { + if (!raw_xml_) + return Jid(); + + return Jid(raw_xml_->Attr(QN_FROM)); +} + +XmppPresenceAvailable +XmppPresenceImpl::available() const { + if (!raw_xml_) + return XMPP_PRESENCE_UNAVAILABLE; + + if (raw_xml_->Attr(QN_TYPE) == "unavailable") + return XMPP_PRESENCE_UNAVAILABLE; + else if (raw_xml_->Attr(QN_TYPE) == "error") + return XMPP_PRESENCE_ERROR; + else + return XMPP_PRESENCE_AVAILABLE; +} + +XmppReturnStatus +XmppPresenceImpl::set_available(XmppPresenceAvailable available) { + if (!raw_xml_) + CreateRawXmlSkeleton(); + + if (available == XMPP_PRESENCE_AVAILABLE) + raw_xml_->ClearAttr(QN_TYPE); + else if (available == XMPP_PRESENCE_UNAVAILABLE) + raw_xml_->SetAttr(QN_TYPE, "unavailable"); + else if (available == XMPP_PRESENCE_ERROR) + raw_xml_->SetAttr(QN_TYPE, "error"); + return XMPP_RETURN_OK; +} + +XmppPresenceShow +XmppPresenceImpl::presence_show() const { + if (!raw_xml_) + return XMPP_PRESENCE_DEFAULT; + + XmppPresenceShow show = XMPP_PRESENCE_DEFAULT; + StringToPresenceShow(raw_xml_->TextNamed(QN_SHOW), &show); + return show; +} + +XmppReturnStatus +XmppPresenceImpl::set_presence_show(XmppPresenceShow show) { + if (!raw_xml_) + CreateRawXmlSkeleton(); + + const char* show_string; + + if(!PresenceShowToString(show, &show_string)) + return XMPP_RETURN_BADARGUMENT; + + raw_xml_->ClearNamedChildren(QN_SHOW); + + if (show!=XMPP_PRESENCE_DEFAULT) { + raw_xml_->AddElement(new XmlElement(QN_SHOW)); + raw_xml_->AddText(show_string, 1); + } + + return XMPP_RETURN_OK; +} + +int +XmppPresenceImpl::priority() const { + if (!raw_xml_) + return 0; + + int raw_priority = 0; + if (!rtc::FromString(raw_xml_->TextNamed(QN_PRIORITY), &raw_priority)) + raw_priority = 0; + if (raw_priority < -128) + raw_priority = -128; + if (raw_priority > 127) + raw_priority = 127; + + return raw_priority; +} + +XmppReturnStatus +XmppPresenceImpl::set_priority(int priority) { + if (!raw_xml_) + CreateRawXmlSkeleton(); + + if (priority < -128 || priority > 127) + return XMPP_RETURN_BADARGUMENT; + + raw_xml_->ClearNamedChildren(QN_PRIORITY); + if (0 != priority) { + std::string priority_string; + if (rtc::ToString(priority, &priority_string)) { + raw_xml_->AddElement(new XmlElement(QN_PRIORITY)); + raw_xml_->AddText(priority_string, 1); + } + } + + return XMPP_RETURN_OK; +} + +const std::string +XmppPresenceImpl::status() const { + if (!raw_xml_) + return STR_EMPTY; + + XmlElement* status_element; + XmlElement* element; + + // Search for a status element with no xml:lang attribute on it. if we can't + // find that then just return the first status element in the stanza. + for (status_element = element = raw_xml_->FirstNamed(QN_STATUS); + element; + element = element->NextNamed(QN_STATUS)) { + if (!element->HasAttr(QN_XML_LANG)) { + status_element = element; + break; + } + } + + if (status_element) { + return status_element->BodyText(); + } + + return STR_EMPTY; +} + +XmppReturnStatus +XmppPresenceImpl::set_status(const std::string& status) { + if (!raw_xml_) + CreateRawXmlSkeleton(); + + raw_xml_->ClearNamedChildren(QN_STATUS); + + if (status != STR_EMPTY) { + raw_xml_->AddElement(new XmlElement(QN_STATUS)); + raw_xml_->AddText(status, 1); + } + + return XMPP_RETURN_OK; +} + +XmppPresenceConnectionStatus +XmppPresenceImpl::connection_status() const { + if (!raw_xml_) + return XMPP_CONNECTION_STATUS_UNKNOWN; + + XmlElement* con = raw_xml_->FirstNamed(QN_GOOGLE_PSTN_CONFERENCE_STATUS); + if (con) { + std::string status = con->Attr(QN_ATTR_STATUS); + if (status == STR_PSTN_CONFERENCE_STATUS_CONNECTING) + return XMPP_CONNECTION_STATUS_CONNECTING; + else if (status == STR_PSTN_CONFERENCE_STATUS_CONNECTED) + return XMPP_CONNECTION_STATUS_CONNECTED; + else if (status == STR_PSTN_CONFERENCE_STATUS_JOINING) + return XMPP_CONNECTION_STATUS_JOINING; + else if (status == STR_PSTN_CONFERENCE_STATUS_HANGUP) + return XMPP_CONNECTION_STATUS_HANGUP; + } + + return XMPP_CONNECTION_STATUS_CONNECTED; +} + +const std::string +XmppPresenceImpl::google_user_id() const { + if (!raw_xml_) + return std::string(); + + XmlElement* muc_user_x = raw_xml_->FirstNamed(QN_MUC_USER_X); + if (muc_user_x) { + XmlElement* muc_user_item = muc_user_x->FirstNamed(QN_MUC_USER_ITEM); + if (muc_user_item) { + return muc_user_item->Attr(QN_GOOGLE_USER_ID); + } + } + + return std::string(); +} + +const std::string +XmppPresenceImpl::nickname() const { + if (!raw_xml_) + return std::string(); + + XmlElement* nickname = raw_xml_->FirstNamed(QN_NICKNAME); + if (nickname) { + return nickname->BodyText(); + } + + return std::string(); +} + +const XmlElement* +XmppPresenceImpl::raw_xml() const { + if (!raw_xml_) + const_cast(this)->CreateRawXmlSkeleton(); + return raw_xml_.get(); +} + +XmppReturnStatus +XmppPresenceImpl::set_raw_xml(const XmlElement * xml) { + if (!xml || + xml->Name() != QN_PRESENCE) + return XMPP_RETURN_BADARGUMENT; + + raw_xml_.reset(new XmlElement(*xml)); + return XMPP_RETURN_OK; +} + +void +XmppPresenceImpl::CreateRawXmlSkeleton() { + raw_xml_.reset(new XmlElement(QN_PRESENCE)); +} + +// XmppRosterContactImpl ------------------------------------------------------- +XmppRosterContact* +XmppRosterContact::Create() { + return new XmppRosterContactImpl(); +} + +XmppRosterContactImpl::XmppRosterContactImpl() { + ResetGroupCache(); +} + +void +XmppRosterContactImpl::SetXmlFromWire(const XmlElement* xml) { + ResetGroupCache(); + if (xml) + raw_xml_.reset(new XmlElement(*xml)); + else + raw_xml_.reset(NULL); +} + +void +XmppRosterContactImpl::ResetGroupCache() { + group_count_ = -1; + group_index_returned_ = -1; + group_returned_ = NULL; +} + +const Jid +XmppRosterContactImpl::jid() const { + return Jid(raw_xml_->Attr(QN_JID)); +} + +XmppReturnStatus +XmppRosterContactImpl::set_jid(const Jid& jid) +{ + if (!raw_xml_) + CreateRawXmlSkeleton(); + + if (!jid.IsValid()) + return XMPP_RETURN_BADARGUMENT; + + raw_xml_->SetAttr(QN_JID, jid.Str()); + + return XMPP_RETURN_OK; +} + +const std::string +XmppRosterContactImpl::name() const { + return raw_xml_->Attr(QN_NAME); +} + +XmppReturnStatus +XmppRosterContactImpl::set_name(const std::string& name) { + if (!raw_xml_) + CreateRawXmlSkeleton(); + + if (name == STR_EMPTY) + raw_xml_->ClearAttr(QN_NAME); + else + raw_xml_->SetAttr(QN_NAME, name); + + return XMPP_RETURN_OK; +} + +XmppSubscriptionState +XmppRosterContactImpl::subscription_state() const { + if (!raw_xml_) + return XMPP_SUBSCRIPTION_NONE; + + XmppSubscriptionState state = XMPP_SUBSCRIPTION_NONE; + + if (StringToSubscriptionState(raw_xml_->Attr(QN_SUBSCRIPTION), + raw_xml_->Attr(QN_ASK), + &state)) + return state; + + return XMPP_SUBSCRIPTION_NONE; +} + +size_t +XmppRosterContactImpl::GetGroupCount() const { + if (!raw_xml_) + return 0; + + if (-1 == group_count_) { + XmlElement *group_element = raw_xml_->FirstNamed(QN_ROSTER_GROUP); + int group_count = 0; + while(group_element) { + group_count++; + group_element = group_element->NextNamed(QN_ROSTER_GROUP); + } + + ASSERT(group_count > 0); // protect the cast + XmppRosterContactImpl * me = const_cast(this); + me->group_count_ = group_count; + } + + return group_count_; +} + +const std::string +XmppRosterContactImpl::GetGroup(size_t index) const { + if (index >= GetGroupCount()) + return STR_EMPTY; + + // We cache the last group index and element that we returned. This way + // going through the groups in order is order n and not n^2. This could be + // enhanced if necessary by starting at the cached value if the index asked + // is after the cached one. + if (group_index_returned_ >= 0 && + index == static_cast(group_index_returned_) + 1) + { + XmppRosterContactImpl * me = const_cast(this); + me->group_returned_ = group_returned_->NextNamed(QN_ROSTER_GROUP); + ASSERT(group_returned_ != NULL); + me->group_index_returned_++; + } else if (group_index_returned_ < 0 || + static_cast(group_index_returned_) != index) { + XmlElement * group_element = raw_xml_->FirstNamed(QN_ROSTER_GROUP); + size_t group_index = 0; + while(group_index < index) { + ASSERT(group_element != NULL); + group_index++; + group_element = group_element->NextNamed(QN_ROSTER_GROUP); + } + + XmppRosterContactImpl * me = const_cast(this); + me->group_index_returned_ = static_cast(group_index); + me->group_returned_ = group_element; + } + + return group_returned_->BodyText(); +} + +XmppReturnStatus +XmppRosterContactImpl::AddGroup(const std::string& group) { + if (group == STR_EMPTY) + return XMPP_RETURN_BADARGUMENT; + + if (!raw_xml_) + CreateRawXmlSkeleton(); + + if (FindGroup(group, NULL, NULL)) + return XMPP_RETURN_OK; + + raw_xml_->AddElement(new XmlElement(QN_ROSTER_GROUP)); + raw_xml_->AddText(group, 1); + ++group_count_; + + return XMPP_RETURN_OK; +} + +XmppReturnStatus +XmppRosterContactImpl::RemoveGroup(const std::string& group) { + if (group == STR_EMPTY) + return XMPP_RETURN_BADARGUMENT; + + if (!raw_xml_) + return XMPP_RETURN_OK; + + XmlChild * child_before; + if (FindGroup(group, NULL, &child_before)) { + raw_xml_->RemoveChildAfter(child_before); + ResetGroupCache(); + } + return XMPP_RETURN_OK; +} + +bool +XmppRosterContactImpl::FindGroup(const std::string& group, + XmlElement** element, + XmlChild** child_before) { + XmlChild * prev_child = NULL; + XmlChild * next_child; + XmlChild * child; + for (child = raw_xml_->FirstChild(); child; child = next_child) { + next_child = child->NextChild(); + if (!child->IsText() && + child->AsElement()->Name() == QN_ROSTER_GROUP && + child->AsElement()->BodyText() == group) { + if (element) + *element = child->AsElement(); + if (child_before) + *child_before = prev_child; + return true; + } + prev_child = child; + } + + return false; +} + +const XmlElement* +XmppRosterContactImpl::raw_xml() const { + if (!raw_xml_) + const_cast(this)->CreateRawXmlSkeleton(); + return raw_xml_.get(); +} + +XmppReturnStatus +XmppRosterContactImpl::set_raw_xml(const XmlElement* xml) { + if (!xml || + xml->Name() != QN_ROSTER_ITEM || + xml->HasAttr(QN_SUBSCRIPTION) || + xml->HasAttr(QN_ASK)) + return XMPP_RETURN_BADARGUMENT; + + ResetGroupCache(); + + raw_xml_.reset(new XmlElement(*xml)); + + return XMPP_RETURN_OK; +} + +void +XmppRosterContactImpl::CreateRawXmlSkeleton() { + raw_xml_.reset(new XmlElement(QN_ROSTER_ITEM)); +} + +// XmppRosterModuleImpl -------------------------------------------------------- +XmppRosterModule * +XmppRosterModule::Create() { + return new XmppRosterModuleImpl(); +} + +XmppRosterModuleImpl::XmppRosterModuleImpl() : + roster_handler_(NULL), + incoming_presence_map_(new JidPresenceVectorMap()), + incoming_presence_vector_(new PresenceVector()), + contacts_(new ContactVector()) { + +} + +XmppRosterModuleImpl::~XmppRosterModuleImpl() { + DeleteIncomingPresence(); + DeleteContacts(); +} + +XmppReturnStatus +XmppRosterModuleImpl::set_roster_handler(XmppRosterHandler * handler) { + roster_handler_ = handler; + return XMPP_RETURN_OK; +} + +XmppRosterHandler* +XmppRosterModuleImpl::roster_handler() { + return roster_handler_; +} + +XmppPresence* +XmppRosterModuleImpl::outgoing_presence() { + return &outgoing_presence_; +} + +XmppReturnStatus +XmppRosterModuleImpl::BroadcastPresence() { + // Scrub the outgoing presence + const XmlElement* element = outgoing_presence_.raw_xml(); + + ASSERT(!element->HasAttr(QN_TO) && + !element->HasAttr(QN_FROM) && + (element->Attr(QN_TYPE) == STR_EMPTY || + element->Attr(QN_TYPE) == "unavailable")); + + if (!engine()) + return XMPP_RETURN_BADSTATE; + + return engine()->SendStanza(element); +} + +XmppReturnStatus +XmppRosterModuleImpl::SendDirectedPresence(const XmppPresence* presence, + const Jid& to_jid) { + if (!presence) + return XMPP_RETURN_BADARGUMENT; + + if (!engine()) + return XMPP_RETURN_BADSTATE; + + XmlElement element(*(presence->raw_xml())); + + if (element.Name() != QN_PRESENCE || + element.HasAttr(QN_TO) || + element.HasAttr(QN_FROM)) + return XMPP_RETURN_BADARGUMENT; + + if (element.HasAttr(QN_TYPE)) { + if (element.Attr(QN_TYPE) != STR_EMPTY && + element.Attr(QN_TYPE) != "unavailable") { + return XMPP_RETURN_BADARGUMENT; + } + } + + element.SetAttr(QN_TO, to_jid.Str()); + + return engine()->SendStanza(&element); +} + +size_t +XmppRosterModuleImpl::GetIncomingPresenceCount() { + return incoming_presence_vector_->size(); +} + +const XmppPresence* +XmppRosterModuleImpl::GetIncomingPresence(size_t index) { + if (index >= incoming_presence_vector_->size()) + return NULL; + return (*incoming_presence_vector_)[index]; +} + +size_t +XmppRosterModuleImpl::GetIncomingPresenceForJidCount(const Jid& jid) +{ + // find the vector in the map + JidPresenceVectorMap::iterator pos; + pos = incoming_presence_map_->find(jid); + if (pos == incoming_presence_map_->end()) + return 0; + + ASSERT(pos->second != NULL); + + return pos->second->size(); +} + +const XmppPresence* +XmppRosterModuleImpl::GetIncomingPresenceForJid(const Jid& jid, + size_t index) { + JidPresenceVectorMap::iterator pos; + pos = incoming_presence_map_->find(jid); + if (pos == incoming_presence_map_->end()) + return NULL; + + ASSERT(pos->second != NULL); + + if (index >= pos->second->size()) + return NULL; + + return (*pos->second)[index]; +} + +XmppReturnStatus +XmppRosterModuleImpl::RequestRosterUpdate() { + if (!engine()) + return XMPP_RETURN_BADSTATE; + + XmlElement roster_get(QN_IQ); + roster_get.AddAttr(QN_TYPE, "get"); + roster_get.AddAttr(QN_ID, engine()->NextId()); + roster_get.AddElement(new XmlElement(QN_ROSTER_QUERY, true)); + return engine()->SendIq(&roster_get, this, NULL); +} + +size_t +XmppRosterModuleImpl::GetRosterContactCount() { + return contacts_->size(); +} + +const XmppRosterContact* +XmppRosterModuleImpl::GetRosterContact(size_t index) { + if (index >= contacts_->size()) + return NULL; + return (*contacts_)[index]; +} + +class RosterPredicate { +public: + explicit RosterPredicate(const Jid& jid) : jid_(jid) { + } + + bool operator() (XmppRosterContactImpl *& contact) { + return contact->jid() == jid_; + } + +private: + Jid jid_; +}; + +const XmppRosterContact* +XmppRosterModuleImpl::FindRosterContact(const Jid& jid) { + ContactVector::iterator pos; + + pos = std::find_if(contacts_->begin(), + contacts_->end(), + RosterPredicate(jid)); + if (pos == contacts_->end()) + return NULL; + + return *pos; +} + +XmppReturnStatus +XmppRosterModuleImpl::RequestRosterChange( + const XmppRosterContact* contact) { + if (!contact) + return XMPP_RETURN_BADARGUMENT; + + Jid jid = contact->jid(); + + if (!jid.IsValid()) + return XMPP_RETURN_BADARGUMENT; + + if (!engine()) + return XMPP_RETURN_BADSTATE; + + const XmlElement* contact_xml = contact->raw_xml(); + if (contact_xml->Name() != QN_ROSTER_ITEM || + contact_xml->HasAttr(QN_SUBSCRIPTION) || + contact_xml->HasAttr(QN_ASK)) + return XMPP_RETURN_BADARGUMENT; + + XmlElement roster_add(QN_IQ); + roster_add.AddAttr(QN_TYPE, "set"); + roster_add.AddAttr(QN_ID, engine()->NextId()); + roster_add.AddElement(new XmlElement(QN_ROSTER_QUERY, true)); + roster_add.AddElement(new XmlElement(*contact_xml), 1); + + return engine()->SendIq(&roster_add, this, NULL); +} + +XmppReturnStatus +XmppRosterModuleImpl::RequestRosterRemove(const Jid& jid) { + if (!jid.IsValid()) + return XMPP_RETURN_BADARGUMENT; + + if (!engine()) + return XMPP_RETURN_BADSTATE; + + XmlElement roster_add(QN_IQ); + roster_add.AddAttr(QN_TYPE, "set"); + roster_add.AddAttr(QN_ID, engine()->NextId()); + roster_add.AddElement(new XmlElement(QN_ROSTER_QUERY, true)); + roster_add.AddAttr(QN_JID, jid.Str(), 1); + roster_add.AddAttr(QN_SUBSCRIPTION, "remove", 1); + + return engine()->SendIq(&roster_add, this, NULL); +} + +XmppReturnStatus +XmppRosterModuleImpl::RequestSubscription(const Jid& jid) { + return SendSubscriptionRequest(jid, "subscribe"); +} + +XmppReturnStatus +XmppRosterModuleImpl::CancelSubscription(const Jid& jid) { + return SendSubscriptionRequest(jid, "unsubscribe"); +} + +XmppReturnStatus +XmppRosterModuleImpl::ApproveSubscriber(const Jid& jid) { + return SendSubscriptionRequest(jid, "subscribed"); +} + +XmppReturnStatus +XmppRosterModuleImpl::CancelSubscriber(const Jid& jid) { + return SendSubscriptionRequest(jid, "unsubscribed"); +} + +void +XmppRosterModuleImpl::IqResponse(XmppIqCookie, const XmlElement * stanza) { + // The only real Iq response that we expect to recieve are initial roster + // population + if (stanza->Attr(QN_TYPE) == "error") + { + if (roster_handler_) + roster_handler_->RosterError(this, stanza); + + return; + } + + ASSERT(stanza->Attr(QN_TYPE) == "result"); + + InternalRosterItems(stanza); +} + +bool +XmppRosterModuleImpl::HandleStanza(const XmlElement * stanza) +{ + ASSERT(engine() != NULL); + + // There are two types of stanzas that we care about: presence and roster push + // Iqs + if (stanza->Name() == QN_PRESENCE) { + const std::string& jid_string = stanza->Attr(QN_FROM); + Jid jid(jid_string); + + if (!jid.IsValid()) + return false; // if the Jid isn't valid, don't process + + const std::string& type = stanza->Attr(QN_TYPE); + XmppSubscriptionRequestType request_type; + if (StringToSubscriptionRequestType(type, &request_type)) + InternalSubscriptionRequest(jid, stanza, request_type); + else if (type == "unavailable" || type == STR_EMPTY) + InternalIncomingPresence(jid, stanza); + else if (type == "error") + InternalIncomingPresenceError(jid, stanza); + else + return false; + + return true; + } else if (stanza->Name() == QN_IQ) { + const XmlElement * roster_query = stanza->FirstNamed(QN_ROSTER_QUERY); + if (!roster_query || stanza->Attr(QN_TYPE) != "set") + return false; + + InternalRosterItems(stanza); + + // respond to the IQ + XmlElement result(QN_IQ); + result.AddAttr(QN_TYPE, "result"); + result.AddAttr(QN_TO, stanza->Attr(QN_FROM)); + result.AddAttr(QN_ID, stanza->Attr(QN_ID)); + + engine()->SendStanza(&result); + return true; + } + + return false; +} + +void +XmppRosterModuleImpl::DeleteIncomingPresence() { + // Clear out the vector of all presence notifications + { + PresenceVector::iterator pos; + for (pos = incoming_presence_vector_->begin(); + pos < incoming_presence_vector_->end(); + ++pos) { + XmppPresenceImpl * presence = *pos; + *pos = NULL; + delete presence; + } + incoming_presence_vector_->clear(); + } + + // Clear out all of the small presence vectors per Jid + { + JidPresenceVectorMap::iterator pos; + for (pos = incoming_presence_map_->begin(); + pos != incoming_presence_map_->end(); + ++pos) { + PresenceVector* presence_vector = pos->second; + pos->second = NULL; + delete presence_vector; + } + incoming_presence_map_->clear(); + } +} + +void +XmppRosterModuleImpl::DeleteContacts() { + ContactVector::iterator pos; + for (pos = contacts_->begin(); + pos < contacts_->end(); + ++pos) { + XmppRosterContact* contact = *pos; + *pos = NULL; + delete contact; + } + contacts_->clear(); +} + +XmppReturnStatus +XmppRosterModuleImpl::SendSubscriptionRequest(const Jid& jid, + const std::string& type) { + if (!jid.IsValid()) + return XMPP_RETURN_BADARGUMENT; + + if (!engine()) + return XMPP_RETURN_BADSTATE; + + XmlElement presence_request(QN_PRESENCE); + presence_request.AddAttr(QN_TO, jid.Str()); + presence_request.AddAttr(QN_TYPE, type); + + return engine()->SendStanza(&presence_request); +} + + +void +XmppRosterModuleImpl::InternalSubscriptionRequest(const Jid& jid, + const XmlElement* stanza, + XmppSubscriptionRequestType + request_type) { + if (roster_handler_) + roster_handler_->SubscriptionRequest(this, jid, request_type, stanza); +} + +class PresencePredicate { +public: + explicit PresencePredicate(const Jid& jid) : jid_(jid) { + } + + bool operator() (XmppPresenceImpl *& contact) { + return contact->jid() == jid_; + } + +private: + Jid jid_; +}; + +void +XmppRosterModuleImpl::InternalIncomingPresence(const Jid& jid, + const XmlElement* stanza) { + bool added = false; + Jid bare_jid = jid.BareJid(); + + // First add the presence to the map + JidPresenceVectorMap::iterator pos; + pos = incoming_presence_map_->find(jid.BareJid()); + if (pos == incoming_presence_map_->end()) { + // Insert a new entry into the map. Get the position of this new entry + pos = (incoming_presence_map_->insert( + std::make_pair(bare_jid, new PresenceVector()))).first; + } + + PresenceVector * presence_vector = pos->second; + ASSERT(presence_vector != NULL); + + // Try to find this jid in the bare jid bucket + PresenceVector::iterator presence_pos; + XmppPresenceImpl* presence; + presence_pos = std::find_if(presence_vector->begin(), + presence_vector->end(), + PresencePredicate(jid)); + + // Update/add it to the bucket + if (presence_pos == presence_vector->end()) { + presence = new XmppPresenceImpl(); + if (XMPP_RETURN_OK == presence->set_raw_xml(stanza)) { + added = true; + presence_vector->push_back(presence); + } else { + delete presence; + presence = NULL; + } + } else { + presence = *presence_pos; + presence->set_raw_xml(stanza); + } + + // now add to the comprehensive vector + if (added) + incoming_presence_vector_->push_back(presence); + + // Call back to the user with the changed presence information + if (roster_handler_) + roster_handler_->IncomingPresenceChanged(this, presence); +} + + +void +XmppRosterModuleImpl::InternalIncomingPresenceError(const Jid& jid, + const XmlElement* stanza) { + if (roster_handler_) + roster_handler_->SubscriptionError(this, jid, stanza); +} + +void +XmppRosterModuleImpl::InternalRosterItems(const XmlElement* stanza) { + const XmlElement* result_data = stanza->FirstNamed(QN_ROSTER_QUERY); + if (!result_data) + return; // unknown stuff in result! + + bool all_new = contacts_->empty(); + + for (const XmlElement* roster_item = result_data->FirstNamed(QN_ROSTER_ITEM); + roster_item; + roster_item = roster_item->NextNamed(QN_ROSTER_ITEM)) + { + const std::string& jid_string = roster_item->Attr(QN_JID); + Jid jid(jid_string); + if (!jid.IsValid()) + continue; + + // This algorithm is N^2 on the number of incoming contacts after the + // initial load. There is no way to do this faster without allowing + // duplicates, introducing more data structures or write a custom data + // structure. We'll see if this becomes a perf problem and fix it if it + // does. + ContactVector::iterator pos = contacts_->end(); + + if (!all_new) { + pos = std::find_if(contacts_->begin(), + contacts_->end(), + RosterPredicate(jid)); + } + + if (pos != contacts_->end()) { // Update/remove a current contact + if (roster_item->Attr(QN_SUBSCRIPTION) == "remove") { + XmppRosterContact* contact = *pos; + contacts_->erase(pos); + if (roster_handler_) + roster_handler_->ContactRemoved(this, contact, + std::distance(contacts_->begin(), pos)); + delete contact; + } else { + XmppRosterContact* old_contact = *pos; + *pos = new XmppRosterContactImpl(); + (*pos)->SetXmlFromWire(roster_item); + if (roster_handler_) + roster_handler_->ContactChanged(this, old_contact, + std::distance(contacts_->begin(), pos)); + delete old_contact; + } + } else { // Add a new contact + XmppRosterContactImpl* contact = new XmppRosterContactImpl(); + contact->SetXmlFromWire(roster_item); + contacts_->push_back(contact); + if (roster_handler_ && !all_new) + roster_handler_->ContactsAdded(this, contacts_->size() - 1, 1); + } + } + + // Send a consolidated update if all contacts are new + if (roster_handler_ && all_new) + roster_handler_->ContactsAdded(this, 0, contacts_->size()); +} + +} diff --git a/webrtc/libjingle/xmpp/rostermoduleimpl.h b/webrtc/libjingle/xmpp/rostermoduleimpl.h new file mode 100644 index 000000000..6e3bd91c8 --- /dev/null +++ b/webrtc/libjingle/xmpp/rostermoduleimpl.h @@ -0,0 +1,285 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPTHREAD_H_ +#define WEBRTC_LIBJINGLE_XMPP_XMPPTHREAD_H_ + +#include "webrtc/libjingle/xmpp/moduleimpl.h" +#include "webrtc/libjingle/xmpp/rostermodule.h" + +namespace buzz { + +//! Presence Information +//! This class stores both presence information for outgoing presence and is +//! returned by methods in XmppRosterModule to represent received incoming +//! presence information. When this class is writeable (non-const) then each +//! update to any property will set the inner xml. Setting the raw_xml will +//! rederive all of the other properties. +class XmppPresenceImpl : public XmppPresence { +public: + virtual ~XmppPresenceImpl() {} + + //! The from Jid of for the presence information. + //! Typically this will be a full Jid with resource specified. For outgoing + //! presence this should remain JID_NULL and will be scrubbed from the + //! stanza when being sent. + virtual const Jid jid() const; + + //! Is the contact available? + virtual XmppPresenceAvailable available() const; + + //! Sets if the user is available or not + virtual XmppReturnStatus set_available(XmppPresenceAvailable available); + + //! The show value of the presence info + virtual XmppPresenceShow presence_show() const; + + //! Set the presence show value + virtual XmppReturnStatus set_presence_show(XmppPresenceShow show); + + //! The Priority of the presence info + virtual int priority() const; + + //! Set the priority of the presence + virtual XmppReturnStatus set_priority(int priority); + + //! The plain text status of the presence info. + //! If there are multiple status because of language, this will either be a + //! status that is not tagged for language or the first available + virtual const std::string status() const; + + //! Sets the status for the presence info. + //! If there is more than one status present already then this will remove + //! them all and replace it with one status element we no specified language + virtual XmppReturnStatus set_status(const std::string& status); + + //! The connection status + virtual XmppPresenceConnectionStatus connection_status() const; + + //! The focus obfuscated GAIA id + virtual const std::string google_user_id() const; + + //! The nickname in the presence + virtual const std::string nickname() const; + + //! The raw xml of the presence update + virtual const XmlElement* raw_xml() const; + + //! Sets the raw presence stanza for the presence update + //! This will cause all other data items in this structure to be rederived + virtual XmppReturnStatus set_raw_xml(const XmlElement * xml); + +private: + XmppPresenceImpl(); + + friend class XmppPresence; + friend class XmppRosterModuleImpl; + + void CreateRawXmlSkeleton(); + + // Store everything in the XML element. If this becomes a perf issue we can + // cache the data. + rtc::scoped_ptr raw_xml_; +}; + +//! A contact as given by the server +class XmppRosterContactImpl : public XmppRosterContact { +public: + virtual ~XmppRosterContactImpl() {} + + //! The jid for the contact. + //! Typically this will be a bare Jid. + virtual const Jid jid() const; + + //! Sets the jid for the roster contact update + virtual XmppReturnStatus set_jid(const Jid& jid); + + //! The name (nickname) stored for this contact + virtual const std::string name() const; + + //! Sets the name + virtual XmppReturnStatus set_name(const std::string& name); + + //! The Presence subscription state stored on the server for this contact + //! This is never settable and will be ignored when generating a roster + //! add/update request + virtual XmppSubscriptionState subscription_state() const; + + //! The number of Groups applied to this contact + virtual size_t GetGroupCount() const; + + //! Gets a Group applied to the contact based on index. + virtual const std::string GetGroup(size_t index) const; + + //! Adds a group to this contact. + //! This will return a no error if the group is already present. + virtual XmppReturnStatus AddGroup(const std::string& group); + + //! Removes a group from the contact. + //! This will return no error if the group isn't there + virtual XmppReturnStatus RemoveGroup(const std::string& group); + + //! The raw xml for this roster contact + virtual const XmlElement* raw_xml() const; + + //! Sets the raw presence stanza for the presence update + //! This will cause all other data items in this structure to be rederived + virtual XmppReturnStatus set_raw_xml(const XmlElement * xml); + +private: + XmppRosterContactImpl(); + + void CreateRawXmlSkeleton(); + void SetXmlFromWire(const XmlElement * xml); + void ResetGroupCache(); + + bool FindGroup(const std::string& group, + XmlElement** element, + XmlChild** child_before); + + + friend class XmppRosterContact; + friend class XmppRosterModuleImpl; + + int group_count_; + int group_index_returned_; + XmlElement * group_returned_; + rtc::scoped_ptr raw_xml_; +}; + +//! An XmppModule for handle roster and presence functionality +class XmppRosterModuleImpl : public XmppModuleImpl, + public XmppRosterModule, public XmppIqHandler { +public: + virtual ~XmppRosterModuleImpl(); + + IMPLEMENT_XMPPMODULE + + //! Sets the roster handler (callbacks) for the module + virtual XmppReturnStatus set_roster_handler(XmppRosterHandler * handler); + + //! Gets the roster handler for the module + virtual XmppRosterHandler* roster_handler(); + + // USER PRESENCE STATE ------------------------------------------------------- + + //! Gets the aggregate outgoing presence + //! This object is non-const and be edited directly. No update is sent + //! to the server until a Broadcast is sent + virtual XmppPresence* outgoing_presence(); + + //! Broadcasts that the user is available. + //! Nothing with respect to presence is sent until this is called. + virtual XmppReturnStatus BroadcastPresence(); + + //! Sends a directed presence to a Jid + //! Note that the client doesn't store where directed presence notifications + //! have been sent. The server can keep the appropriate state + virtual XmppReturnStatus SendDirectedPresence(const XmppPresence* presence, + const Jid& to_jid); + + // INCOMING PRESENCE STATUS -------------------------------------------------- + + //! Returns the number of incoming presence data recorded + virtual size_t GetIncomingPresenceCount(); + + //! Returns an incoming presence datum based on index + virtual const XmppPresence* GetIncomingPresence(size_t index); + + //! Gets the number of presence data for a bare Jid + //! There may be a datum per resource + virtual size_t GetIncomingPresenceForJidCount(const Jid& jid); + + //! Returns a single presence data for a Jid based on index + virtual const XmppPresence* GetIncomingPresenceForJid(const Jid& jid, + size_t index); + + // ROSTER MANAGEMENT --------------------------------------------------------- + + //! Requests an update of the roster from the server + //! This must be called to initialize the client side cache of the roster + //! After this is sent the server should keep this module apprised of any + //! changes. + virtual XmppReturnStatus RequestRosterUpdate(); + + //! Returns the number of contacts in the roster + virtual size_t GetRosterContactCount(); + + //! Returns a contact by index + virtual const XmppRosterContact* GetRosterContact(size_t index); + + //! Finds a contact by Jid + virtual const XmppRosterContact* FindRosterContact(const Jid& jid); + + //! Send a request to the server to add a contact + //! Note that the contact won't show up in the roster until the server can + //! respond. This happens async when the socket is being serviced + virtual XmppReturnStatus RequestRosterChange( + const XmppRosterContact* contact); + + //! Request that the server remove a contact + //! The jabber protocol specifies that the server should also cancel any + //! subscriptions when this is done. Like adding, this contact won't be + //! removed until the server responds. + virtual XmppReturnStatus RequestRosterRemove(const Jid& jid); + + // SUBSCRIPTION MANAGEMENT --------------------------------------------------- + + //! Request a subscription to presence notifications form a Jid + virtual XmppReturnStatus RequestSubscription(const Jid& jid); + + //! Cancel a subscription to presence notifications from a Jid + virtual XmppReturnStatus CancelSubscription(const Jid& jid); + + //! Approve a request to deliver presence notifications to a jid + virtual XmppReturnStatus ApproveSubscriber(const Jid& jid); + + //! Deny or cancel presence notification deliver to a jid + virtual XmppReturnStatus CancelSubscriber(const Jid& jid); + + // XmppIqHandler IMPLEMENTATION ---------------------------------------------- + virtual void IqResponse(XmppIqCookie cookie, const XmlElement * stanza); + +protected: + // XmppModuleImpl OVERRIDES -------------------------------------------------- + virtual bool HandleStanza(const XmlElement *); + + // PRIVATE DATA -------------------------------------------------------------- +private: + friend class XmppRosterModule; + XmppRosterModuleImpl(); + + // Helper functions + void DeleteIncomingPresence(); + void DeleteContacts(); + XmppReturnStatus SendSubscriptionRequest(const Jid& jid, + const std::string& type); + void InternalSubscriptionRequest(const Jid& jid, const XmlElement* stanza, + XmppSubscriptionRequestType request_type); + void InternalIncomingPresence(const Jid& jid, const XmlElement* stanza); + void InternalIncomingPresenceError(const Jid& jid, const XmlElement* stanza); + void InternalRosterItems(const XmlElement* stanza); + + // Member data + XmppPresenceImpl outgoing_presence_; + XmppRosterHandler* roster_handler_; + + typedef std::vector PresenceVector; + typedef std::map JidPresenceVectorMap; + rtc::scoped_ptr incoming_presence_map_; + rtc::scoped_ptr incoming_presence_vector_; + + typedef std::vector ContactVector; + rtc::scoped_ptr contacts_; +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_XMPPTHREAD_H_ diff --git a/webrtc/libjingle/xmpp/saslcookiemechanism.h b/webrtc/libjingle/xmpp/saslcookiemechanism.h new file mode 100644 index 000000000..7a912466a --- /dev/null +++ b/webrtc/libjingle/xmpp/saslcookiemechanism.h @@ -0,0 +1,69 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_SASLCOOKIEMECHANISM_H_ +#define WEBRTC_LIBJINGLE_XMPP_SASLCOOKIEMECHANISM_H_ + +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/saslmechanism.h" + +namespace buzz { + +class SaslCookieMechanism : public SaslMechanism { + +public: + SaslCookieMechanism(const std::string & mechanism, + const std::string & username, + const std::string & cookie, + const std::string & token_service) + : mechanism_(mechanism), + username_(username), + cookie_(cookie), + token_service_(token_service) {} + + SaslCookieMechanism(const std::string & mechanism, + const std::string & username, + const std::string & cookie) + : mechanism_(mechanism), + username_(username), + cookie_(cookie), + token_service_("") {} + + 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_); + if (!token_service_.empty()) { + el->AddAttr(QN_GOOGLE_AUTH_SERVICE, token_service_); + } + + 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_; + std::string token_service_; +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_SASLCOOKIEMECHANISM_H_ diff --git a/webrtc/libjingle/xmpp/saslhandler.h b/webrtc/libjingle/xmpp/saslhandler.h new file mode 100644 index 000000000..f2e384440 --- /dev/null +++ b/webrtc/libjingle/xmpp/saslhandler.h @@ -0,0 +1,42 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_SASLHANDLER_H_ +#define WEBRTC_LIBJINGLE_XMPP_SASLHANDLER_H_ + +#include +#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 // WEBRTC_LIBJINGLE_XMPP_SASLHANDLER_H_ diff --git a/webrtc/libjingle/xmpp/saslmechanism.cc b/webrtc/libjingle/xmpp/saslmechanism.cc new file mode 100644 index 000000000..b4d6e9baa --- /dev/null +++ b/webrtc/libjingle/xmpp/saslmechanism.cc @@ -0,0 +1,55 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/saslmechanism.h" +#include "webrtc/base/base64.h" + +using rtc::Base64; + +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, Base64::DO_LAX); +} + +std::string +SaslMechanism::Base64EncodeFromArray(const char * plain, size_t length) { + std::string result; + Base64::EncodeFromArray(plain, length, &result); + return result; +} + +} diff --git a/webrtc/libjingle/xmpp/saslmechanism.h b/webrtc/libjingle/xmpp/saslmechanism.h new file mode 100644 index 000000000..9c392e566 --- /dev/null +++ b/webrtc/libjingle/xmpp/saslmechanism.h @@ -0,0 +1,57 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_SASLMECHANISM_H_ +#define WEBRTC_LIBJINGLE_XMPP_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 // WEBRTC_LIBJINGLE_XMPP_SASLMECHANISM_H_ diff --git a/webrtc/libjingle/xmpp/saslplainmechanism.h b/webrtc/libjingle/xmpp/saslplainmechanism.h new file mode 100644 index 000000000..8e162e25b --- /dev/null +++ b/webrtc/libjingle/xmpp/saslplainmechanism.h @@ -0,0 +1,48 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_SASLPLAINMECHANISM_H_ +#define WEBRTC_LIBJINGLE_XMPP_SASLPLAINMECHANISM_H_ + +#include "webrtc/libjingle/xmpp/saslmechanism.h" +#include "webrtc/base/cryptstring.h" + +namespace buzz { + +class SaslPlainMechanism : public SaslMechanism { + +public: + SaslPlainMechanism(const buzz::Jid user_jid, const rtc::CryptString & 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"); + + rtc::FormatCryptString credential; + credential.Append("\0", 1); + credential.Append(user_jid_.node()); + credential.Append("\0", 1); + credential.Append(&password_); + el->AddText(Base64EncodeFromArray(credential.GetData(), credential.GetLength())); + return el; + } + +private: + Jid user_jid_; + rtc::CryptString password_; +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_SASLPLAINMECHANISM_H_ diff --git a/webrtc/libjingle/xmpp/util_unittest.cc b/webrtc/libjingle/xmpp/util_unittest.cc new file mode 100644 index 000000000..330ab48ef --- /dev/null +++ b/webrtc/libjingle/xmpp/util_unittest.cc @@ -0,0 +1,109 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/util_unittest.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/base/gunit.h" + +namespace buzz { + +void XmppTestHandler::WriteOutput(const char * bytes, size_t len) { + output_ << std::string(bytes, len); +} + +void XmppTestHandler::StartTls(const std::string & cname) { + output_ << "[START-TLS " << cname << "]"; +} + +void XmppTestHandler::CloseConnection() { + output_ << "[CLOSED]"; +} + +void XmppTestHandler::OnStateChange(int state) { + switch (static_cast(state)) { + case XmppEngine::STATE_START: + session_ << "[START]"; + break; + case XmppEngine::STATE_OPENING: + session_ << "[OPENING]"; + break; + case XmppEngine::STATE_OPEN: + session_ << "[OPEN]"; + break; + case XmppEngine::STATE_CLOSED: + session_ << "[CLOSED]"; + switch (engine_->GetError(NULL)) { + case XmppEngine::ERROR_NONE: + // do nothing + break; + case XmppEngine::ERROR_XML: + session_ << "[ERROR-XML]"; + break; + case XmppEngine::ERROR_STREAM: + session_ << "[ERROR-STREAM]"; + break; + case XmppEngine::ERROR_VERSION: + session_ << "[ERROR-VERSION]"; + break; + case XmppEngine::ERROR_UNAUTHORIZED: + session_ << "[ERROR-UNAUTHORIZED]"; + break; + case XmppEngine::ERROR_TLS: + session_ << "[ERROR-TLS]"; + break; + case XmppEngine::ERROR_AUTH: + session_ << "[ERROR-AUTH]"; + break; + case XmppEngine::ERROR_BIND: + session_ << "[ERROR-BIND]"; + break; + case XmppEngine::ERROR_CONNECTION_CLOSED: + session_ << "[ERROR-CONNECTION-CLOSED]"; + break; + case XmppEngine::ERROR_DOCUMENT_CLOSED: + session_ << "[ERROR-DOCUMENT-CLOSED]"; + break; + default: + break; + } + break; + default: + break; + } +} + +bool XmppTestHandler::HandleStanza(const XmlElement * stanza) { + stanza_ << stanza->Str(); + return true; +} + +std::string XmppTestHandler::OutputActivity() { + std::string result = output_.str(); + output_.str(""); + return result; +} + +std::string XmppTestHandler::SessionActivity() { + std::string result = session_.str(); + session_.str(""); + return result; +} + +std::string XmppTestHandler::StanzaActivity() { + std::string result = stanza_.str(); + stanza_.str(""); + return result; +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/util_unittest.h b/webrtc/libjingle/xmpp/util_unittest.h new file mode 100644 index 000000000..38009fb4e --- /dev/null +++ b/webrtc/libjingle/xmpp/util_unittest.h @@ -0,0 +1,58 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_UTIL_UNITTEST_H_ +#define WEBRTC_LIBJINGLE_XMPP_UTIL_UNITTEST_H_ + +#include +#include +#include "webrtc/libjingle/xmpp/xmppengine.h" + +namespace buzz { + +// This class captures callbacks from engine. +class XmppTestHandler : public XmppOutputHandler, public XmppSessionHandler, + public XmppStanzaHandler { + public: + explicit XmppTestHandler(XmppEngine* engine) : engine_(engine) {} + virtual ~XmppTestHandler() {} + + void SetEngine(XmppEngine* engine); + + // Output handler + virtual void WriteOutput(const char * bytes, size_t len); + virtual void StartTls(const std::string & cname); + virtual void CloseConnection(); + + // Session handler + virtual void OnStateChange(int state); + + // Stanza handler + virtual bool HandleStanza(const XmlElement* stanza); + + std::string OutputActivity(); + std::string SessionActivity(); + std::string StanzaActivity(); + + private: + XmppEngine* engine_; + std::stringstream output_; + std::stringstream session_; + std::stringstream stanza_; +}; + +} // namespace buzz + +inline std::ostream& operator<<(std::ostream& os, const buzz::Jid& jid) { + os << jid.Str(); + return os; +} + +#endif // WEBRTC_LIBJINGLE_XMPP_UTIL_UNITTEST_H_ diff --git a/webrtc/libjingle/xmpp/xmpp.gyp b/webrtc/libjingle/xmpp/xmpp.gyp new file mode 100644 index 000000000..35755d688 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmpp.gyp @@ -0,0 +1,141 @@ +# Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. + +{ + 'includes': [ '../../build/common.gypi', ], + 'targets': [ + { + 'target_name': 'rtc_xmpp', + 'type': 'static_library', + 'dependencies': [ + '<(webrtc_root)/base/base.gyp:webrtc_base', + '<(webrtc_root)/libjingle/xmllite/xmllite.gyp:rtc_xmllite', + '<(DEPTH)/third_party/expat/expat.gyp:expat', + ], + 'defines': [ + 'FEATURE_ENABLE_SSL', + ], + 'cflags_cc!': [ + '-Wnon-virtual-dtor', + ], + 'export_dependent_settings': [ + '<(DEPTH)/third_party/expat/expat.gyp:expat', + ], + 'sources': [ + 'asyncsocket.h', + 'chatroommodule.h', + 'chatroommoduleimpl.cc', + 'constants.cc', + 'constants.h', + 'discoitemsquerytask.cc', + 'discoitemsquerytask.h', + 'hangoutpubsubclient.cc', + 'hangoutpubsubclient.h', + 'iqtask.cc', + 'iqtask.h', + 'jid.cc', + 'jid.h', + 'module.h', + 'moduleimpl.cc', + 'moduleimpl.h', + 'mucroomconfigtask.cc', + 'mucroomconfigtask.h', + 'mucroomdiscoverytask.cc', + 'mucroomdiscoverytask.h', + 'mucroomlookuptask.cc', + 'mucroomlookuptask.h', + 'mucroomuniquehangoutidtask.cc', + 'mucroomuniquehangoutidtask.h', + 'pingtask.cc', + 'pingtask.h', + 'plainsaslhandler.h', + 'presenceouttask.cc', + 'presenceouttask.h', + 'presencereceivetask.cc', + 'presencereceivetask.h', + 'presencestatus.cc', + 'presencestatus.h', + 'prexmppauth.h', + 'pubsub_task.cc', + 'pubsub_task.h', + 'pubsubclient.cc', + 'pubsubclient.h', + 'pubsubstateclient.cc', + 'pubsubstateclient.h', + 'pubsubtasks.cc', + 'pubsubtasks.h', + 'receivetask.cc', + 'receivetask.h', + 'rostermodule.h', + 'rostermoduleimpl.cc', + 'rostermoduleimpl.h', + 'saslcookiemechanism.h', + 'saslhandler.h', + 'saslmechanism.cc', + 'saslmechanism.h', + 'saslplainmechanism.h', + 'xmppauth.cc', + 'xmppauth.h', + 'xmppclient.cc', + 'xmppclient.h', + 'xmppclientsettings.h', + 'xmppengine.h', + 'xmppengineimpl.cc', + 'xmppengineimpl.h', + 'xmppengineimpl_iq.cc', + 'xmpplogintask.cc', + 'xmpplogintask.h', + 'xmpppump.cc', + 'xmpppump.h', + 'xmppsocket.cc', + 'xmppsocket.h', + 'xmppstanzaparser.cc', + 'xmppstanzaparser.h', + 'xmpptask.cc', + 'xmpptask.h', + 'xmppthread.cc', + 'xmppthread.h', + ], + 'direct_dependent_settings': { + 'cflags_cc!': [ + '-Wnon-virtual-dtor', + ], + 'defines': [ + 'FEATURE_ENABLE_SSL', + 'FEATURE_ENABLE_VOICEMAIL', + ], + }, + 'conditions': [ + ['build_with_chromium==0', { + 'defines': [ + 'FEATURE_ENABLE_VOICEMAIL', + 'FEATURE_ENABLE_PSTN', + ], + }], + ['os_posix==1', { + 'configurations': { + 'Debug_Base': { + 'defines': [ + # Chromium's build/common.gypi defines this for all posix + # _except_ for ios & mac. We want it there as well, e.g. + # because ASSERT and friends trigger off of it. + '_DEBUG', + ], + }, + } + }], + ['OS=="android"', { + 'cflags!': [ + '-Wextra', + '-Wall', + ], + }], + ], + }], +} + diff --git a/webrtc/libjingle/xmpp/xmpp_tests.gypi b/webrtc/libjingle/xmpp/xmpp_tests.gypi new file mode 100644 index 000000000..f1dec1cd1 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmpp_tests.gypi @@ -0,0 +1,37 @@ +# Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. + +{ + 'includes': [ '../../build/common.gypi', ], + 'targets': [ + { + 'target_name': 'rtc_xmpp_unittest', + 'type': 'none', + 'direct_dependent_settings': { + 'sources': [ + 'fakexmppclient.h', + 'hangoutpubsubclient_unittest.cc', + 'jid_unittest.cc', + 'mucroomconfigtask_unittest.cc', + 'mucroomdiscoverytask_unittest.cc', + 'mucroomlookuptask_unittest.cc', + 'mucroomuniquehangoutidtask_unittest.cc', + 'pingtask_unittest.cc', + 'pubsubclient_unittest.cc', + 'pubsubtasks_unittest.cc', + 'util_unittest.cc', + 'util_unittest.h', + 'xmppengine_unittest.cc', + 'xmpplogintask_unittest.cc', + 'xmppstanzaparser_unittest.cc', + ], + }, + }, + ], +} + diff --git a/webrtc/libjingle/xmpp/xmppauth.cc b/webrtc/libjingle/xmpp/xmppauth.cc new file mode 100644 index 000000000..a3d2f6784 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppauth.cc @@ -0,0 +1,88 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/xmppauth.h" + +#include + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/saslcookiemechanism.h" +#include "webrtc/libjingle/xmpp/saslplainmechanism.h" + +XmppAuth::XmppAuth() : done_(false) { +} + +XmppAuth::~XmppAuth() { +} + +void XmppAuth::StartPreXmppAuth(const buzz::Jid& jid, + const rtc::SocketAddress& server, + const rtc::CryptString& pass, + const std::string& auth_mechanism, + const std::string& auth_token) { + jid_ = jid; + passwd_ = pass; + auth_mechanism_ = auth_mechanism; + auth_token_ = auth_token; + done_ = true; + + SignalAuthDone(); +} + +static bool contains(const std::vector& strings, + const std::string& string) { + return std::find(strings.begin(), strings.end(), string) != strings.end(); +} + +std::string XmppAuth::ChooseBestSaslMechanism( + const std::vector& mechanisms, + bool encrypted) { + // First try Oauth2. + if (GetAuthMechanism() == buzz::AUTH_MECHANISM_OAUTH2 && + contains(mechanisms, buzz::AUTH_MECHANISM_OAUTH2)) { + return buzz::AUTH_MECHANISM_OAUTH2; + } + + // A token is the weakest auth - 15s, service-limited, so prefer it. + if (GetAuthMechanism() == buzz::AUTH_MECHANISM_GOOGLE_TOKEN && + contains(mechanisms, buzz::AUTH_MECHANISM_GOOGLE_TOKEN)) { + return buzz::AUTH_MECHANISM_GOOGLE_TOKEN; + } + + // A cookie is the next weakest - 14 days. + if (GetAuthMechanism() == buzz::AUTH_MECHANISM_GOOGLE_COOKIE && + contains(mechanisms, buzz::AUTH_MECHANISM_GOOGLE_COOKIE)) { + return buzz::AUTH_MECHANISM_GOOGLE_COOKIE; + } + + // As a last resort, use plain authentication. + if (contains(mechanisms, buzz::AUTH_MECHANISM_PLAIN)) { + return buzz::AUTH_MECHANISM_PLAIN; + } + + // No good mechanism found + return ""; +} + +buzz::SaslMechanism* XmppAuth::CreateSaslMechanism( + const std::string& mechanism) { + if (mechanism == buzz::AUTH_MECHANISM_OAUTH2) { + return new buzz::SaslCookieMechanism( + mechanism, jid_.Str(), auth_token_, "oauth2"); + } else if (mechanism == buzz::AUTH_MECHANISM_GOOGLE_TOKEN) { + return new buzz::SaslCookieMechanism(mechanism, jid_.Str(), auth_token_); + // } else if (mechanism == buzz::AUTH_MECHANISM_GOOGLE_COOKIE) { + // return new buzz::SaslCookieMechanism(mechanism, jid.Str(), sid_); + } else if (mechanism == buzz::AUTH_MECHANISM_PLAIN) { + return new buzz::SaslPlainMechanism(jid_, passwd_); + } else { + return NULL; + } +} diff --git a/webrtc/libjingle/xmpp/xmppauth.h b/webrtc/libjingle/xmpp/xmppauth.h new file mode 100644 index 000000000..dd363d17c --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppauth.h @@ -0,0 +1,61 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPAUTH_H_ +#define WEBRTC_LIBJINGLE_XMPP_XMPPAUTH_H_ + +#include + +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/prexmppauth.h" +#include "webrtc/libjingle/xmpp/saslhandler.h" +#include "webrtc/base/cryptstring.h" +#include "webrtc/base/sigslot.h" + +class XmppAuth: public buzz::PreXmppAuth { +public: + XmppAuth(); + virtual ~XmppAuth(); + + // TODO: Just have one "secret" that is either pass or + // token? + virtual void StartPreXmppAuth(const buzz::Jid& jid, + const rtc::SocketAddress& server, + const rtc::CryptString& pass, + const std::string& auth_mechanism, + const std::string& auth_token); + + virtual bool IsAuthDone() const { return done_; } + virtual bool IsAuthorized() const { return true; } + virtual bool HadError() const { return false; } + virtual int GetError() const { return 0; } + virtual buzz::CaptchaChallenge GetCaptchaChallenge() const { + return buzz::CaptchaChallenge(); + } + virtual std::string GetAuthMechanism() const { return auth_mechanism_; } + virtual std::string GetAuthToken() const { return auth_token_; } + + virtual std::string ChooseBestSaslMechanism( + const std::vector& mechanisms, + bool encrypted); + + virtual buzz::SaslMechanism * CreateSaslMechanism( + const std::string& mechanism); + +private: + buzz::Jid jid_; + rtc::CryptString passwd_; + std::string auth_mechanism_; + std::string auth_token_; + bool done_; +}; + +#endif // WEBRTC_LIBJINGLE_XMPP_XMPPAUTH_H_ + diff --git a/webrtc/libjingle/xmpp/xmppclient.cc b/webrtc/libjingle/xmpp/xmppclient.cc new file mode 100644 index 000000000..7c2a5e693 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppclient.cc @@ -0,0 +1,424 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/xmppclient.h" + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/plainsaslhandler.h" +#include "webrtc/libjingle/xmpp/prexmppauth.h" +#include "webrtc/libjingle/xmpp/saslplainmechanism.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/stringutils.h" +#include "xmpptask.h" + +namespace buzz { + +class XmppClient::Private : + public sigslot::has_slots<>, + public XmppSessionHandler, + public XmppOutputHandler { +public: + + explicit Private(XmppClient* client) : + client_(client), + socket_(), + engine_(), + proxy_port_(0), + pre_engine_error_(XmppEngine::ERROR_NONE), + pre_engine_subcode_(0), + signal_closed_(false), + allow_plain_(false) {} + + virtual ~Private() { + // We need to disconnect from socket_ before engine_ is destructed (by + // the auto-generated destructor code). + ResetSocket(); + } + + // the owner + XmppClient* const client_; + + // the two main objects + rtc::scoped_ptr socket_; + rtc::scoped_ptr engine_; + rtc::scoped_ptr pre_auth_; + rtc::CryptString pass_; + std::string auth_mechanism_; + std::string auth_token_; + rtc::SocketAddress server_; + std::string proxy_host_; + int proxy_port_; + XmppEngine::Error pre_engine_error_; + int pre_engine_subcode_; + CaptchaChallenge captcha_challenge_; + bool signal_closed_; + bool allow_plain_; + + void ResetSocket() { + if (socket_) { + socket_->SignalConnected.disconnect(this); + socket_->SignalRead.disconnect(this); + socket_->SignalClosed.disconnect(this); + socket_.reset(NULL); + } + } + + // 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(); +}; + +bool IsTestServer(const std::string& server_name, + const std::string& test_server_domain) { + return (!test_server_domain.empty() && + rtc::ends_with(server_name.c_str(), + test_server_domain.c_str())); +} + +XmppReturnStatus XmppClient::Connect( + const XmppClientSettings& settings, + const std::string& lang, AsyncSocket* socket, PreXmppAuth* pre_auth) { + if (socket == NULL) + return XMPP_RETURN_BADARGUMENT; + if (d_->socket_) + 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()); + if (!settings.resource().empty()) { + d_->engine_->SetRequestedResource(settings.resource()); + } + d_->engine_->SetTls(settings.use_tls()); + + // The talk.google.com server returns a certificate with common-name: + // CN="gmail.com" for @gmail.com accounts, + // CN="googlemail.com" for @googlemail.com accounts, + // CN="talk.google.com" for other accounts (such as @example.com), + // so we tweak the tls server setting for those other accounts to match the + // returned certificate CN of "talk.google.com". + // For other servers, we leave the strings empty, which causes the jid's + // domain to be used. We do the same for gmail.com and googlemail.com as the + // returned CN matches the account domain in those cases. + std::string server_name = settings.server().HostAsURIString(); + if (server_name == buzz::STR_TALK_GOOGLE_COM || + server_name == buzz::STR_TALKX_L_GOOGLE_COM || + server_name == buzz::STR_XMPP_GOOGLE_COM || + server_name == buzz::STR_XMPPX_L_GOOGLE_COM || + IsTestServer(server_name, settings.test_server_domain())) { + if (settings.host() != STR_GMAIL_COM && + settings.host() != STR_GOOGLEMAIL_COM) { + d_->engine_->SetTlsServer("", STR_TALK_GOOGLE_COM); + } + } + + // Set language + d_->engine_->SetLanguage(lang); + + d_->engine_->SetUser(buzz::Jid(settings.user(), settings.host(), STR_EMPTY)); + + d_->pass_ = settings.pass(); + d_->auth_mechanism_ = settings.auth_mechanism(); + d_->auth_token_ = settings.auth_token(); + d_->server_ = settings.server(); + d_->proxy_host_ = settings.proxy_host(); + d_->proxy_port_ = settings.proxy_port(); + d_->allow_plain_ = settings.allow_plain(); + d_->pre_auth_.reset(pre_auth); + + return XMPP_RETURN_OK; +} + +XmppEngine::State XmppClient::GetState() const { + if (!d_->engine_) + return XmppEngine::STATE_NONE; + return d_->engine_->GetState(); +} + +XmppEngine::Error XmppClient::GetError(int* subcode) { + if (subcode) { + *subcode = 0; + } + if (!d_->engine_) + return XmppEngine::ERROR_NONE; + if (d_->pre_engine_error_ != XmppEngine::ERROR_NONE) { + if (subcode) { + *subcode = d_->pre_engine_subcode_; + } + return d_->pre_engine_error_; + } + return d_->engine_->GetError(subcode); +} + +const XmlElement* XmppClient::GetStreamError() { + if (!d_->engine_) { + return NULL; + } + return d_->engine_->GetStreamError(); +} + +CaptchaChallenge XmppClient::GetCaptchaChallenge() { + if (!d_->engine_) + return CaptchaChallenge(); + return d_->captcha_challenge_; +} + +std::string XmppClient::GetAuthMechanism() { + if (!d_->engine_) + return ""; + return d_->auth_mechanism_; +} + +std::string XmppClient::GetAuthToken() { + if (!d_->engine_) + return ""; + return d_->auth_token_; +} + +int XmppClient::ProcessStart() { + // Should not happen, but was observed in crash reports + if (!d_->socket_) { + LOG(LS_ERROR) << "socket_ already reset"; + return STATE_DONE; + } + + if (d_->pre_auth_) { + d_->pre_auth_->SignalAuthDone.connect(this, &XmppClient::OnAuthDone); + d_->pre_auth_->StartPreXmppAuth( + d_->engine_->GetUser(), d_->server_, d_->pass_, + d_->auth_mechanism_, d_->auth_token_); + d_->pass_.Clear(); // done with this; + return STATE_PRE_XMPP_LOGIN; + } + else { + d_->engine_->SetSaslHandler(new PlainSaslHandler( + d_->engine_->GetUser(), d_->pass_, d_->allow_plain_)); + d_->pass_.Clear(); // done with this; + return STATE_START_XMPP_LOGIN; + } +} + +void XmppClient::OnAuthDone() { + Wake(); +} + +int XmppClient::ProcessTokenLogin() { + // Should not happen, but was observed in crash reports + if (!d_->socket_) { + LOG(LS_ERROR) << "socket_ already reset"; + return STATE_DONE; + } + + // Don't know how this could happen, but crash reports show it as NULL + if (!d_->pre_auth_) { + 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; + d_->pre_engine_subcode_ = d_->pre_auth_->GetError(); + } + else { + d_->pre_engine_error_ = XmppEngine::ERROR_UNAUTHORIZED; + d_->pre_engine_subcode_ = 0; + d_->captcha_challenge_ = d_->pre_auth_->GetCaptchaChallenge(); + } + d_->pre_auth_.reset(NULL); // done with this + EnsureClosed(); + return STATE_ERROR; + } + + // Save auth token as a result + + d_->auth_mechanism_ = d_->pre_auth_->GetAuthMechanism(); + d_->auth_token_ = d_->pre_auth_->GetAuthToken(); + + // transfer ownership of pre_auth_ to engine + d_->engine_->SetSaslHandler(d_->pre_auth_.release()); + return STATE_START_XMPP_LOGIN; +} + +int XmppClient::ProcessStartXmppLogin() { + // Should not happen, but was observed in crash reports + if (!d_->socket_) { + LOG(LS_ERROR) << "socket_ already reset"; + return STATE_DONE; + } + + // 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_ || d_->engine_->GetState() == XmppEngine::STATE_CLOSED)) + return STATE_DONE; + return STATE_BLOCKED; +} + +XmppReturnStatus XmppClient::Disconnect() { + if (!d_->socket_) + return XMPP_RETURN_BADSTATE; + Abort(); + d_->engine_->Disconnect(); + d_->ResetSocket(); + return XMPP_RETURN_OK; +} + +XmppClient::XmppClient(TaskParent* parent) + : XmppTaskParentInterface(parent), + delivering_signal_(false), + valid_(false) { + d_.reset(new Private(this)); + valid_ = true; +} + +XmppClient::~XmppClient() { + valid_ = false; +} + +const Jid& XmppClient::jid() const { + 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 (;;) { + // Should not happen, but was observed in crash reports + if (!socket_) { + LOG(LS_ERROR) << "socket_ already reset"; + return; + } + + if (!socket_->Read(bytes, sizeof(bytes), &bytes_read)) { + // TODO: deal with error information + return; + } + + if (bytes_read == 0) + return; + +//#ifdef _DEBUG + client_->SignalLogInput(bytes, static_cast(bytes_read)); +//#endif + + engine_->HandleInput(bytes, bytes_read); + } +} + +void XmppClient::Private::OnSocketClosed() { + int code = socket_->GetError(); + engine_->ConnectionClosed(code); +} + +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, static_cast(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; + } +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/xmppclient.h b/webrtc/libjingle/xmpp/xmppclient.h new file mode 100644 index 000000000..7b9eb7ab7 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppclient.h @@ -0,0 +1,148 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPCLIENT_H_ +#define WEBRTC_LIBJINGLE_XMPP_XMPPCLIENT_H_ + +#include +#include "webrtc/libjingle/xmpp/asyncsocket.h" +#include "webrtc/libjingle/xmpp/xmppclientsettings.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" +#include "webrtc/base/basicdefs.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/task.h" + +namespace buzz { + +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 XmppTaskParentInterface, + public XmppClientInterface, + public sigslot::has_slots<> +{ +public: + explicit XmppClient(rtc::TaskParent * parent); + virtual ~XmppClient(); + + XmppReturnStatus Connect(const XmppClientSettings & settings, + const std::string & lang, + AsyncSocket * socket, + PreXmppAuth * preauth); + + virtual int ProcessStart(); + virtual int ProcessResponse(); + XmppReturnStatus Disconnect(); + + sigslot::signal1 SignalStateChange; + XmppEngine::Error GetError(int *subcode); + + // When there is a stanza, return the stanza + // so that they can be handled. + const XmlElement *GetStreamError(); + + // 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 token + // (if we used GAIA authentication) + std::string GetAuthMechanism(); + std::string GetAuthToken(); + + XmppReturnStatus SendRaw(const std::string & text); + + XmppEngine* engine(); + + sigslot::signal2 SignalLogInput; + sigslot::signal2 SignalLogOutput; + + // As XmppTaskParentIntreface + virtual XmppClientInterface* GetClient() { return this; } + + // As XmppClientInterface + virtual XmppEngine::State GetState() const; + virtual const Jid& jid() const; + virtual std::string NextId(); + virtual XmppReturnStatus SendStanza(const XmlElement *stanza); + virtual XmppReturnStatus SendStanzaError(const XmlElement * pelOriginal, + XmppStanzaError code, + const std::string & text); + virtual void AddXmppTask(XmppTask *, XmppEngine::HandlerLevel); + virtual void RemoveXmppTask(XmppTask *); + + private: + friend class XmppTask; + + void OnAuthDone(); + + // 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 ProcessTokenLogin(); + 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 ProcessTokenLogin(); + int ProcessStartXmppLogin(); + void EnsureClosed(); + + class Private; + friend class Private; + rtc::scoped_ptr d_; + + bool delivering_signal_; + bool valid_; +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_XMPPCLIENT_H_ diff --git a/webrtc/libjingle/xmpp/xmppclientsettings.h b/webrtc/libjingle/xmpp/xmppclientsettings.h new file mode 100644 index 000000000..5b7572aa6 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppclientsettings.h @@ -0,0 +1,111 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPCLIENTSETTINGS_H_ +#define WEBRTC_LIBJINGLE_XMPP_XMPPCLIENTSETTINGS_H_ + +#include "webrtc/p2p/base/port.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/base/cryptstring.h" + +namespace buzz { + +class XmppUserSettings { + public: + XmppUserSettings() + : use_tls_(buzz::TLS_DISABLED), + allow_plain_(false) { + } + + void set_user(const std::string& user) { user_ = user; } + void set_host(const std::string& host) { host_ = host; } + void set_pass(const rtc::CryptString& pass) { pass_ = pass; } + void set_auth_token(const std::string& mechanism, + const std::string& token) { + auth_mechanism_ = mechanism; + auth_token_ = token; + } + void set_resource(const std::string& resource) { resource_ = resource; } + void set_use_tls(const TlsOptions use_tls) { use_tls_ = use_tls; } + void set_allow_plain(bool f) { allow_plain_ = f; } + void set_test_server_domain(const std::string& test_server_domain) { + test_server_domain_ = test_server_domain; + } + void set_token_service(const std::string& token_service) { + token_service_ = token_service; + } + + const std::string& user() const { return user_; } + const std::string& host() const { return host_; } + const rtc::CryptString& pass() const { return pass_; } + const std::string& auth_mechanism() const { return auth_mechanism_; } + const std::string& auth_token() const { return auth_token_; } + const std::string& resource() const { return resource_; } + TlsOptions use_tls() const { return use_tls_; } + bool allow_plain() const { return allow_plain_; } + const std::string& test_server_domain() const { return test_server_domain_; } + const std::string& token_service() const { return token_service_; } + + private: + std::string user_; + std::string host_; + rtc::CryptString pass_; + std::string auth_mechanism_; + std::string auth_token_; + std::string resource_; + TlsOptions use_tls_; + bool allow_plain_; + std::string test_server_domain_; + std::string token_service_; +}; + +class XmppClientSettings : public XmppUserSettings { + public: + XmppClientSettings() + : protocol_(cricket::PROTO_TCP), + proxy_(rtc::PROXY_NONE), + proxy_port_(80), + use_proxy_auth_(false) { + } + + void set_server(const rtc::SocketAddress& server) { + server_ = server; + } + void set_protocol(cricket::ProtocolType protocol) { protocol_ = protocol; } + void set_proxy(rtc::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 rtc::CryptString& pass) { proxy_pass_ = pass; } + + const rtc::SocketAddress& server() const { return server_; } + cricket::ProtocolType protocol() const { return protocol_; } + rtc::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 rtc::CryptString& proxy_pass() const { return proxy_pass_; } + + private: + rtc::SocketAddress server_; + cricket::ProtocolType protocol_; + rtc::ProxyType proxy_; + std::string proxy_host_; + int proxy_port_; + bool use_proxy_auth_; + std::string proxy_user_; + rtc::CryptString proxy_pass_; +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_XMPPCLIENT_H_ diff --git a/webrtc/libjingle/xmpp/xmppengine.h b/webrtc/libjingle/xmpp/xmppengine.h new file mode 100644 index 000000000..2bc2453ce --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppengine.h @@ -0,0 +1,332 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPENGINE_H_ +#define WEBRTC_LIBJINGLE_XMPP_XMPPENGINE_H_ + +// also part of the API +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/jid.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, +}; + +// TlsOptions +// This is used by API to identify TLS setting. +enum TlsOptions { + TLS_DISABLED, + TLS_ENABLED, + TLS_REQUIRED +}; + +//! 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: + virtual ~XmppOutputHandler() {} + + //! 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: + virtual ~XmppSessionHandler() {} + //! 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: + virtual ~XmppStanzaHandler() {} + //! 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: + virtual ~XmppIqHandler() {} + //! 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 + ERROR_NETWORK_TIMEOUT, //!< Some sort of timeout (eg., we never got the roster) + ERROR_MISSING_USERNAME //!< User has a Google Account but no nickname + }; + + //! 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(int subcode) = 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 SetTls(TlsOptions 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 SetTlsServer(const std::string & proxy_hostname, + const std::string & proxy_domain) = 0; + + //! Gets whether TLS will be used within the connection. + virtual TlsOptions GetTls() = 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; + + //! Sets language + virtual void SetLanguage(const std::string & lang) = 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(int *subcode) = 0; + + //! The stream:error stanza, when the error is XmppEngine::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 // WEBRTC_LIBJINGLE_XMPP_XMPPENGINE_H_ diff --git a/webrtc/libjingle/xmpp/xmppengine_unittest.cc b/webrtc/libjingle/xmpp/xmppengine_unittest.cc new file mode 100644 index 000000000..7af151ba4 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppengine_unittest.cc @@ -0,0 +1,325 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/plainsaslhandler.h" +#include "webrtc/libjingle/xmpp/saslplainmechanism.h" +#include "webrtc/libjingle/xmpp/util_unittest.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" + +using buzz::Jid; +using buzz::QName; +using buzz::XmlElement; +using buzz::XmppEngine; +using buzz::XmppIqCookie; +using buzz::XmppIqHandler; +using buzz::XmppTestHandler; +using buzz::QN_ID; +using buzz::QN_IQ; +using buzz::QN_TYPE; +using buzz::QN_ROSTER_QUERY; +using buzz::XMPP_RETURN_OK; +using buzz::XMPP_RETURN_BADARGUMENT; + +// XmppEngineTestIqHandler +// This class grabs the response to an IQ stanza and stores it in a string. +class XmppEngineTestIqHandler : public XmppIqHandler { + public: + virtual void IqResponse(XmppIqCookie, const XmlElement * stanza) { + ss_ << stanza->Str(); + } + + std::string IqResponseActivity() { + std::string result = ss_.str(); + ss_.str(""); + return result; + } + + private: + std::stringstream ss_; +}; + +class XmppEngineTest : public testing::Test { + public: + XmppEngine* engine() { return engine_.get(); } + XmppTestHandler* handler() { return handler_.get(); } + virtual void SetUp() { + engine_.reset(XmppEngine::Create()); + handler_.reset(new XmppTestHandler(engine_.get())); + + Jid jid("david@my-server"); + rtc::InsecureCryptStringImpl pass; + pass.password() = "david"; + engine_->SetSessionHandler(handler_.get()); + engine_->SetOutputHandler(handler_.get()); + engine_->AddStanzaHandler(handler_.get()); + engine_->SetUser(jid); + engine_->SetSaslHandler( + new buzz::PlainSaslHandler(jid, rtc::CryptString(pass), true)); + } + virtual void TearDown() { + handler_.reset(); + engine_.reset(); + } + void RunLogin(); + + private: + rtc::scoped_ptr engine_; + rtc::scoped_ptr handler_; +}; + +void XmppEngineTest::RunLogin() { + // Connect + EXPECT_EQ(XmppEngine::STATE_START, engine()->GetState()); + engine()->Connect(); + EXPECT_EQ(XmppEngine::STATE_OPENING, engine()->GetState()); + + EXPECT_EQ("[OPENING]", handler_->SessionActivity()); + + EXPECT_EQ("\r\n", handler_->OutputActivity()); + + std::string input = + ""; + engine()->HandleInput(input.c_str(), input.length()); + + input = + "" + "" + "" + "" + "" + "DIGEST-MD5" + "PLAIN" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("", + handler_->OutputActivity()); + + EXPECT_EQ("", handler_->SessionActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + + input = ""; + engine()->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("[START-TLS my-server]" + "\r\n", handler_->OutputActivity()); + + EXPECT_EQ("", handler_->SessionActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + + input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + input = + "" + "" + "DIGEST-MD5" + "PLAIN" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("AGRhdmlkAGRhdmlk", + handler_->OutputActivity()); + + EXPECT_EQ("", handler_->SessionActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + + input = ""; + engine()->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("\r\n", handler_->OutputActivity()); + + EXPECT_EQ("", handler_->SessionActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + + input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + input = "" + "" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("" + "", + handler_->OutputActivity()); + + EXPECT_EQ("", handler_->SessionActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + + input = "" + "" + "david@my-server/test"; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("" + "", + handler_->OutputActivity()); + + EXPECT_EQ("", handler_->SessionActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + + input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[OPEN]", handler_->SessionActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + EXPECT_EQ(Jid("david@my-server/test"), engine()->FullJid()); +} + +// TestSuccessfulLogin() +// This function simply tests to see if a login works. This includes +// encryption and authentication +TEST_F(XmppEngineTest, TestSuccessfulLoginAndDisconnect) { + RunLogin(); + engine()->Disconnect(); + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppEngineTest, TestSuccessfulLoginAndConnectionClosed) { + RunLogin(); + engine()->ConnectionClosed(0); + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-CONNECTION-CLOSED]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + + +// TestNotXmpp() +// This tests the error case when connecting to a non XMPP service +TEST_F(XmppEngineTest, TestNotXmpp) { + // Connect + engine()->Connect(); + EXPECT_EQ("\r\n", handler()->OutputActivity()); + + // Send garbage response (courtesy of apache) + std::string input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[OPENING][CLOSED][ERROR-XML]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +// TestPassthrough() +// This tests that arbitrary stanzas can be passed to the server through +// the engine. +TEST_F(XmppEngineTest, TestPassthrough) { + // Queue up an app stanza + XmlElement application_stanza(QName("test", "app-stanza")); + application_stanza.AddText("this-is-a-test"); + engine()->SendStanza(&application_stanza); + + // Do the whole login handshake + RunLogin(); + + EXPECT_EQ("this-is-a-test" + "", handler()->OutputActivity()); + + // do another stanza + XmlElement roster_get(QN_IQ); + roster_get.AddAttr(QN_TYPE, "get"); + roster_get.AddAttr(QN_ID, engine()->NextId()); + roster_get.AddElement(new XmlElement(QN_ROSTER_QUERY, true)); + engine()->SendStanza(&roster_get); + EXPECT_EQ("" + "", handler()->OutputActivity()); + + // now say the server ends the stream + engine()->HandleInput("", 16); + EXPECT_EQ("[CLOSED][ERROR-DOCUMENT-CLOSED]", handler()->SessionActivity()); + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +// TestIqCallback() +// This tests the routing of Iq stanzas and responses. +TEST_F(XmppEngineTest, TestIqCallback) { + XmppEngineTestIqHandler iq_response; + XmppIqCookie cookie; + + // Do the whole login handshake + RunLogin(); + + // Build an iq request + XmlElement roster_get(QN_IQ); + roster_get.AddAttr(QN_TYPE, "get"); + roster_get.AddAttr(QN_ID, engine()->NextId()); + roster_get.AddElement(new XmlElement(QN_ROSTER_QUERY, true)); + engine()->SendIq(&roster_get, &iq_response, &cookie); + EXPECT_EQ("" + "", handler()->OutputActivity()); + EXPECT_EQ("", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); + EXPECT_EQ("", iq_response.IqResponseActivity()); + + // now say the server responds to the iq + std::string input = "" + "foo" + ""; + engine()->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("", handler()->OutputActivity()); + EXPECT_EQ("", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); + EXPECT_EQ("" + "foo" + "", iq_response.IqResponseActivity()); + + EXPECT_EQ(XMPP_RETURN_BADARGUMENT, engine()->RemoveIqHandler(cookie, NULL)); + + // Do it again with another id to test cancel + roster_get.SetAttr(QN_ID, engine()->NextId()); + engine()->SendIq(&roster_get, &iq_response, &cookie); + EXPECT_EQ("" + "", handler()->OutputActivity()); + EXPECT_EQ("", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); + EXPECT_EQ("", iq_response.IqResponseActivity()); + + // cancel the handler this time + EXPECT_EQ(XMPP_RETURN_OK, engine()->RemoveIqHandler(cookie, NULL)); + + // now say the server responds to the iq: the iq handler should not get it. + input = "bar" + ""; + engine()->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("" + "bar" + "", handler()->StanzaActivity()); + EXPECT_EQ("", iq_response.IqResponseActivity()); + EXPECT_EQ("", handler()->OutputActivity()); + EXPECT_EQ("", handler()->SessionActivity()); +} diff --git a/webrtc/libjingle/xmpp/xmppengineimpl.cc b/webrtc/libjingle/xmpp/xmppengineimpl.cc new file mode 100644 index 000000000..e77a1d934 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppengineimpl.cc @@ -0,0 +1,446 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/xmppengineimpl.h" + +#include +#include +#include + +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlprinter.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/saslhandler.h" +#include "webrtc/libjingle/xmpp/xmpplogintask.h" +#include "webrtc/base/common.h" + +namespace buzz { + +XmppEngine* XmppEngine::Create() { + return new XmppEngineImpl(); +} + + +XmppEngineImpl::XmppEngineImpl() + : stanza_parse_handler_(this), + stanza_parser_(&stanza_parse_handler_), + engine_entered_(0), + password_(), + requested_resource_(STR_EMPTY), + tls_option_(buzz::TLS_REQUIRED), + login_task_(new XmppLoginTask(this)), + next_id_(0), + state_(STATE_START), + encrypted_(false), + error_code_(ERROR_NONE), + subcode_(0), + stream_error_(), + raised_reset_(false), + output_handler_(NULL), + session_handler_(NULL), + iq_entries_(new IqEntryVector()), + sasl_handler_(), + output_(new std::stringstream()) { + for (int i = 0; i < HL_COUNT; i+= 1) { + stanza_handlers_[i].reset(new StanzaHandlerVector()); + } + + // Add XMPP namespaces to XML namespaces stack. + xmlns_stack_.AddXmlns("stream", "http://etherx.jabber.org/streams"); + xmlns_stack_.AddXmlns("", "jabber:client"); +} + +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); + + // TODO: The return value of the xml parser is not checked. + stanza_parser_.Parse(bytes, len, false); + + return XMPP_RETURN_OK; +} + +XmppReturnStatus XmppEngineImpl::ConnectionClosed(int subcode) { + if (state_ != STATE_CLOSED) { + EnterExit ee(this); + // If told that connection closed and not already closed, + // then connection was unpexectedly dropped. + if (subcode) { + SignalError(ERROR_SOCKET, subcode); + } else { + SignalError(ERROR_CONNECTION_CLOSED, 0); // no subcode + } + } + return XMPP_RETURN_OK; +} + +XmppReturnStatus XmppEngineImpl::SetTls(TlsOptions use_tls) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + tls_option_ = use_tls; + return XMPP_RETURN_OK; +} + +XmppReturnStatus XmppEngineImpl::SetTlsServer( + const std::string& tls_server_hostname, + const std::string& tls_server_domain) { + if (state_ != STATE_START) + return XMPP_RETURN_BADSTATE; + + tls_server_hostname_ = tls_server_hostname; + tls_server_domain_= tls_server_domain; + + return XMPP_RETURN_OK; +} + +TlsOptions XmppEngineImpl::GetTls() { + return tls_option_; +} + +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_) { + 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_) { + // 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_) + 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* start) { + if (HasError() || raised_reset_) + return; + + if (login_task_) { + // start-stream should go to login task + login_task_->IncomingStanza(start, true); + if (login_task_->IsDone()) + login_task_.reset(); + } + else { + // if not logging in, it's an error to see a start + SignalError(ERROR_XML, 0); + } +} + +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_) { + // 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)) + return; + } + } + + // 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); + } + } +} + +void XmppEngineImpl::IncomingEnd(bool isError) { + if (HasError() || raised_reset_) + return; + + SignalError(isError ? ERROR_XML : ERROR_DOCUMENT_CLOSED, 0); +} + +void XmppEngineImpl::InternalSendStart(const std::string& to) { + std::string hostname = tls_server_hostname_; + if (hostname.empty()) + hostname = to; + + // If not language is specified, the spec says use * + std::string lang = lang_; + if (lang.length() == 0) + lang = "*"; + + // 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)); + + XmlPrinter::PrintXml(output_.get(), element, &xmlns_stack_); +} + +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* stream_error) { + if (state_ != STATE_CLOSED) { + stream_error_.reset(new XmlElement(*stream_error)); + SignalError(ERROR_STREAM, 0); + } +} + +void XmppEngineImpl::SignalError(Error error_code, int sub_code) { + if (state_ != STATE_CLOSED) { + error_code_ = error_code; + subcode_ = sub_code; + state_ = STATE_CLOSED; + } +} + +bool XmppEngineImpl::HasError() { + return error_code_ != ERROR_NONE; +} + +void XmppEngineImpl::StartTls(const std::string& domain) { + if (output_handler_) { + // As substitute for the real (login jid's) domain, we permit + // verifying a tls_server_domain_ instead, if one was passed. + // This allows us to avoid running a proxy that needs to handle + // valuable certificates. + output_handler_->StartTls( + tls_server_domain_.empty() ? domain : tls_server_domain_); + encrypted_ = true; + } +} + +XmppEngineImpl::EnterExit::EnterExit(XmppEngineImpl* engine) + : engine_(engine), + state_(engine->state_) { + 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->stanza_parser_.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. + } +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/xmppengineimpl.h b/webrtc/libjingle/xmpp/xmppengineimpl.h new file mode 100644 index 000000000..c322596c4 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppengineimpl.h @@ -0,0 +1,265 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPENGINEIMPL_H_ +#define WEBRTC_LIBJINGLE_XMPP_XMPPENGINEIMPL_H_ + +#include +#include +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/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(int subcode); + + // 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 SetTls(TlsOptions use_tls); + + //! 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 SetTlsServer(const std::string& proxy_hostname, + const std::string& proxy_domain); + + //! Gets whether TLS will be used within the connection. + virtual TlsOptions GetTls(); + + //! 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(); + + //! Sets language + virtual void SetLanguage(const std::string& lang) { + lang_ = lang; + } + + // 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(int *subcode) { + if (subcode) { + *subcode = subcode_; + } + return error_code_; + } + + //! The stream:error stanza, when the error is XmppEngine::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* stanza); + + //! 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* stanza, + 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 *stanza); + void IncomingStart(const XmlElement *stanza); + void IncomingEnd(bool isError); + + void InternalSendStart(const std::string& domainName); + void InternalSendStanza(const XmlElement* stanza); + std::string ChooseBestSaslMechanism( + const std::vector& mechanisms, bool encrypted); + SaslMechanism* GetSaslMechanism(const std::string& name); + void SignalBound(const Jid& fullJid); + void SignalStreamError(const XmlElement* streamError); + void SignalError(Error errorCode, int subCode); + 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 ~StanzaParseHandler() {} + + virtual void StartStream(const XmlElement* stream) { + outer_->IncomingStart(stream); + } + virtual void Stanza(const XmlElement* stanza) { + outer_->IncomingStanza(stanza); + } + 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_; + }; + + friend class StanzaParseHandler; + friend class EnterExit; + + StanzaParseHandler stanza_parse_handler_; + XmppStanzaParser stanza_parser_; + + // state + int engine_entered_; + Jid user_jid_; + std::string password_; + std::string requested_resource_; + TlsOptions tls_option_; + std::string tls_server_hostname_; + std::string tls_server_domain_; + rtc::scoped_ptr login_task_; + std::string lang_; + + int next_id_; + Jid bound_jid_; + State state_; + bool encrypted_; + Error error_code_; + int subcode_; + rtc::scoped_ptr stream_error_; + bool raised_reset_; + XmppOutputHandler* output_handler_; + XmppSessionHandler* session_handler_; + + XmlnsStack xmlns_stack_; + + typedef std::vector StanzaHandlerVector; + rtc::scoped_ptr stanza_handlers_[HL_COUNT]; + + typedef std::vector IqEntryVector; + rtc::scoped_ptr iq_entries_; + + rtc::scoped_ptr sasl_handler_; + + rtc::scoped_ptr output_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_XMPPENGINEIMPL_H_ diff --git a/webrtc/libjingle/xmpp/xmppengineimpl_iq.cc b/webrtc/libjingle/xmpp/xmppengineimpl_iq.cc new file mode 100644 index 000000000..23b9fc43e --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppengineimpl_iq.cc @@ -0,0 +1,260 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/xmppengineimpl.h" +#include "webrtc/base/common.h" + +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/webrtc/libjingle/xmpp/xmpplogintask.cc b/webrtc/libjingle/xmpp/xmpplogintask.cc new file mode 100644 index 000000000..f5745cd97 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmpplogintask.cc @@ -0,0 +1,380 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/xmpplogintask.h" + +#include +#include + +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/saslmechanism.h" +#include "webrtc/libjingle/xmpp/xmppengineimpl.h" +#include "webrtc/base/base64.h" +#include "webrtc/base/common.h" + +using rtc::ConstantLabel; + +namespace buzz { + +#ifdef _DEBUG +const ConstantLabel XmppLoginTask::LOGINTASK_STATES[] = { + KLABEL(LOGINSTATE_INIT), + KLABEL(LOGINSTATE_STREAMSTART_SENT), + KLABEL(LOGINSTATE_STARTED_XMPP), + KLABEL(LOGINSTATE_TLS_INIT), + KLABEL(LOGINSTATE_AUTH_INIT), + KLABEL(LOGINSTATE_BIND_INIT), + KLABEL(LOGINSTATE_TLS_REQUESTED), + KLABEL(LOGINSTATE_SASL_RUNNING), + KLABEL(LOGINSTATE_BIND_REQUESTED), + KLABEL(LOGINSTATE_SESSION_REQUESTED), + KLABEL(LOGINSTATE_DONE), + LASTLABEL +}; +#endif // _DEBUG +XmppLoginTask::XmppLoginTask(XmppEngineImpl * pctx) : + pctx_(pctx), + authNeeded_(true), + allowNonGoogleLogin_(true), + state_(LOGINSTATE_INIT), + pelStanza_(NULL), + isStart_(false), + iqId_(STR_EMPTY), + pelFeatures_(), + fullJid_(STR_EMPTY), + streamId_(STR_EMPTY), + pvecQueuedStanzas_(new std::vector()), + sasl_mech_() { +} + +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; + +#if _DEBUG + LOG(LS_VERBOSE) << "XmppLoginTask::Advance - " + << rtc::ErrorName(state_, LOGINTASK_STATES); +#endif // _DEBUG + + switch (state_) { + + case LOGINSTATE_INIT: { + pctx_->RaiseReset(); + pelFeatures_.reset(NULL); + + // 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_->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); + + bool tls_present = (GetFeature(QN_TLS_STARTTLS) != NULL); + // Error if TLS required but not present. + if (pctx_->tls_option_ == buzz::TLS_REQUIRED && !tls_present) { + return Failure(XmppEngine::ERROR_TLS); + } + // Use TLS if required or enabled, and also available + if ((pctx_->tls_option_ == buzz::TLS_REQUIRED || + pctx_->tls_option_ == buzz::TLS_ENABLED) && tls_present) { + 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_option_ = buzz::TLS_ENABLED; + 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_) { + return Failure(XmppEngine::ERROR_AUTH); + } + + // OK, let's start it. + XmlElement * auth = sasl_mech_->StartSaslAuth(); + if (auth == NULL) { + return Failure(XmppEngine::ERROR_AUTH); + } + if (allowNonGoogleLogin_) { + // Setting the following two attributes is required to support + // non-google ids. + + // Allow login with non-google id accounts. + auth->SetAttr(QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN, "true"); + + // Allow login with either the non-google id or the friendly email. + auth->SetAttr(QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT, "true"); + } + + 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); + } + + // 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, 0); + 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/webrtc/libjingle/xmpp/xmpplogintask.h b/webrtc/libjingle/xmpp/xmpplogintask.h new file mode 100644 index 000000000..58e0a2f3f --- /dev/null +++ b/webrtc/libjingle/xmpp/xmpplogintask.h @@ -0,0 +1,87 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_LOGINTASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_LOGINTASK_H_ + +#include +#include + +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" + +namespace buzz { + +class XmlElement; +class XmppEngineImpl; +class SaslMechanism; + + +// TODO: Rename to LoginTask. +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); + void set_allow_non_google_login(bool b) + { allowNonGoogleLogin_ = b; } + +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_; + bool allowNonGoogleLogin_; + LoginTaskState state_; + const XmlElement * pelStanza_; + bool isStart_; + std::string iqId_; + rtc::scoped_ptr pelFeatures_; + Jid fullJid_; + std::string streamId_; + rtc::scoped_ptr > pvecQueuedStanzas_; + + rtc::scoped_ptr sasl_mech_; + +#ifdef _DEBUG + static const rtc::ConstantLabel LOGINTASK_STATES[]; +#endif // _DEBUG +}; + +} + +#endif // WEBRTC_LIBJINGLE_XMPP_LOGINTASK_H_ diff --git a/webrtc/libjingle/xmpp/xmpplogintask_unittest.cc b/webrtc/libjingle/xmpp/xmpplogintask_unittest.cc new file mode 100644 index 000000000..035275fea --- /dev/null +++ b/webrtc/libjingle/xmpp/xmpplogintask_unittest.cc @@ -0,0 +1,621 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/plainsaslhandler.h" +#include "webrtc/libjingle/xmpp/saslplainmechanism.h" +#include "webrtc/libjingle/xmpp/util_unittest.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/base/common.h" +#include "webrtc/base/cryptstring.h" +#include "webrtc/base/gunit.h" + +using buzz::Jid; +using buzz::QName; +using buzz::XmlElement; +using buzz::XmppEngine; +using buzz::XmppTestHandler; + +enum XlttStage { + XLTT_STAGE_CONNECT = 0, + XLTT_STAGE_STREAMSTART, + XLTT_STAGE_TLS_FEATURES, + XLTT_STAGE_TLS_PROCEED, + XLTT_STAGE_ENCRYPTED_START, + XLTT_STAGE_AUTH_FEATURES, + XLTT_STAGE_AUTH_SUCCESS, + XLTT_STAGE_AUTHENTICATED_START, + XLTT_STAGE_BIND_FEATURES, + XLTT_STAGE_BIND_SUCCESS, + XLTT_STAGE_SESSION_SUCCESS, +}; + +class XmppLoginTaskTest : public testing::Test { + public: + XmppEngine* engine() { return engine_.get(); } + XmppTestHandler* handler() { return handler_.get(); } + virtual void SetUp() { + engine_.reset(XmppEngine::Create()); + handler_.reset(new XmppTestHandler(engine_.get())); + + Jid jid("david@my-server"); + rtc::InsecureCryptStringImpl pass; + pass.password() = "david"; + engine_->SetSessionHandler(handler_.get()); + engine_->SetOutputHandler(handler_.get()); + engine_->AddStanzaHandler(handler_.get()); + engine_->SetUser(jid); + engine_->SetSaslHandler( + new buzz::PlainSaslHandler(jid, rtc::CryptString(pass), true)); + } + virtual void TearDown() { + handler_.reset(); + engine_.reset(); + } + void RunPartialLogin(XlttStage startstage, XlttStage endstage); + void SetTlsOptions(buzz::TlsOptions option); + + private: + rtc::scoped_ptr engine_; + rtc::scoped_ptr handler_; +}; + +void XmppLoginTaskTest::SetTlsOptions(buzz::TlsOptions option) { + engine_->SetTls(option); +} +void XmppLoginTaskTest::RunPartialLogin(XlttStage startstage, + XlttStage endstage) { + std::string input; + + switch (startstage) { + case XLTT_STAGE_CONNECT: { + engine_->Connect(); + XmlElement appStanza(QName("test", "app-stanza")); + appStanza.AddText("this-is-a-test"); + engine_->SendStanza(&appStanza); + + EXPECT_EQ("\r\n", handler_->OutputActivity()); + EXPECT_EQ("[OPENING]", handler_->SessionActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + if (endstage == XLTT_STAGE_CONNECT) + return; + } + + case XLTT_STAGE_STREAMSTART: { + input = ""; + engine_->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("", handler_->StanzaActivity()); + EXPECT_EQ("", handler_->SessionActivity()); + EXPECT_EQ("", handler_->OutputActivity()); + if (endstage == XLTT_STAGE_STREAMSTART) + return; + } + + case XLTT_STAGE_TLS_FEATURES: { + input = "" + "" + ""; + engine_->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("", + handler_->OutputActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + EXPECT_EQ("", handler_->SessionActivity()); + if (endstage == XLTT_STAGE_TLS_FEATURES) + return; + } + + case XLTT_STAGE_TLS_PROCEED: { + input = std::string(""); + engine_->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("[START-TLS my-server]" + "\r\n", handler_->OutputActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + EXPECT_EQ("", handler_->SessionActivity()); + if (endstage == XLTT_STAGE_TLS_PROCEED) + return; + } + + case XLTT_STAGE_ENCRYPTED_START: { + input = std::string(""); + engine_->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("", handler_->StanzaActivity()); + EXPECT_EQ("", handler_->SessionActivity()); + EXPECT_EQ("", handler_->OutputActivity()); + if (endstage == XLTT_STAGE_ENCRYPTED_START) + return; + } + + case XLTT_STAGE_AUTH_FEATURES: { + input = "" + "" + "DIGEST-MD5" + "PLAIN" + "" + ""; + engine_->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("AGRhdmlkAGRhdmlk", + handler_->OutputActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + EXPECT_EQ("", handler_->SessionActivity()); + if (endstage == XLTT_STAGE_AUTH_FEATURES) + return; + } + + case XLTT_STAGE_AUTH_SUCCESS: { + input = ""; + engine_->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("\r\n", handler_->OutputActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + EXPECT_EQ("", handler_->SessionActivity()); + if (endstage == XLTT_STAGE_AUTH_SUCCESS) + return; + } + + case XLTT_STAGE_AUTHENTICATED_START: { + input = std::string(""); + engine_->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("", handler_->StanzaActivity()); + EXPECT_EQ("", handler_->SessionActivity()); + EXPECT_EQ("", handler_->OutputActivity()); + if (endstage == XLTT_STAGE_AUTHENTICATED_START) + return; + } + + case XLTT_STAGE_BIND_FEATURES: { + input = "" + "" + "" + ""; + engine_->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("" + "", + handler_->OutputActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + EXPECT_EQ("", handler_->SessionActivity()); + if (endstage == XLTT_STAGE_BIND_FEATURES) + return; + } + + case XLTT_STAGE_BIND_SUCCESS: { + input = "" + "" + "david@my-server/test"; + engine_->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("" + "", + handler_->OutputActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + EXPECT_EQ("", handler_->SessionActivity()); + if (endstage == XLTT_STAGE_BIND_SUCCESS) + return; + } + + case XLTT_STAGE_SESSION_SUCCESS: { + input = ""; + engine_->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("this-is-a-test" + "", handler_->OutputActivity()); + EXPECT_EQ("[OPEN]", handler_->SessionActivity()); + EXPECT_EQ("", handler_->StanzaActivity()); + if (endstage == XLTT_STAGE_SESSION_SUCCESS) + return; + } + } +} + +TEST_F(XmppLoginTaskTest, TestUtf8Good) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_CONNECT); + + std::string input = "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("", handler()->OutputActivity()); + EXPECT_EQ("", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestNonUtf8Bad) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_CONNECT); + + std::string input = "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-XML]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestNoFeatures) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART); + + std::string input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-VERSION]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestTlsRequiredNotPresent) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART); + + std::string input = "" + "" + "DIGEST-MD5" + "PLAIN" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-TLS]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestTlsRequeiredAndPresent) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART); + + std::string input = "" + "" + "" + "" + "" + "X-GOOGLE-TOKEN" + "PLAIN" + "X-OAUTH2" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("", + handler()->OutputActivity()); + EXPECT_EQ("", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestTlsEnabledNotPresent) { + SetTlsOptions(buzz::TLS_ENABLED); + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART); + + std::string input = "" + "" + "DIGEST-MD5" + "PLAIN" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("AGRhdmlkAGRhdmlk", handler()->OutputActivity()); + EXPECT_EQ("", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestTlsEnabledAndPresent) { + SetTlsOptions(buzz::TLS_ENABLED); + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART); + + std::string input = "" + "" + "X-GOOGLE-TOKEN" + "PLAIN" + "X-OAUTH2" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("AGRhdmlkAGRhdmlk", handler()->OutputActivity()); + EXPECT_EQ("", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestTlsDisabledNotPresent) { + SetTlsOptions(buzz::TLS_DISABLED); + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART); + + std::string input = "" + "" + "DIGEST-MD5" + "PLAIN" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("AGRhdmlkAGRhdmlk", handler()->OutputActivity()); + EXPECT_EQ("", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestTlsDisabledAndPresent) { + SetTlsOptions(buzz::TLS_DISABLED); + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART); + + std::string input = "" + "" + "X-GOOGLE-TOKEN" + "PLAIN" + "X-OAUTH2" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("AGRhdmlkAGRhdmlk", handler()->OutputActivity()); + EXPECT_EQ("", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestTlsFailure) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_TLS_FEATURES); + + std::string input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-TLS]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestTlsBadStream) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_TLS_PROCEED); + + std::string input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-VERSION]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestMissingSaslPlain) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_ENCRYPTED_START); + + std::string input = "" + "" + "DIGEST-MD5" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-AUTH]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestWrongPassword) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_AUTH_FEATURES); + + std::string input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-UNAUTHORIZED]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestAuthBadStream) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_AUTH_SUCCESS); + + std::string input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-VERSION]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestMissingBindFeature) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_AUTHENTICATED_START); + + std::string input = "" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity()); +} + +TEST_F(XmppLoginTaskTest, TestMissingSessionFeature) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_AUTHENTICATED_START); + + std::string input = "" + "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +/* TODO: Handle this case properly inside XmppLoginTask. +TEST_F(XmppLoginTaskTest, TestBindFailure1) { + // check wrong JID + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_FEATURES); + + std::string input = "" + "" + "davey@my-server/test"; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} +*/ + +TEST_F(XmppLoginTaskTest, TestBindFailure2) { + // check missing JID + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_FEATURES); + + std::string input = "" + ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestBindFailure3) { + // check plain failure + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_FEATURES); + + std::string input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity()); + EXPECT_EQ("", handler()->StanzaActivity()); +} + +TEST_F(XmppLoginTaskTest, TestBindFailure4) { + // check wrong id to ignore + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_FEATURES); + + std::string input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + // continue after an ignored iq + RunPartialLogin(XLTT_STAGE_BIND_SUCCESS, XLTT_STAGE_SESSION_SUCCESS); +} + +TEST_F(XmppLoginTaskTest, TestSessionFailurePlain1) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_SUCCESS); + + std::string input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity()); +} + +TEST_F(XmppLoginTaskTest, TestSessionFailurePlain2) { + RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_SUCCESS); + + // check reverse iq to ignore + // TODO: consider queueing or passing through? + std::string input = ""; + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("", handler()->OutputActivity()); + EXPECT_EQ("", handler()->SessionActivity()); + + // continue after an ignored iq + RunPartialLogin(XLTT_STAGE_SESSION_SUCCESS, XLTT_STAGE_SESSION_SUCCESS); +} + +TEST_F(XmppLoginTaskTest, TestBadXml) { + int errorKind = 0; + for (XlttStage stage = XLTT_STAGE_CONNECT; + stage <= XLTT_STAGE_SESSION_SUCCESS; + stage = static_cast(stage + 1)) { + RunPartialLogin(XLTT_STAGE_CONNECT, stage); + + std::string input; + switch (errorKind++ % 5) { + case 0: input = "&syntax;"; break; + case 1: input = ""; break; + case 2: input = ""; break; + case 3: input = "<>"; break; + case 4: input = ""; break; + } + + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-XML]", handler()->SessionActivity()); + + TearDown(); + SetUp(); + } +} + +TEST_F(XmppLoginTaskTest, TestStreamError) { + for (XlttStage stage = XLTT_STAGE_CONNECT; + stage <= XLTT_STAGE_SESSION_SUCCESS; + stage = static_cast(stage + 1)) { + switch (stage) { + case XLTT_STAGE_CONNECT: + case XLTT_STAGE_TLS_PROCEED: + case XLTT_STAGE_AUTH_SUCCESS: + continue; + default: + break; + } + + RunPartialLogin(XLTT_STAGE_CONNECT, stage); + + std::string input = "" + "" + "" + "Some special application diagnostic information!" + "" + "" + ""; + + engine()->HandleInput(input.c_str(), input.length()); + + EXPECT_EQ("[CLOSED]", handler()->OutputActivity()); + EXPECT_EQ("[CLOSED][ERROR-STREAM]", handler()->SessionActivity()); + + EXPECT_EQ("" + "" + "" + "Some special application diagnostic information!" + "" + "" + "", engine()->GetStreamError()->Str()); + + TearDown(); + SetUp(); + } +} + diff --git a/webrtc/libjingle/xmpp/xmpppump.cc b/webrtc/libjingle/xmpp/xmpppump.cc new file mode 100644 index 000000000..45259b121 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmpppump.cc @@ -0,0 +1,67 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/xmpppump.h" + +#include "webrtc/libjingle/xmpp/xmppauth.h" + +namespace buzz { + +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); + if (!AllChildrenDone()) { + client_->SignalStateChange.connect(this, &XmppPump::OnStateChange); + client_->Connect(xcs, "", socket, auth); + client_->Start(); + } +} + +void XmppPump::DoDisconnect() { + if (!AllChildrenDone()) + 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() { + rtc::Thread::Current()->Post(this); +} + +int64 XmppPump::CurrentTime() { + return (int64)rtc::Time(); +} + +void XmppPump::OnMessage(rtc::Message *pmsg) { + RunTasks(); +} + +buzz::XmppReturnStatus XmppPump::SendStanza(const buzz::XmlElement *stanza) { + if (!AllChildrenDone()) + return client_->SendStanza(stanza); + return buzz::XMPP_RETURN_BADSTATE; +} + +} // namespace buzz + diff --git a/webrtc/libjingle/xmpp/xmpppump.h b/webrtc/libjingle/xmpp/xmpppump.h new file mode 100644 index 000000000..816329876 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmpppump.h @@ -0,0 +1,62 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPPUMP_H_ +#define WEBRTC_LIBJINGLE_XMPP_XMPPPUMP_H_ + +#include "webrtc/libjingle/xmpp/xmppclient.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" +#include "webrtc/base/messagequeue.h" +#include "webrtc/base/taskrunner.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" + +namespace buzz { + +// Simple xmpp pump + +class XmppPumpNotify { +public: + virtual ~XmppPumpNotify() {} + virtual void OnStateChange(buzz::XmppEngine::State state) = 0; +}; + +class XmppPump : public rtc::MessageHandler, public rtc::TaskRunner { +public: + XmppPump(buzz::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(); + + int64 CurrentTime(); + + void OnMessage(rtc::Message *pmsg); + + buzz::XmppReturnStatus SendStanza(const buzz::XmlElement *stanza); + +private: + buzz::XmppClient *client_; + buzz::XmppEngine::State state_; + buzz::XmppPumpNotify *notify_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_XMPPPUMP_H_ + diff --git a/webrtc/libjingle/xmpp/xmppsocket.cc b/webrtc/libjingle/xmpp/xmppsocket.cc new file mode 100644 index 000000000..d67a71fe4 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppsocket.cc @@ -0,0 +1,245 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "xmppsocket.h" + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include "webrtc/base/basicdefs.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/thread.h" +#ifdef FEATURE_ENABLE_SSL +#include "webrtc/base/ssladapter.h" +#endif + +#ifdef USE_SSLSTREAM +#include "webrtc/base/socketstream.h" +#ifdef FEATURE_ENABLE_SSL +#include "webrtc/base/sslstreamadapter.h" +#endif // FEATURE_ENABLE_SSL +#endif // USE_SSLSTREAM + +namespace buzz { + +XmppSocket::XmppSocket(buzz::TlsOptions tls) : cricket_socket_(NULL), + tls_(tls) { + state_ = buzz::AsyncSocket::STATE_CLOSED; +} + +void XmppSocket::CreateCricketSocket(int family) { + rtc::Thread* pth = rtc::Thread::Current(); + if (family == AF_UNSPEC) { + family = AF_INET; + } + rtc::AsyncSocket* socket = + pth->socketserver()->CreateAsyncSocket(family, SOCK_STREAM); +#ifndef USE_SSLSTREAM +#ifdef FEATURE_ENABLE_SSL + if (tls_ != buzz::TLS_DISABLED) { + socket = rtc::SSLAdapter::Create(socket); + } +#endif // FEATURE_ENABLE_SSL + cricket_socket_ = socket; + cricket_socket_->SignalReadEvent.connect(this, &XmppSocket::OnReadEvent); + cricket_socket_->SignalWriteEvent.connect(this, &XmppSocket::OnWriteEvent); + cricket_socket_->SignalConnectEvent.connect(this, + &XmppSocket::OnConnectEvent); + cricket_socket_->SignalCloseEvent.connect(this, &XmppSocket::OnCloseEvent); +#else // USE_SSLSTREAM + cricket_socket_ = socket; + stream_ = new rtc::SocketStream(cricket_socket_); +#ifdef FEATURE_ENABLE_SSL + if (tls_ != buzz::TLS_DISABLED) + stream_ = rtc::SSLStreamAdapter::Create(stream_); +#endif // FEATURE_ENABLE_SSL + stream_->SignalEvent.connect(this, &XmppSocket::OnEvent); +#endif // USE_SSLSTREAM +} + +XmppSocket::~XmppSocket() { + Close(); +#ifndef USE_SSLSTREAM + delete cricket_socket_; +#else // USE_SSLSTREAM + delete stream_; +#endif // USE_SSLSTREAM +} + +#ifndef USE_SSLSTREAM +void XmppSocket::OnReadEvent(rtc::AsyncSocket * socket) { + SignalRead(); +} + +void XmppSocket::OnWriteEvent(rtc::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_.Consume(written); + continue; + } + if (!cricket_socket_->IsBlocking()) + LOG(LS_ERROR) << "Send error: " << cricket_socket_->GetError(); + return; + } +} + +void XmppSocket::OnConnectEvent(rtc::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(); +} + +void XmppSocket::OnCloseEvent(rtc::AsyncSocket * socket, int error) { + SignalCloseEvent(error); +} + +#else // USE_SSLSTREAM + +void XmppSocket::OnEvent(rtc::StreamInterface* stream, + int events, int err) { + if ((events & rtc::SE_OPEN)) { +#if defined(FEATURE_ENABLE_SSL) + if (state_ == buzz::AsyncSocket::STATE_TLS_CONNECTING) { + state_ = buzz::AsyncSocket::STATE_TLS_OPEN; + SignalSSLConnected(); + events |= rtc::SE_WRITE; + } else +#endif + { + state_ = buzz::AsyncSocket::STATE_OPEN; + SignalConnected(); + } + } + if ((events & rtc::SE_READ)) + SignalRead(); + if ((events & rtc::SE_WRITE)) { + // Write bytes if there are any + while (buffer_.Length() != 0) { + rtc::StreamResult result; + size_t written; + int error; + result = stream_->Write(buffer_.Data(), buffer_.Length(), + &written, &error); + if (result == rtc::SR_ERROR) { + LOG(LS_ERROR) << "Send error: " << error; + return; + } + if (result == rtc::SR_BLOCK) + return; + ASSERT(result == rtc::SR_SUCCESS); + ASSERT(written > 0); + buffer_.Shift(written); + } + } + if ((events & rtc::SE_CLOSE)) + SignalCloseEvent(err); +} +#endif // USE_SSLSTREAM + +buzz::AsyncSocket::State XmppSocket::state() { + return state_; +} + +buzz::AsyncSocket::Error XmppSocket::error() { + return buzz::AsyncSocket::ERROR_NONE; +} + +int XmppSocket::GetError() { + return 0; +} + +bool XmppSocket::Connect(const rtc::SocketAddress& addr) { + if (cricket_socket_ == NULL) { + CreateCricketSocket(addr.family()); + } + if (cricket_socket_->Connect(addr) < 0) { + return cricket_socket_->IsBlocking(); + } + return true; +} + +bool XmppSocket::Read(char * data, size_t len, size_t* len_read) { +#ifndef USE_SSLSTREAM + int read = cricket_socket_->Recv(data, len); + if (read > 0) { + *len_read = (size_t)read; + return true; + } +#else // USE_SSLSTREAM + rtc::StreamResult result = stream_->Read(data, len, len_read, NULL); + if (result == rtc::SR_SUCCESS) + return true; +#endif // USE_SSLSTREAM + return false; +} + +bool XmppSocket::Write(const char * data, size_t len) { + buffer_.WriteBytes(data, len); +#ifndef USE_SSLSTREAM + OnWriteEvent(cricket_socket_); +#else // USE_SSLSTREAM + OnEvent(stream_, rtc::SE_WRITE, 0); +#endif // USE_SSLSTREAM + return true; +} + +bool XmppSocket::Close() { + if (state_ != buzz::AsyncSocket::STATE_OPEN) + return false; +#ifndef USE_SSLSTREAM + if (cricket_socket_->Close() == 0) { + state_ = buzz::AsyncSocket::STATE_CLOSED; + SignalClosed(); + return true; + } + return false; +#else // USE_SSLSTREAM + state_ = buzz::AsyncSocket::STATE_CLOSED; + stream_->Close(); + SignalClosed(); + return true; +#endif // USE_SSLSTREAM +} + +bool XmppSocket::StartTls(const std::string & domainname) { +#if defined(FEATURE_ENABLE_SSL) + if (tls_ == buzz::TLS_DISABLED) + return false; +#ifndef USE_SSLSTREAM + rtc::SSLAdapter* ssl_adapter = + static_cast(cricket_socket_); + if (ssl_adapter->StartSSL(domainname.c_str(), false) != 0) + return false; +#else // USE_SSLSTREAM + rtc::SSLStreamAdapter* ssl_stream = + static_cast(stream_); + if (ssl_stream->StartSSLWithServer(domainname.c_str()) != 0) + return false; +#endif // USE_SSLSTREAM + state_ = buzz::AsyncSocket::STATE_TLS_CONNECTING; + return true; +#else // !defined(FEATURE_ENABLE_SSL) + return false; +#endif // !defined(FEATURE_ENABLE_SSL) +} + +} // namespace buzz + diff --git a/webrtc/libjingle/xmpp/xmppsocket.h b/webrtc/libjingle/xmpp/xmppsocket.h new file mode 100644 index 000000000..527b23a5d --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppsocket.h @@ -0,0 +1,72 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPSOCKET_H_ +#define WEBRTC_LIBJINGLE_XMPP_XMPPSOCKET_H_ + +#include "webrtc/libjingle/xmpp/asyncsocket.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/base/asyncsocket.h" +#include "webrtc/base/bytebuffer.h" +#include "webrtc/base/sigslot.h" + +// The below define selects the SSLStreamAdapter implementation for +// SSL, as opposed to the SSLAdapter socket adapter. +// #define USE_SSLSTREAM + +namespace rtc { + class StreamInterface; + class SocketAddress; +}; +extern rtc::AsyncSocket* cricket_socket_; + +namespace buzz { + +class XmppSocket : public buzz::AsyncSocket, public sigslot::has_slots<> { +public: + XmppSocket(buzz::TlsOptions tls); + ~XmppSocket(); + + virtual buzz::AsyncSocket::State state(); + virtual buzz::AsyncSocket::Error error(); + virtual int GetError(); + + virtual bool Connect(const rtc::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); + + sigslot::signal1 SignalCloseEvent; + +private: + void CreateCricketSocket(int family); +#ifndef USE_SSLSTREAM + void OnReadEvent(rtc::AsyncSocket * socket); + void OnWriteEvent(rtc::AsyncSocket * socket); + void OnConnectEvent(rtc::AsyncSocket * socket); + void OnCloseEvent(rtc::AsyncSocket * socket, int error); +#else // USE_SSLSTREAM + void OnEvent(rtc::StreamInterface* stream, int events, int err); +#endif // USE_SSLSTREAM + + rtc::AsyncSocket * cricket_socket_; +#ifdef USE_SSLSTREAM + rtc::StreamInterface *stream_; +#endif // USE_SSLSTREAM + buzz::AsyncSocket::State state_; + rtc::ByteBuffer buffer_; + buzz::TlsOptions tls_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_XMPPSOCKET_H_ + diff --git a/webrtc/libjingle/xmpp/xmppstanzaparser.cc b/webrtc/libjingle/xmpp/xmppstanzaparser.cc new file mode 100644 index 000000000..035bb0b6f --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppstanzaparser.cc @@ -0,0 +1,89 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/xmppstanzaparser.h" + +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/base/common.h" +#ifdef EXPAT_RELATIVE_PATH +#include "expat.h" +#else +#include "third_party/expat/v2_0_1/Source/lib/expat.h" +#endif + +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) { + RTC_UNUSED(pctx); + RTC_UNUSED(errCode); + psph_->XmlError(); +} + +} diff --git a/webrtc/libjingle/xmpp/xmppstanzaparser.h b/webrtc/libjingle/xmpp/xmppstanzaparser.h new file mode 100644 index 000000000..3ad052e4f --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppstanzaparser.h @@ -0,0 +1,80 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPSTANZAPARSER_H_ +#define WEBRTC_LIBJINGLE_XMPP_XMPPSTANZAPARSER_H_ + +#include "webrtc/libjingle/xmllite/xmlbuilder.h" +#include "webrtc/libjingle/xmllite/xmlparser.h" + + +namespace buzz { + +class XmlElement; + +class XmppStanzaParseHandler { +public: + virtual ~XmppStanzaParseHandler() {} + 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 // WEBRTC_LIBJINGLE_XMPP_XMPPSTANZAPARSER_H_ diff --git a/webrtc/libjingle/xmpp/xmppstanzaparser_unittest.cc b/webrtc/libjingle/xmpp/xmppstanzaparser_unittest.cc new file mode 100644 index 000000000..65fcac9d6 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppstanzaparser_unittest.cc @@ -0,0 +1,175 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/xmppstanzaparser.h" +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" + +using buzz::QName; +using buzz::XmlElement; +using buzz::XmppStanzaParser; +using buzz::XmppStanzaParseHandler; + +class XmppStanzaParserTestHandler : public XmppStanzaParseHandler { + public: + virtual void StartStream(const XmlElement * element) { + ss_ << "START" << element->Str(); + } + virtual void Stanza(const XmlElement * element) { + ss_ << "STANZA" << element->Str(); + } + virtual void EndStream() { + ss_ << "END"; + } + virtual void XmlError() { + ss_ << "ERROR"; + } + + std::string Str() { + return ss_.str(); + } + + std::string StrClear() { + std::string result = ss_.str(); + ss_.str(""); + return result; + } + + private: + std::stringstream ss_; +}; + + +TEST(XmppStanzaParserTest, TestTrivial) { + XmppStanzaParserTestHandler handler; + XmppStanzaParser parser(&handler); + std::string fragment; + + fragment = ""; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("STARTEND", handler.StrClear()); +} + +TEST(XmppStanzaParserTest, TestStanzaAtATime) { + XmppStanzaParserTestHandler handler; + XmppStanzaParser parser(&handler); + std::string fragment; + + fragment = ""; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("START", handler.StrClear()); + + fragment = "hello"; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("STANZA" + "hello", handler.StrClear()); + + fragment = " SOME TEXT TO IGNORE "; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("", handler.StrClear()); + + fragment = ""; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("STANZA" + "", handler.StrClear()); + + fragment = ""; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("END", handler.StrClear()); +} + +TEST(XmppStanzaParserTest, TestFragmentedStanzas) { + XmppStanzaParserTestHandler handler; + XmppStanzaParser parser(&handler); + std::string fragment; + + fragment = "", handler.StrClear()); + + fragment = "lo IGNORE ME " + "" + "helloSTANZA", handler.StrClear()); + + fragment = "ream:stream>"; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("END", handler.StrClear()); +} + +TEST(XmppStanzaParserTest, TestReset) { + XmppStanzaParserTestHandler handler; + XmppStanzaParser parser(&handler); + std::string fragment; + + fragment = "", handler.StrClear()); + parser.Reset(); + + fragment = ""; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("START", handler.StrClear()); + + fragment = "hello"; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("STANZA" + "hello", handler.StrClear()); +} + +TEST(XmppStanzaParserTest, TestError) { + XmppStanzaParserTestHandler handler; + XmppStanzaParser parser(&handler); + std::string fragment; + + fragment = "<-foobar/>"; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("ERROR", handler.StrClear()); + + parser.Reset(); + fragment = ""; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("ERROR", handler.StrClear()); + parser.Reset(); + + fragment = "ns:stream='str'>hel"; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("ERROR", handler.StrClear()); + parser.Reset(); + + fragment = "" + ""; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("STARTSTANZA" + "ERROR", handler.StrClear()); +} diff --git a/webrtc/libjingle/xmpp/xmpptask.cc b/webrtc/libjingle/xmpp/xmpptask.cc new file mode 100644 index 000000000..09067058a --- /dev/null +++ b/webrtc/libjingle/xmpp/xmpptask.cc @@ -0,0 +1,158 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/xmppclient.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" + +namespace buzz { + +XmppClientInterface::XmppClientInterface() { +} + +XmppClientInterface::~XmppClientInterface() { +} + +XmppTask::XmppTask(XmppTaskParentInterface* parent, + XmppEngine::HandlerLevel level) + : XmppTaskBase(parent), stopped_(false) { +#ifdef _DEBUG + debug_force_timeout_ = false; +#endif + + id_ = GetClient()->NextId(); + GetClient()->AddXmppTask(this, level); + GetClient()->SignalDisconnected.connect(this, &XmppTask::OnDisconnect); +} + +XmppTask::~XmppTask() { + StopImpl(); +} + +void XmppTask::StopImpl() { + while (NextStanza() != NULL) {} + if (!stopped_) { + GetClient()->RemoveXmppTask(this); + GetClient()->SignalDisconnected.disconnect(this); + stopped_ = true; + } +} + +XmppReturnStatus XmppTask::SendStanza(const XmlElement* stanza) { + if (stopped_) + return XMPP_RETURN_BADSTATE; + return GetClient()->SendStanza(stanza); +} + +XmppReturnStatus XmppTask::SendStanzaError(const XmlElement* element_original, + XmppStanzaError code, + const std::string& text) { + if (stopped_) + return XMPP_RETURN_BADSTATE; + return GetClient()->SendStanzaError(element_original, code, text); +} + +void XmppTask::Stop() { + StopImpl(); + Task::Stop(); +} + +void XmppTask::OnDisconnect() { + Error(); +} + +void XmppTask::QueueStanza(const XmlElement* stanza) { +#ifdef _DEBUG + if (debug_force_timeout_) + return; +#endif + + 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.IsEmpty()) + 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; + + return MatchStanzaFrom(stanza, to); +} + +bool XmppTask::MatchStanzaFrom(const XmlElement* stanza, + const Jid& to) { + Jid from(stanza->Attr(QN_FROM)); + if (from == to) + return true; + + // We address the server as "", check if we are doing so here. + if (!to.IsEmpty()) + return false; + + // It is legal for the server to identify itself with "domain" or + // "myself@domain" + Jid me = GetClient()->jid(); + return (from == Jid(me.domain())) || (from == me.BareJid()); +} + +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/webrtc/libjingle/xmpp/xmpptask.h b/webrtc/libjingle/xmpp/xmpptask.h new file mode 100644 index 000000000..e27b265c9 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmpptask.h @@ -0,0 +1,172 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPTASK_H_ +#define WEBRTC_LIBJINGLE_XMPP_XMPPTASK_H_ + +#include +#include +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/task.h" +#include "webrtc/base/taskparent.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 XmppTask; + +// XmppClientInterface is an abstract interface for sending and +// handling stanzas. It can be implemented for unit tests or +// different network environments. It will usually be implemented by +// XmppClient. +class XmppClientInterface { + public: + XmppClientInterface(); + virtual ~XmppClientInterface(); + + virtual XmppEngine::State GetState() const = 0; + virtual const Jid& jid() const = 0; + virtual std::string NextId() = 0; + virtual XmppReturnStatus SendStanza(const XmlElement* stanza) = 0; + virtual XmppReturnStatus SendStanzaError(const XmlElement* original_stanza, + XmppStanzaError error_code, + const std::string& message) = 0; + virtual void AddXmppTask(XmppTask* task, XmppEngine::HandlerLevel level) = 0; + virtual void RemoveXmppTask(XmppTask* task) = 0; + sigslot::signal0<> SignalDisconnected; + + DISALLOW_EVIL_CONSTRUCTORS(XmppClientInterface); +}; + +// XmppTaskParentInterface is the interface require for any parent of +// an XmppTask. It needs, for example, a way to get an +// XmppClientInterface. + +// We really ought to inherit from a TaskParentInterface, but we tried +// that and it's way too complicated to change +// Task/TaskParent/TaskRunner. For now, this works. +class XmppTaskParentInterface : public rtc::Task { + public: + explicit XmppTaskParentInterface(rtc::TaskParent* parent) + : Task(parent) { + } + virtual ~XmppTaskParentInterface() {} + + virtual XmppClientInterface* GetClient() = 0; + + DISALLOW_EVIL_CONSTRUCTORS(XmppTaskParentInterface); +}; + +class XmppTaskBase : public XmppTaskParentInterface { + public: + explicit XmppTaskBase(XmppTaskParentInterface* parent) + : XmppTaskParentInterface(parent), + parent_(parent) { + } + virtual ~XmppTaskBase() {} + + virtual XmppClientInterface* GetClient() { + return parent_->GetClient(); + } + + protected: + XmppTaskParentInterface* parent_; + + DISALLOW_EVIL_CONSTRUCTORS(XmppTaskBase); +}; + +class XmppTask : public XmppTaskBase, + public XmppStanzaHandler, + public sigslot::has_slots<> +{ + public: + XmppTask(XmppTaskParentInterface* parent, + XmppEngine::HandlerLevel level = XmppEngine::HL_NONE); + virtual ~XmppTask(); + + std::string task_id() const { return id_; } + void set_task_id(std::string id) { id_ = id; } + +#ifdef _DEBUG + void set_debug_force_timeout(const bool f) { debug_force_timeout_ = f; } +#endif + + virtual bool HandleStanza(const XmlElement* stanza) { return false; } + + protected: + 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 void OnDisconnect(); + + virtual void QueueStanza(const XmlElement* stanza); + const XmlElement* NextStanza(); + + bool MatchStanzaFrom(const XmlElement* stanza, const Jid& match_jid); + + bool MatchResponseIq(const XmlElement* stanza, const Jid& to, + const std::string& task_id); + + static bool MatchRequestIq(const XmlElement* stanza, const std::string& type, + const QName& qn); + static XmlElement *MakeIqResult(const XmlElement* query); + static XmlElement *MakeIq(const std::string& type, + const Jid& to, const std::string& task_id); + + // Returns true if the task is under the specified rate limit and updates the + // rate limit accordingly + bool VerifyTaskRateLimit(const std::string task_name, int max_count, + int per_x_seconds); + +private: + void StopImpl(); + + bool stopped_; + std::deque stanza_queue_; + rtc::scoped_ptr next_stanza_; + std::string id_; + +#ifdef _DEBUG + bool debug_force_timeout_; +#endif +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_XMPPTASK_H_ diff --git a/webrtc/libjingle/xmpp/xmppthread.cc b/webrtc/libjingle/xmpp/xmppthread.cc new file mode 100644 index 000000000..faf21642e --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppthread.cc @@ -0,0 +1,69 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmpp/xmppthread.h" + +#include "webrtc/libjingle/xmpp/xmppauth.h" +#include "webrtc/libjingle/xmpp/xmppclientsettings.h" + +namespace buzz { +namespace { + +const uint32 MSG_LOGIN = 1; +const uint32 MSG_DISCONNECT = 2; + +struct LoginData: public rtc::MessageData { + LoginData(const buzz::XmppClientSettings& s) : xcs(s) {} + virtual ~LoginData() {} + + buzz::XmppClientSettings xcs; +}; + +} // namespace + +XmppThread::XmppThread() { + pump_ = new buzz::XmppPump(this); +} + +XmppThread::~XmppThread() { + Stop(); + delete pump_; +} + +void XmppThread::ProcessMessages(int cms) { + rtc::Thread::ProcessMessages(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(rtc::Message* pmsg) { + if (pmsg->message_id == MSG_LOGIN) { + ASSERT(pmsg->pdata != NULL); + LoginData* data = reinterpret_cast(pmsg->pdata); + pump_->DoLogin(data->xcs, new XmppSocket(buzz::TLS_DISABLED), + new XmppAuth()); + delete data; + } else if (pmsg->message_id == MSG_DISCONNECT) { + pump_->DoDisconnect(); + } else { + ASSERT(false); + } +} + +} // namespace buzz diff --git a/webrtc/libjingle/xmpp/xmppthread.h b/webrtc/libjingle/xmpp/xmppthread.h new file mode 100644 index 000000000..801792504 --- /dev/null +++ b/webrtc/libjingle/xmpp/xmppthread.h @@ -0,0 +1,45 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPTHREAD_H_ +#define WEBRTC_LIBJINGLE_XMPP_XMPPTHREAD_H_ + +#include "webrtc/libjingle/xmpp/xmppclientsettings.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpppump.h" +#include "webrtc/libjingle/xmpp/xmppsocket.h" +#include "webrtc/base/thread.h" + +namespace buzz { + +class XmppThread: + public rtc::Thread, buzz::XmppPumpNotify, rtc::MessageHandler { +public: + XmppThread(); + ~XmppThread(); + + buzz::XmppClient* client() { return pump_->client(); } + + void ProcessMessages(int cms); + + void Login(const buzz::XmppClientSettings & xcs); + void Disconnect(); + +private: + buzz::XmppPump* pump_; + + void OnStateChange(buzz::XmppEngine::State state); + void OnMessage(rtc::Message* pmsg); +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMPP_XMPPTHREAD_H_ + diff --git a/webrtc/p2p/OWNERS b/webrtc/p2p/OWNERS new file mode 100644 index 000000000..1a24a6a8a --- /dev/null +++ b/webrtc/p2p/OWNERS @@ -0,0 +1,13 @@ +henrika@webrtc.org +henrike@webrtc.org +henrikg@webrtc.org +hta@webrtc.org +jiayl@webrtc.org +juberti@webrtc.org +mflodman@webrtc.org +perkj@webrtc.org +pthatcher@webrtc.org +sergeyu@chromium.org +tommi@webrtc.org + +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/p2p/base/asyncstuntcpsocket.cc b/webrtc/p2p/base/asyncstuntcpsocket.cc new file mode 100644 index 000000000..2b1b69358 --- /dev/null +++ b/webrtc/p2p/base/asyncstuntcpsocket.cc @@ -0,0 +1,153 @@ +/* + * Copyright 2013 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/asyncstuntcpsocket.h" + +#include + +#include "webrtc/p2p/base/stun.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" + +namespace cricket { + +static const size_t kMaxPacketSize = 64 * 1024; + +typedef uint16 PacketLength; +static const size_t kPacketLenSize = sizeof(PacketLength); +static const size_t kPacketLenOffset = 2; +static const size_t kBufSize = kMaxPacketSize + kStunHeaderSize; +static const size_t kTurnChannelDataHdrSize = 4; + +inline bool IsStunMessage(uint16 msg_type) { + // The first two bits of a channel data message are 0b01. + return (msg_type & 0xC000) ? false : true; +} + +// AsyncStunTCPSocket +// Binds and connects |socket| and creates AsyncTCPSocket for +// it. Takes ownership of |socket|. Returns NULL if bind() or +// connect() fail (|socket| is destroyed in that case). +AsyncStunTCPSocket* AsyncStunTCPSocket::Create( + rtc::AsyncSocket* socket, + const rtc::SocketAddress& bind_address, + const rtc::SocketAddress& remote_address) { + return new AsyncStunTCPSocket(AsyncTCPSocketBase::ConnectSocket( + socket, bind_address, remote_address), false); +} + +AsyncStunTCPSocket::AsyncStunTCPSocket( + rtc::AsyncSocket* socket, bool listen) + : rtc::AsyncTCPSocketBase(socket, listen, kBufSize) { +} + +int AsyncStunTCPSocket::Send(const void *pv, size_t cb, + const rtc::PacketOptions& options) { + if (cb > kBufSize || cb < kPacketLenSize + kPacketLenOffset) { + SetError(EMSGSIZE); + return -1; + } + + // If we are blocking on send, then silently drop this packet + if (!IsOutBufferEmpty()) + return static_cast(cb); + + int pad_bytes; + size_t expected_pkt_len = GetExpectedLength(pv, cb, &pad_bytes); + + // Accepts only complete STUN/ChannelData packets. + if (cb != expected_pkt_len) + return -1; + + AppendToOutBuffer(pv, cb); + + ASSERT(pad_bytes < 4); + char padding[4] = {0}; + AppendToOutBuffer(padding, pad_bytes); + + int res = FlushOutBuffer(); + if (res <= 0) { + // drop packet if we made no progress + ClearOutBuffer(); + return res; + } + + // We claim to have sent the whole thing, even if we only sent partial + return static_cast(cb); +} + +void AsyncStunTCPSocket::ProcessInput(char* data, size_t* len) { + rtc::SocketAddress remote_addr(GetRemoteAddress()); + // STUN packet - First 4 bytes. Total header size is 20 bytes. + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |0 0| STUN Message Type | Message Length | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + // TURN ChannelData + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Channel Number | Length | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + while (true) { + // We need at least 4 bytes to read the STUN or ChannelData packet length. + if (*len < kPacketLenOffset + kPacketLenSize) + return; + + int pad_bytes; + size_t expected_pkt_len = GetExpectedLength(data, *len, &pad_bytes); + size_t actual_length = expected_pkt_len + pad_bytes; + + if (*len < actual_length) { + return; + } + + SignalReadPacket(this, data, expected_pkt_len, remote_addr, + rtc::CreatePacketTime(0)); + + *len -= actual_length; + if (*len > 0) { + memmove(data, data + actual_length, *len); + } + } +} + +void AsyncStunTCPSocket::HandleIncomingConnection( + rtc::AsyncSocket* socket) { + SignalNewConnection(this, new AsyncStunTCPSocket(socket, false)); +} + +size_t AsyncStunTCPSocket::GetExpectedLength(const void* data, size_t len, + int* pad_bytes) { + *pad_bytes = 0; + PacketLength pkt_len = + rtc::GetBE16(static_cast(data) + kPacketLenOffset); + size_t expected_pkt_len; + uint16 msg_type = rtc::GetBE16(data); + if (IsStunMessage(msg_type)) { + // STUN message. + expected_pkt_len = kStunHeaderSize + pkt_len; + } else { + // TURN ChannelData message. + expected_pkt_len = kTurnChannelDataHdrSize + pkt_len; + // From RFC 5766 section 11.5 + // Over TCP and TLS-over-TCP, the ChannelData message MUST be padded to + // a multiple of four bytes in order to ensure the alignment of + // subsequent messages. The padding is not reflected in the length + // field of the ChannelData message, so the actual size of a ChannelData + // message (including padding) is (4 + Length) rounded up to the nearest + // multiple of 4. Over UDP, the padding is not required but MAY be + // included. + if (expected_pkt_len % 4) + *pad_bytes = 4 - (expected_pkt_len % 4); + } + return expected_pkt_len; +} + +} // namespace cricket diff --git a/webrtc/p2p/base/asyncstuntcpsocket.h b/webrtc/p2p/base/asyncstuntcpsocket.h new file mode 100644 index 000000000..4f53b0311 --- /dev/null +++ b/webrtc/p2p/base/asyncstuntcpsocket.h @@ -0,0 +1,50 @@ +/* + * Copyright 2013 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_ASYNCSTUNTCPSOCKET_H_ +#define WEBRTC_P2P_BASE_ASYNCSTUNTCPSOCKET_H_ + +#include "webrtc/base/asynctcpsocket.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/socketfactory.h" + +namespace cricket { + +class AsyncStunTCPSocket : public rtc::AsyncTCPSocketBase { + public: + // Binds and connects |socket| and creates AsyncTCPSocket for + // it. Takes ownership of |socket|. Returns NULL if bind() or + // connect() fail (|socket| is destroyed in that case). + static AsyncStunTCPSocket* Create( + rtc::AsyncSocket* socket, + const rtc::SocketAddress& bind_address, + const rtc::SocketAddress& remote_address); + + AsyncStunTCPSocket(rtc::AsyncSocket* socket, bool listen); + virtual ~AsyncStunTCPSocket() {} + + virtual int Send(const void* pv, size_t cb, + const rtc::PacketOptions& options); + virtual void ProcessInput(char* data, size_t* len); + virtual void HandleIncomingConnection(rtc::AsyncSocket* socket); + + private: + // This method returns the message hdr + length written in the header. + // This method also returns the number of padding bytes needed/added to the + // turn message. |pad_bytes| should be used only when |is_turn| is true. + size_t GetExpectedLength(const void* data, size_t len, + int* pad_bytes); + + DISALLOW_EVIL_CONSTRUCTORS(AsyncStunTCPSocket); +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_ASYNCSTUNTCPSOCKET_H_ diff --git a/webrtc/p2p/base/asyncstuntcpsocket_unittest.cc b/webrtc/p2p/base/asyncstuntcpsocket_unittest.cc new file mode 100644 index 000000000..22c1b2690 --- /dev/null +++ b/webrtc/p2p/base/asyncstuntcpsocket_unittest.cc @@ -0,0 +1,263 @@ +/* + * Copyright 2013 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/asyncstuntcpsocket.h" +#include "webrtc/base/asyncsocket.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/virtualsocketserver.h" + +namespace cricket { + +static unsigned char kStunMessageWithZeroLength[] = { + 0x00, 0x01, 0x00, 0x00, // length of 0 (last 2 bytes) + 0x21, 0x12, 0xA4, 0x42, + '0', '1', '2', '3', + '4', '5', '6', '7', + '8', '9', 'a', 'b', +}; + + +static unsigned char kTurnChannelDataMessageWithZeroLength[] = { + 0x40, 0x00, 0x00, 0x00, // length of 0 (last 2 bytes) +}; + +static unsigned char kTurnChannelDataMessage[] = { + 0x40, 0x00, 0x00, 0x10, + 0x21, 0x12, 0xA4, 0x42, + '0', '1', '2', '3', + '4', '5', '6', '7', + '8', '9', 'a', 'b', +}; + +static unsigned char kStunMessageWithInvalidLength[] = { + 0x00, 0x01, 0x00, 0x10, + 0x21, 0x12, 0xA4, 0x42, + '0', '1', '2', '3', + '4', '5', '6', '7', + '8', '9', 'a', 'b', +}; + +static unsigned char kTurnChannelDataMessageWithInvalidLength[] = { + 0x80, 0x00, 0x00, 0x20, + 0x21, 0x12, 0xA4, 0x42, + '0', '1', '2', '3', + '4', '5', '6', '7', + '8', '9', 'a', 'b', +}; + +static unsigned char kTurnChannelDataMessageWithOddLength[] = { + 0x40, 0x00, 0x00, 0x05, + 0x21, 0x12, 0xA4, 0x42, + '0', +}; + + +static const rtc::SocketAddress kClientAddr("11.11.11.11", 0); +static const rtc::SocketAddress kServerAddr("22.22.22.22", 0); + +class AsyncStunTCPSocketTest : public testing::Test, + public sigslot::has_slots<> { + protected: + AsyncStunTCPSocketTest() + : vss_(new rtc::VirtualSocketServer(NULL)), + ss_scope_(vss_.get()) { + } + + virtual void SetUp() { + CreateSockets(); + } + + void CreateSockets() { + rtc::AsyncSocket* server = vss_->CreateAsyncSocket( + kServerAddr.family(), SOCK_STREAM); + server->Bind(kServerAddr); + recv_socket_.reset(new AsyncStunTCPSocket(server, true)); + recv_socket_->SignalNewConnection.connect( + this, &AsyncStunTCPSocketTest::OnNewConnection); + + rtc::AsyncSocket* client = vss_->CreateAsyncSocket( + kClientAddr.family(), SOCK_STREAM); + send_socket_.reset(AsyncStunTCPSocket::Create( + client, kClientAddr, recv_socket_->GetLocalAddress())); + ASSERT_TRUE(send_socket_.get() != NULL); + vss_->ProcessMessagesUntilIdle(); + } + + void OnReadPacket(rtc::AsyncPacketSocket* socket, const char* data, + size_t len, const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time) { + recv_packets_.push_back(std::string(data, len)); + } + + void OnNewConnection(rtc::AsyncPacketSocket* server, + rtc::AsyncPacketSocket* new_socket) { + listen_socket_.reset(new_socket); + new_socket->SignalReadPacket.connect( + this, &AsyncStunTCPSocketTest::OnReadPacket); + } + + bool Send(const void* data, size_t len) { + rtc::PacketOptions options; + size_t ret = send_socket_->Send( + reinterpret_cast(data), len, options); + vss_->ProcessMessagesUntilIdle(); + return (ret == len); + } + + bool CheckData(const void* data, int len) { + bool ret = false; + if (recv_packets_.size()) { + std::string packet = recv_packets_.front(); + recv_packets_.pop_front(); + ret = (memcmp(data, packet.c_str(), len) == 0); + } + return ret; + } + + rtc::scoped_ptr vss_; + rtc::SocketServerScope ss_scope_; + rtc::scoped_ptr send_socket_; + rtc::scoped_ptr recv_socket_; + rtc::scoped_ptr listen_socket_; + std::list recv_packets_; +}; + +// Testing a stun packet sent/recv properly. +TEST_F(AsyncStunTCPSocketTest, TestSingleStunPacket) { + EXPECT_TRUE(Send(kStunMessageWithZeroLength, + sizeof(kStunMessageWithZeroLength))); + EXPECT_EQ(1u, recv_packets_.size()); + EXPECT_TRUE(CheckData(kStunMessageWithZeroLength, + sizeof(kStunMessageWithZeroLength))); +} + +// Verify sending multiple packets. +TEST_F(AsyncStunTCPSocketTest, TestMultipleStunPackets) { + EXPECT_TRUE(Send(kStunMessageWithZeroLength, + sizeof(kStunMessageWithZeroLength))); + EXPECT_TRUE(Send(kStunMessageWithZeroLength, + sizeof(kStunMessageWithZeroLength))); + EXPECT_TRUE(Send(kStunMessageWithZeroLength, + sizeof(kStunMessageWithZeroLength))); + EXPECT_TRUE(Send(kStunMessageWithZeroLength, + sizeof(kStunMessageWithZeroLength))); + EXPECT_EQ(4u, recv_packets_.size()); +} + +// Verifying TURN channel data message with zero length. +TEST_F(AsyncStunTCPSocketTest, TestTurnChannelDataWithZeroLength) { + EXPECT_TRUE(Send(kTurnChannelDataMessageWithZeroLength, + sizeof(kTurnChannelDataMessageWithZeroLength))); + EXPECT_EQ(1u, recv_packets_.size()); + EXPECT_TRUE(CheckData(kTurnChannelDataMessageWithZeroLength, + sizeof(kTurnChannelDataMessageWithZeroLength))); +} + +// Verifying TURN channel data message. +TEST_F(AsyncStunTCPSocketTest, TestTurnChannelData) { + EXPECT_TRUE(Send(kTurnChannelDataMessage, + sizeof(kTurnChannelDataMessage))); + EXPECT_EQ(1u, recv_packets_.size()); + EXPECT_TRUE(CheckData(kTurnChannelDataMessage, + sizeof(kTurnChannelDataMessage))); +} + +// Verifying TURN channel messages which needs padding handled properly. +TEST_F(AsyncStunTCPSocketTest, TestTurnChannelDataPadding) { + EXPECT_TRUE(Send(kTurnChannelDataMessageWithOddLength, + sizeof(kTurnChannelDataMessageWithOddLength))); + EXPECT_EQ(1u, recv_packets_.size()); + EXPECT_TRUE(CheckData(kTurnChannelDataMessageWithOddLength, + sizeof(kTurnChannelDataMessageWithOddLength))); +} + +// Verifying stun message with invalid length. +TEST_F(AsyncStunTCPSocketTest, TestStunInvalidLength) { + EXPECT_FALSE(Send(kStunMessageWithInvalidLength, + sizeof(kStunMessageWithInvalidLength))); + EXPECT_EQ(0u, recv_packets_.size()); + + // Modify the message length to larger value. + kStunMessageWithInvalidLength[2] = 0xFF; + kStunMessageWithInvalidLength[3] = 0xFF; + EXPECT_FALSE(Send(kStunMessageWithInvalidLength, + sizeof(kStunMessageWithInvalidLength))); + + // Modify the message length to smaller value. + kStunMessageWithInvalidLength[2] = 0x00; + kStunMessageWithInvalidLength[3] = 0x01; + EXPECT_FALSE(Send(kStunMessageWithInvalidLength, + sizeof(kStunMessageWithInvalidLength))); +} + +// Verifying TURN channel data message with invalid length. +TEST_F(AsyncStunTCPSocketTest, TestTurnChannelDataWithInvalidLength) { + EXPECT_FALSE(Send(kTurnChannelDataMessageWithInvalidLength, + sizeof(kTurnChannelDataMessageWithInvalidLength))); + // Modify the length to larger value. + kTurnChannelDataMessageWithInvalidLength[2] = 0xFF; + kTurnChannelDataMessageWithInvalidLength[3] = 0xF0; + EXPECT_FALSE(Send(kTurnChannelDataMessageWithInvalidLength, + sizeof(kTurnChannelDataMessageWithInvalidLength))); + + // Modify the length to smaller value. + kTurnChannelDataMessageWithInvalidLength[2] = 0x00; + kTurnChannelDataMessageWithInvalidLength[3] = 0x00; + EXPECT_FALSE(Send(kTurnChannelDataMessageWithInvalidLength, + sizeof(kTurnChannelDataMessageWithInvalidLength))); +} + +// Verifying a small buffer handled (dropped) properly. This will be +// a common one for both stun and turn. +TEST_F(AsyncStunTCPSocketTest, TestTooSmallMessageBuffer) { + char data[1]; + EXPECT_FALSE(Send(data, sizeof(data))); +} + +// Verifying a legal large turn message. +TEST_F(AsyncStunTCPSocketTest, TestMaximumSizeTurnPacket) { + // We have problem in getting the SignalWriteEvent from the virtual socket + // server. So increasing the send buffer to 64k. + // TODO(mallinath) - Remove this setting after we fix vss issue. + vss_->set_send_buffer_capacity(64 * 1024); + unsigned char packet[65539]; + packet[0] = 0x40; + packet[1] = 0x00; + packet[2] = 0xFF; + packet[3] = 0xFF; + EXPECT_TRUE(Send(packet, sizeof(packet))); +} + +// Verifying a legal large stun message. +TEST_F(AsyncStunTCPSocketTest, TestMaximumSizeStunPacket) { + // We have problem in getting the SignalWriteEvent from the virtual socket + // server. So increasing the send buffer to 64k. + // TODO(mallinath) - Remove this setting after we fix vss issue. + vss_->set_send_buffer_capacity(64 * 1024); + unsigned char packet[65552]; + packet[0] = 0x00; + packet[1] = 0x01; + packet[2] = 0xFF; + packet[3] = 0xFC; + EXPECT_TRUE(Send(packet, sizeof(packet))); +} + +// Investigate why WriteEvent is not signaled from VSS. +TEST_F(AsyncStunTCPSocketTest, DISABLED_TestWithSmallSendBuffer) { + vss_->set_send_buffer_capacity(1); + Send(kTurnChannelDataMessageWithOddLength, + sizeof(kTurnChannelDataMessageWithOddLength)); + EXPECT_EQ(1u, recv_packets_.size()); + EXPECT_TRUE(CheckData(kTurnChannelDataMessageWithOddLength, + sizeof(kTurnChannelDataMessageWithOddLength))); +} + +} // namespace cricket diff --git a/webrtc/p2p/base/basicpacketsocketfactory.cc b/webrtc/p2p/base/basicpacketsocketfactory.cc new file mode 100644 index 000000000..06dfe76e5 --- /dev/null +++ b/webrtc/p2p/base/basicpacketsocketfactory.cc @@ -0,0 +1,204 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/basicpacketsocketfactory.h" + +#include "webrtc/p2p/base/asyncstuntcpsocket.h" +#include "webrtc/p2p/base/stun.h" +#include "webrtc/base/asynctcpsocket.h" +#include "webrtc/base/asyncudpsocket.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/nethelpers.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/socketadapters.h" +#include "webrtc/base/ssladapter.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +BasicPacketSocketFactory::BasicPacketSocketFactory() + : thread_(Thread::Current()), + socket_factory_(NULL) { +} + +BasicPacketSocketFactory::BasicPacketSocketFactory(Thread* thread) + : thread_(thread), + socket_factory_(NULL) { +} + +BasicPacketSocketFactory::BasicPacketSocketFactory( + SocketFactory* socket_factory) + : thread_(NULL), + socket_factory_(socket_factory) { +} + +BasicPacketSocketFactory::~BasicPacketSocketFactory() { +} + +AsyncPacketSocket* BasicPacketSocketFactory::CreateUdpSocket( + const SocketAddress& address, int min_port, int max_port) { + // UDP sockets are simple. + rtc::AsyncSocket* socket = + socket_factory()->CreateAsyncSocket( + address.family(), SOCK_DGRAM); + if (!socket) { + return NULL; + } + if (BindSocket(socket, address, min_port, max_port) < 0) { + LOG(LS_ERROR) << "UDP bind failed with error " + << socket->GetError(); + delete socket; + return NULL; + } + return new rtc::AsyncUDPSocket(socket); +} + +AsyncPacketSocket* BasicPacketSocketFactory::CreateServerTcpSocket( + const SocketAddress& local_address, int min_port, int max_port, int opts) { + + // Fail if TLS is required. + if (opts & PacketSocketFactory::OPT_TLS) { + LOG(LS_ERROR) << "TLS support currently is not available."; + return NULL; + } + + rtc::AsyncSocket* socket = + socket_factory()->CreateAsyncSocket(local_address.family(), + SOCK_STREAM); + if (!socket) { + return NULL; + } + + if (BindSocket(socket, local_address, min_port, max_port) < 0) { + LOG(LS_ERROR) << "TCP bind failed with error " + << socket->GetError(); + delete socket; + return NULL; + } + + // If using SSLTCP, wrap the TCP socket in a pseudo-SSL socket. + if (opts & PacketSocketFactory::OPT_SSLTCP) { + ASSERT(!(opts & PacketSocketFactory::OPT_TLS)); + socket = new rtc::AsyncSSLSocket(socket); + } + + // Set TCP_NODELAY (via OPT_NODELAY) for improved performance. + // See http://go/gtalktcpnodelayexperiment + socket->SetOption(rtc::Socket::OPT_NODELAY, 1); + + if (opts & PacketSocketFactory::OPT_STUN) + return new cricket::AsyncStunTCPSocket(socket, true); + + return new rtc::AsyncTCPSocket(socket, true); +} + +AsyncPacketSocket* BasicPacketSocketFactory::CreateClientTcpSocket( + const SocketAddress& local_address, const SocketAddress& remote_address, + const ProxyInfo& proxy_info, const std::string& user_agent, int opts) { + + rtc::AsyncSocket* socket = + socket_factory()->CreateAsyncSocket(local_address.family(), SOCK_STREAM); + if (!socket) { + return NULL; + } + + if (BindSocket(socket, local_address, 0, 0) < 0) { + LOG(LS_ERROR) << "TCP bind failed with error " + << socket->GetError(); + delete socket; + return NULL; + } + + // If using a proxy, wrap the socket in a proxy socket. + if (proxy_info.type == rtc::PROXY_SOCKS5) { + socket = new rtc::AsyncSocksProxySocket( + socket, proxy_info.address, proxy_info.username, proxy_info.password); + } else if (proxy_info.type == rtc::PROXY_HTTPS) { + socket = new rtc::AsyncHttpsProxySocket( + socket, user_agent, proxy_info.address, + proxy_info.username, proxy_info.password); + } + + // If using TLS, wrap the socket in an SSL adapter. + if (opts & PacketSocketFactory::OPT_TLS) { + ASSERT(!(opts & PacketSocketFactory::OPT_SSLTCP)); + + rtc::SSLAdapter* ssl_adapter = rtc::SSLAdapter::Create(socket); + if (!ssl_adapter) { + return NULL; + } + + socket = ssl_adapter; + + if (ssl_adapter->StartSSL(remote_address.hostname().c_str(), false) != 0) { + delete ssl_adapter; + return NULL; + } + + // If using SSLTCP, wrap the TCP socket in a pseudo-SSL socket. + } else if (opts & PacketSocketFactory::OPT_SSLTCP) { + ASSERT(!(opts & PacketSocketFactory::OPT_TLS)); + socket = new rtc::AsyncSSLSocket(socket); + } + + if (socket->Connect(remote_address) < 0) { + LOG(LS_ERROR) << "TCP connect failed with error " + << socket->GetError(); + delete socket; + return NULL; + } + + // Finally, wrap that socket in a TCP or STUN TCP packet socket. + AsyncPacketSocket* tcp_socket; + if (opts & PacketSocketFactory::OPT_STUN) { + tcp_socket = new cricket::AsyncStunTCPSocket(socket, false); + } else { + tcp_socket = new rtc::AsyncTCPSocket(socket, false); + } + + // Set TCP_NODELAY (via OPT_NODELAY) for improved performance. + // See http://go/gtalktcpnodelayexperiment + tcp_socket->SetOption(rtc::Socket::OPT_NODELAY, 1); + + return tcp_socket; +} + +AsyncResolverInterface* BasicPacketSocketFactory::CreateAsyncResolver() { + return new rtc::AsyncResolver(); +} + +int BasicPacketSocketFactory::BindSocket( + AsyncSocket* socket, const SocketAddress& local_address, + int min_port, int max_port) { + int ret = -1; + if (min_port == 0 && max_port == 0) { + // If there's no port range, let the OS pick a port for us. + ret = socket->Bind(local_address); + } else { + // Otherwise, try to find a port in the provided range. + for (int port = min_port; ret < 0 && port <= max_port; ++port) { + ret = socket->Bind(rtc::SocketAddress(local_address.ipaddr(), + port)); + } + } + return ret; +} + +SocketFactory* BasicPacketSocketFactory::socket_factory() { + if (thread_) { + ASSERT(thread_ == Thread::Current()); + return thread_->socketserver(); + } else { + return socket_factory_; + } +} + +} // namespace rtc diff --git a/webrtc/p2p/base/basicpacketsocketfactory.h b/webrtc/p2p/base/basicpacketsocketfactory.h new file mode 100644 index 000000000..fb3a52691 --- /dev/null +++ b/webrtc/p2p/base/basicpacketsocketfactory.h @@ -0,0 +1,51 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_BASICPACKETSOCKETFACTORY_H_ +#define WEBRTC_P2P_BASE_BASICPACKETSOCKETFACTORY_H_ + +#include "webrtc/p2p/base/packetsocketfactory.h" + +namespace rtc { + +class AsyncSocket; +class SocketFactory; +class Thread; + +class BasicPacketSocketFactory : public PacketSocketFactory { + public: + BasicPacketSocketFactory(); + explicit BasicPacketSocketFactory(Thread* thread); + explicit BasicPacketSocketFactory(SocketFactory* socket_factory); + virtual ~BasicPacketSocketFactory(); + + virtual AsyncPacketSocket* CreateUdpSocket( + const SocketAddress& local_address, int min_port, int max_port); + virtual AsyncPacketSocket* CreateServerTcpSocket( + const SocketAddress& local_address, int min_port, int max_port, int opts); + virtual AsyncPacketSocket* CreateClientTcpSocket( + const SocketAddress& local_address, const SocketAddress& remote_address, + const ProxyInfo& proxy_info, const std::string& user_agent, int opts); + + virtual AsyncResolverInterface* CreateAsyncResolver(); + + private: + int BindSocket(AsyncSocket* socket, const SocketAddress& local_address, + int min_port, int max_port); + + SocketFactory* socket_factory(); + + Thread* thread_; + SocketFactory* socket_factory_; +}; + +} // namespace rtc + +#endif // WEBRTC_P2P_BASE_BASICPACKETSOCKETFACTORY_H_ diff --git a/webrtc/p2p/base/candidate.h b/webrtc/p2p/base/candidate.h new file mode 100644 index 000000000..72bc69ef2 --- /dev/null +++ b/webrtc/p2p/base/candidate.h @@ -0,0 +1,212 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_CANDIDATE_H_ +#define WEBRTC_P2P_BASE_CANDIDATE_H_ + +#include +#include + +#include +#include +#include + +#include "webrtc/p2p/base/constants.h" +#include "webrtc/base/basictypes.h" +#include "webrtc/base/socketaddress.h" + +namespace cricket { + +// Candidate for ICE based connection discovery. + +class Candidate { + public: + // TODO: Match the ordering and param list as per RFC 5245 + // candidate-attribute syntax. http://tools.ietf.org/html/rfc5245#section-15.1 + Candidate() : component_(0), priority_(0), generation_(0) {} + Candidate(const std::string& id, int component, const std::string& protocol, + const rtc::SocketAddress& address, uint32 priority, + const std::string& username, const std::string& password, + const std::string& type, const std::string& network_name, + uint32 generation, const std::string& foundation) + : id_(id), component_(component), protocol_(protocol), address_(address), + priority_(priority), username_(username), password_(password), + type_(type), network_name_(network_name), generation_(generation), + foundation_(foundation) { + } + + const std::string & id() const { return id_; } + void set_id(const std::string & id) { id_ = id; } + + int component() const { return component_; } + void set_component(int component) { component_ = component; } + + const std::string & protocol() const { return protocol_; } + void set_protocol(const std::string & protocol) { protocol_ = protocol; } + + const rtc::SocketAddress & address() const { return address_; } + void set_address(const rtc::SocketAddress & address) { + address_ = address; + } + + uint32 priority() const { return priority_; } + void set_priority(const uint32 priority) { priority_ = priority; } + +// void set_type_preference(uint32 type_preference) { +// priority_ = GetPriority(type_preference); +// } + + // Maps old preference (which was 0.0-1.0) to match priority (which + // is 0-2^32-1) to to match RFC 5245, section 4.1.2.1. Also see + // https://docs.google.com/a/google.com/document/d/ + // 1iNQDiwDKMh0NQOrCqbj3DKKRT0Dn5_5UJYhmZO-t7Uc/edit + float preference() const { + // The preference value is clamped to two decimal precision. + return static_cast(((priority_ >> 24) * 100 / 127) / 100.0); + } + + void set_preference(float preference) { + // Limiting priority to UINT_MAX when value exceeds uint32 max. + // This can happen for e.g. when preference = 3. + uint64 prio_val = static_cast(preference * 127) << 24; + priority_ = static_cast( + rtc::_min(prio_val, static_cast(UINT_MAX))); + } + + 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_; + } + + const std::string& foundation() const { + return foundation_; + } + + void set_foundation(const std::string& foundation) { + foundation_ = foundation; + } + + const rtc::SocketAddress & related_address() const { + return related_address_; + } + void set_related_address( + const rtc::SocketAddress & related_address) { + related_address_ = related_address; + } + const std::string& tcptype() const { return tcptype_; } + void set_tcptype(const std::string& tcptype){ + tcptype_ = tcptype; + } + + // 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 priority, since that should be the same if the rest is (and it's + // a float so equality checking is always worrisome). + return (id_ == c.id_) && + (component_ == c.component_) && + (protocol_ == c.protocol_) && + (address_ == c.address_) && + (username_ == c.username_) && + (password_ == c.password_) && + (type_ == c.type_) && + (generation_ == c.generation_) && + (foundation_ == c.foundation_) && + (related_address_ == c.related_address_); + } + + std::string ToString() const { + return ToStringInternal(false); + } + + std::string ToSensitiveString() const { + return ToStringInternal(true); + } + + uint32 GetPriority(uint32 type_preference, + int network_adapter_preference, + int relay_preference) const { + // RFC 5245 - 4.1.2.1. + // priority = (2^24)*(type preference) + + // (2^8)*(local preference) + + // (2^0)*(256 - component ID) + + // |local_preference| length is 2 bytes, 0-65535 inclusive. + // In our implemenation we will partion local_preference into + // 0 1 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | NIC Pref | Addr Pref | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // NIC Type - Type of the network adapter e.g. 3G/Wifi/Wired. + // Addr Pref - Address preference value as per RFC 3484. + // local preference = (NIC Type << 8 | Addr_Pref) - relay preference. + + int addr_pref = IPAddressPrecedence(address_.ipaddr()); + int local_preference = ((network_adapter_preference << 8) | addr_pref) + + relay_preference; + + return (type_preference << 24) | + (local_preference << 8) | + (256 - component_); + } + + private: + std::string ToStringInternal(bool sensitive) const { + std::ostringstream ost; + std::string address = sensitive ? address_.ToSensitiveString() : + address_.ToString(); + ost << "Cand[" << foundation_ << ":" << component_ << ":" + << protocol_ << ":" << priority_ << ":" + << address << ":" << type_ << ":" << related_address_ << ":" + << username_ << ":" << password_ << "]"; + return ost.str(); + } + + std::string id_; + int component_; + std::string protocol_; + rtc::SocketAddress address_; + uint32 priority_; + std::string username_; + std::string password_; + std::string type_; + std::string network_name_; + uint32 generation_; + std::string foundation_; + rtc::SocketAddress related_address_; + std::string tcptype_; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_CANDIDATE_H_ diff --git a/webrtc/p2p/base/common.h b/webrtc/p2p/base/common.h new file mode 100644 index 000000000..8a3178c80 --- /dev/null +++ b/webrtc/p2p/base/common.h @@ -0,0 +1,20 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_COMMON_H_ +#define WEBRTC_P2P_BASE_COMMON_H_ + +#include "webrtc/base/logging.h" + +// Common log description format for jingle messages +#define LOG_J(sev, obj) LOG(sev) << "Jingle:" << obj->ToString() << ": " +#define LOG_JV(sev, obj) LOG_V(sev) << "Jingle:" << obj->ToString() << ": " + +#endif // WEBRTC_P2P_BASE_COMMON_H_ diff --git a/webrtc/p2p/base/constants.cc b/webrtc/p2p/base/constants.cc new file mode 100644 index 000000000..84c9ea260 --- /dev/null +++ b/webrtc/p2p/base/constants.cc @@ -0,0 +1,257 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/constants.h" + +#include + +#include "webrtc/libjingle/xmllite/qname.h" + +namespace cricket { + +const char NS_EMPTY[] = ""; +const char NS_JINGLE[] = "urn:xmpp:jingle:1"; +const char NS_JINGLE_DRAFT[] = "google:jingle"; +const char NS_GINGLE[] = "http://www.google.com/session"; + +// actions (aka or ) +const buzz::StaticQName QN_ACTION = { NS_EMPTY, "action" }; +const char LN_INITIATOR[] = "initiator"; +const buzz::StaticQName QN_INITIATOR = { NS_EMPTY, LN_INITIATOR }; +const buzz::StaticQName QN_CREATOR = { NS_EMPTY, "creator" }; + +const buzz::StaticQName QN_JINGLE = { NS_JINGLE, "jingle" }; +const buzz::StaticQName QN_JINGLE_CONTENT = { NS_JINGLE, "content" }; +const buzz::StaticQName QN_JINGLE_CONTENT_NAME = { NS_EMPTY, "name" }; +const buzz::StaticQName QN_JINGLE_CONTENT_MEDIA = { NS_EMPTY, "media" }; +const buzz::StaticQName QN_JINGLE_REASON = { NS_JINGLE, "reason" }; +const buzz::StaticQName QN_JINGLE_DRAFT_GROUP = { NS_JINGLE_DRAFT, "group" }; +const buzz::StaticQName QN_JINGLE_DRAFT_GROUP_TYPE = { NS_EMPTY, "type" }; +const char JINGLE_CONTENT_MEDIA_AUDIO[] = "audio"; +const char JINGLE_CONTENT_MEDIA_VIDEO[] = "video"; +const char JINGLE_CONTENT_MEDIA_DATA[] = "data"; +const char JINGLE_ACTION_SESSION_INITIATE[] = "session-initiate"; +const char JINGLE_ACTION_SESSION_INFO[] = "session-info"; +const char JINGLE_ACTION_SESSION_ACCEPT[] = "session-accept"; +const char JINGLE_ACTION_SESSION_TERMINATE[] = "session-terminate"; +const char JINGLE_ACTION_TRANSPORT_INFO[] = "transport-info"; +const char JINGLE_ACTION_TRANSPORT_ACCEPT[] = "transport-accept"; +const char JINGLE_ACTION_DESCRIPTION_INFO[] = "description-info"; + +const buzz::StaticQName QN_GINGLE_SESSION = { NS_GINGLE, "session" }; +const char GINGLE_ACTION_INITIATE[] = "initiate"; +const char GINGLE_ACTION_INFO[] = "info"; +const char GINGLE_ACTION_ACCEPT[] = "accept"; +const char GINGLE_ACTION_REJECT[] = "reject"; +const char GINGLE_ACTION_TERMINATE[] = "terminate"; +const char GINGLE_ACTION_CANDIDATES[] = "candidates"; +const char GINGLE_ACTION_UPDATE[] = "update"; + +const char LN_ERROR[] = "error"; +const buzz::StaticQName QN_GINGLE_REDIRECT = { NS_GINGLE, "redirect" }; +const char STR_REDIRECT_PREFIX[] = "xmpp:"; + +// Session Contents (aka Gingle +// or Jingle ) +const char LN_DESCRIPTION[] = "description"; +const char LN_PAYLOADTYPE[] = "payload-type"; +const buzz::StaticQName QN_ID = { NS_EMPTY, "id" }; +const buzz::StaticQName QN_SID = { NS_EMPTY, "sid" }; +const buzz::StaticQName QN_NAME = { NS_EMPTY, "name" }; +const buzz::StaticQName QN_CLOCKRATE = { NS_EMPTY, "clockrate" }; +const buzz::StaticQName QN_BITRATE = { NS_EMPTY, "bitrate" }; +const buzz::StaticQName QN_CHANNELS = { NS_EMPTY, "channels" }; +const buzz::StaticQName QN_WIDTH = { NS_EMPTY, "width" }; +const buzz::StaticQName QN_HEIGHT = { NS_EMPTY, "height" }; +const buzz::StaticQName QN_FRAMERATE = { NS_EMPTY, "framerate" }; +const char LN_NAME[] = "name"; +const char LN_VALUE[] = "value"; +const buzz::StaticQName QN_PAYLOADTYPE_PARAMETER_NAME = { NS_EMPTY, LN_NAME }; +const buzz::StaticQName QN_PAYLOADTYPE_PARAMETER_VALUE = { NS_EMPTY, LN_VALUE }; +const char PAYLOADTYPE_PARAMETER_BITRATE[] = "bitrate"; +const char PAYLOADTYPE_PARAMETER_HEIGHT[] = "height"; +const char PAYLOADTYPE_PARAMETER_WIDTH[] = "width"; +const char PAYLOADTYPE_PARAMETER_FRAMERATE[] = "framerate"; +const char LN_BANDWIDTH[] = "bandwidth"; + +const char CN_AUDIO[] = "audio"; +const char CN_VIDEO[] = "video"; +const char CN_DATA[] = "data"; +const char CN_OTHER[] = "main"; +// other SDP related strings +const char GROUP_TYPE_BUNDLE[] = "BUNDLE"; + +const char NS_JINGLE_RTP[] = "urn:xmpp:jingle:apps:rtp:1"; +const buzz::StaticQName QN_JINGLE_RTP_CONTENT = + { NS_JINGLE_RTP, LN_DESCRIPTION }; +const buzz::StaticQName QN_SSRC = { NS_EMPTY, "ssrc" }; +const buzz::StaticQName QN_JINGLE_RTP_PAYLOADTYPE = + { NS_JINGLE_RTP, LN_PAYLOADTYPE }; +const buzz::StaticQName QN_JINGLE_RTP_BANDWIDTH = + { NS_JINGLE_RTP, LN_BANDWIDTH }; +const buzz::StaticQName QN_JINGLE_RTCP_MUX = { NS_JINGLE_RTP, "rtcp-mux" }; +const buzz::StaticQName QN_JINGLE_RTCP_FB = { NS_JINGLE_RTP, "rtcp-fb" }; +const buzz::StaticQName QN_SUBTYPE = { NS_EMPTY, "subtype" }; +const buzz::StaticQName QN_PARAMETER = { NS_JINGLE_RTP, "parameter" }; +const buzz::StaticQName QN_JINGLE_RTP_HDREXT = + { NS_JINGLE_RTP, "rtp-hdrext" }; +const buzz::StaticQName QN_URI = { NS_EMPTY, "uri" }; + +const char NS_JINGLE_DRAFT_SCTP[] = "google:jingle:sctp"; +const buzz::StaticQName QN_JINGLE_DRAFT_SCTP_CONTENT = + { NS_JINGLE_DRAFT_SCTP, LN_DESCRIPTION }; +const buzz::StaticQName QN_JINGLE_DRAFT_SCTP_STREAM = + { NS_JINGLE_DRAFT_SCTP, "stream" }; + +const char NS_GINGLE_AUDIO[] = "http://www.google.com/session/phone"; +const buzz::StaticQName QN_GINGLE_AUDIO_CONTENT = + { NS_GINGLE_AUDIO, LN_DESCRIPTION }; +const buzz::StaticQName QN_GINGLE_AUDIO_PAYLOADTYPE = + { NS_GINGLE_AUDIO, LN_PAYLOADTYPE }; +const buzz::StaticQName QN_GINGLE_AUDIO_SRCID = { NS_GINGLE_AUDIO, "src-id" }; +const char NS_GINGLE_VIDEO[] = "http://www.google.com/session/video"; +const buzz::StaticQName QN_GINGLE_VIDEO_CONTENT = + { NS_GINGLE_VIDEO, LN_DESCRIPTION }; +const buzz::StaticQName QN_GINGLE_VIDEO_PAYLOADTYPE = + { NS_GINGLE_VIDEO, LN_PAYLOADTYPE }; +const buzz::StaticQName QN_GINGLE_VIDEO_SRCID = { NS_GINGLE_VIDEO, "src-id" }; +const buzz::StaticQName QN_GINGLE_VIDEO_BANDWIDTH = + { NS_GINGLE_VIDEO, LN_BANDWIDTH }; + +// Crypto support. +const buzz::StaticQName QN_ENCRYPTION = { NS_JINGLE_RTP, "encryption" }; +const buzz::StaticQName QN_ENCRYPTION_REQUIRED = { NS_EMPTY, "required" }; +const buzz::StaticQName QN_CRYPTO = { NS_JINGLE_RTP, "crypto" }; +const buzz::StaticQName QN_GINGLE_AUDIO_CRYPTO_USAGE = + { NS_GINGLE_AUDIO, "usage" }; +const buzz::StaticQName QN_GINGLE_VIDEO_CRYPTO_USAGE = + { NS_GINGLE_VIDEO, "usage" }; +const buzz::StaticQName QN_CRYPTO_SUITE = { NS_EMPTY, "crypto-suite" }; +const buzz::StaticQName QN_CRYPTO_KEY_PARAMS = { NS_EMPTY, "key-params" }; +const buzz::StaticQName QN_CRYPTO_TAG = { NS_EMPTY, "tag" }; +const buzz::StaticQName QN_CRYPTO_SESSION_PARAMS = + { NS_EMPTY, "session-params" }; + +// Transports and candidates. +const char LN_TRANSPORT[] = "transport"; +const char LN_CANDIDATE[] = "candidate"; +const buzz::StaticQName QN_UFRAG = { cricket::NS_EMPTY, "ufrag" }; +const buzz::StaticQName QN_PWD = { cricket::NS_EMPTY, "pwd" }; +const buzz::StaticQName QN_COMPONENT = { cricket::NS_EMPTY, "component" }; +const buzz::StaticQName QN_IP = { cricket::NS_EMPTY, "ip" }; +const buzz::StaticQName QN_PORT = { cricket::NS_EMPTY, "port" }; +const buzz::StaticQName QN_NETWORK = { cricket::NS_EMPTY, "network" }; +const buzz::StaticQName QN_GENERATION = { cricket::NS_EMPTY, "generation" }; +const buzz::StaticQName QN_PRIORITY = { cricket::NS_EMPTY, "priority" }; +const buzz::StaticQName QN_PROTOCOL = { cricket::NS_EMPTY, "protocol" }; +const char ICE_CANDIDATE_TYPE_PEER_STUN[] = "prflx"; +const char ICE_CANDIDATE_TYPE_SERVER_STUN[] = "srflx"; +// Minimum ufrag length is 4 characters as per RFC5245. We chose 16 because +// some internal systems expect username to be 16 bytes. +const int ICE_UFRAG_LENGTH = 16; +// Minimum password length of 22 characters as per RFC5245. We chose 24 because +// some internal systems expect password to be multiple of 4. +const int ICE_PWD_LENGTH = 24; +const size_t ICE_UFRAG_MIN_LENGTH = 4; +const size_t ICE_PWD_MIN_LENGTH = 22; +const size_t ICE_UFRAG_MAX_LENGTH = 255; +const size_t ICE_PWD_MAX_LENGTH = 256; +// TODO: This is media-specific, so might belong +// somewhere like media/base/constants.h +const int ICE_CANDIDATE_COMPONENT_RTP = 1; +const int ICE_CANDIDATE_COMPONENT_RTCP = 2; +const int ICE_CANDIDATE_COMPONENT_DEFAULT = 1; + +const buzz::StaticQName QN_FINGERPRINT = { cricket::NS_EMPTY, "fingerprint" }; +const buzz::StaticQName QN_FINGERPRINT_ALGORITHM = + { cricket::NS_EMPTY, "algorithm" }; +const buzz::StaticQName QN_FINGERPRINT_DIGEST = { cricket::NS_EMPTY, "digest" }; + +const char NS_JINGLE_ICE_UDP[] = "urn:xmpp:jingle:transports:ice-udp:1"; + +const char ICE_OPTION_GICE[] = "google-ice"; +const char NS_GINGLE_P2P[] = "http://www.google.com/transport/p2p"; +const buzz::StaticQName QN_GINGLE_P2P_TRANSPORT = + { NS_GINGLE_P2P, LN_TRANSPORT }; +const buzz::StaticQName QN_GINGLE_P2P_CANDIDATE = + { NS_GINGLE_P2P, LN_CANDIDATE }; +const buzz::StaticQName QN_GINGLE_P2P_UNKNOWN_CHANNEL_NAME = + { NS_GINGLE_P2P, "unknown-channel-name" }; +const buzz::StaticQName QN_GINGLE_CANDIDATE = { NS_GINGLE, LN_CANDIDATE }; +const buzz::StaticQName QN_ADDRESS = { cricket::NS_EMPTY, "address" }; +const buzz::StaticQName QN_USERNAME = { cricket::NS_EMPTY, "username" }; +const buzz::StaticQName QN_PASSWORD = { cricket::NS_EMPTY, "password" }; +const buzz::StaticQName QN_PREFERENCE = { cricket::NS_EMPTY, "preference" }; +const char GICE_CHANNEL_NAME_RTP[] = "rtp"; +const char GICE_CHANNEL_NAME_RTCP[] = "rtcp"; +const char GICE_CHANNEL_NAME_VIDEO_RTP[] = "video_rtp"; +const char GICE_CHANNEL_NAME_VIDEO_RTCP[] = "video_rtcp"; +const char GICE_CHANNEL_NAME_DATA_RTP[] = "data_rtp"; +const char GICE_CHANNEL_NAME_DATA_RTCP[] = "data_rtcp"; + +// terminate reasons and errors +const char JINGLE_ERROR_BAD_REQUEST[] = "bad-request"; +const char JINGLE_ERROR_OUT_OF_ORDER[] = "out-of-order"; +const char JINGLE_ERROR_UNKNOWN_SESSION[] = "unknown-session"; + +// Call terminate reasons from XEP-166 +const char STR_TERMINATE_DECLINE[] = "decline"; +const char STR_TERMINATE_SUCCESS[] = "success"; +const char STR_TERMINATE_ERROR[] = "general-error"; +const char STR_TERMINATE_INCOMPATIBLE_PARAMETERS[] = "incompatible-parameters"; + +// Old terminate reasons used by cricket +const char STR_TERMINATE_CALL_ENDED[] = "call-ended"; +const char STR_TERMINATE_RECIPIENT_UNAVAILABLE[] = "recipient-unavailable"; +const char STR_TERMINATE_RECIPIENT_BUSY[] = "recipient-busy"; +const char STR_TERMINATE_INSUFFICIENT_FUNDS[] = "insufficient-funds"; +const char STR_TERMINATE_NUMBER_MALFORMED[] = "number-malformed"; +const char STR_TERMINATE_NUMBER_DISALLOWED[] = "number-disallowed"; +const char STR_TERMINATE_PROTOCOL_ERROR[] = "protocol-error"; +const char STR_TERMINATE_INTERNAL_SERVER_ERROR[] = "internal-server-error"; +const char STR_TERMINATE_UNKNOWN_ERROR[] = "unknown-error"; + +// Draft view and notify messages. +const char STR_JINGLE_DRAFT_CONTENT_NAME_VIDEO[] = "video"; +const char STR_JINGLE_DRAFT_CONTENT_NAME_AUDIO[] = "audio"; +const buzz::StaticQName QN_NICK = { cricket::NS_EMPTY, "nick" }; +const buzz::StaticQName QN_TYPE = { cricket::NS_EMPTY, "type" }; +const buzz::StaticQName QN_JINGLE_DRAFT_VIEW = { NS_JINGLE_DRAFT, "view" }; +const char STR_JINGLE_DRAFT_VIEW_TYPE_NONE[] = "none"; +const char STR_JINGLE_DRAFT_VIEW_TYPE_STATIC[] = "static"; +const buzz::StaticQName QN_JINGLE_DRAFT_PARAMS = { NS_JINGLE_DRAFT, "params" }; +const buzz::StaticQName QN_JINGLE_DRAFT_STREAMS = { NS_JINGLE_DRAFT, "streams" }; +const buzz::StaticQName QN_JINGLE_DRAFT_STREAM = { NS_JINGLE_DRAFT, "stream" }; +const buzz::StaticQName QN_DISPLAY = { cricket::NS_EMPTY, "display" }; +const buzz::StaticQName QN_CNAME = { cricket::NS_EMPTY, "cname" }; +const buzz::StaticQName QN_JINGLE_DRAFT_SSRC = { NS_JINGLE_DRAFT, "ssrc" }; +const buzz::StaticQName QN_JINGLE_DRAFT_SSRC_GROUP = + { NS_JINGLE_DRAFT, "ssrc-group" }; +const buzz::StaticQName QN_SEMANTICS = { cricket::NS_EMPTY, "semantics" }; +const buzz::StaticQName QN_JINGLE_LEGACY_NOTIFY = { NS_JINGLE_DRAFT, "notify" }; +const buzz::StaticQName QN_JINGLE_LEGACY_SOURCE = { NS_JINGLE_DRAFT, "source" }; + +const char NS_GINGLE_RAW[] = "http://www.google.com/transport/raw-udp"; +const buzz::StaticQName QN_GINGLE_RAW_TRANSPORT = { NS_GINGLE_RAW, "transport" }; +const buzz::StaticQName QN_GINGLE_RAW_CHANNEL = { NS_GINGLE_RAW, "channel" }; + +// old stuff +#ifdef FEATURE_ENABLE_VOICEMAIL +const char NS_VOICEMAIL[] = "http://www.google.com/session/voicemail"; +const buzz::StaticQName QN_VOICEMAIL_REGARDING = { NS_VOICEMAIL, "regarding" }; +#endif + +// From RFC 4145, SDP setup attribute values. +const char CONNECTIONROLE_ACTIVE_STR[] = "active"; +const char CONNECTIONROLE_PASSIVE_STR[] = "passive"; +const char CONNECTIONROLE_ACTPASS_STR[] = "actpass"; +const char CONNECTIONROLE_HOLDCONN_STR[] = "holdconn"; + +} // namespace cricket diff --git a/webrtc/p2p/base/constants.h b/webrtc/p2p/base/constants.h new file mode 100644 index 000000000..57423f5b3 --- /dev/null +++ b/webrtc/p2p/base/constants.h @@ -0,0 +1,259 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_CONSTANTS_H_ +#define WEBRTC_P2P_BASE_CONSTANTS_H_ + +#include +#include "webrtc/libjingle/xmllite/qname.h" + +// This file contains constants related to signaling that are used in various +// classes in this directory. + +namespace cricket { + +// NS_ == namespace +// QN_ == buzz::QName (namespace + name) +// LN_ == "local name" == QName::LocalPart() +// these are useful when you need to find a tag +// that has different namespaces (like or ) + +extern const char NS_EMPTY[]; +extern const char NS_JINGLE[]; +extern const char NS_JINGLE_DRAFT[]; +extern const char NS_GINGLE[]; + +enum SignalingProtocol { + PROTOCOL_JINGLE, + PROTOCOL_GINGLE, + PROTOCOL_HYBRID, +}; + +// actions (aka Gingle or Jingle ) +extern const buzz::StaticQName QN_ACTION; +extern const char LN_INITIATOR[]; +extern const buzz::StaticQName QN_INITIATOR; +extern const buzz::StaticQName QN_CREATOR; + +extern const buzz::StaticQName QN_JINGLE; +extern const buzz::StaticQName QN_JINGLE_CONTENT; +extern const buzz::StaticQName QN_JINGLE_CONTENT_NAME; +extern const buzz::StaticQName QN_JINGLE_CONTENT_MEDIA; +extern const buzz::StaticQName QN_JINGLE_REASON; +extern const buzz::StaticQName QN_JINGLE_DRAFT_GROUP; +extern const buzz::StaticQName QN_JINGLE_DRAFT_GROUP_TYPE; +extern const char JINGLE_CONTENT_MEDIA_AUDIO[]; +extern const char JINGLE_CONTENT_MEDIA_VIDEO[]; +extern const char JINGLE_CONTENT_MEDIA_DATA[]; +extern const char JINGLE_ACTION_SESSION_INITIATE[]; +extern const char JINGLE_ACTION_SESSION_INFO[]; +extern const char JINGLE_ACTION_SESSION_ACCEPT[]; +extern const char JINGLE_ACTION_SESSION_TERMINATE[]; +extern const char JINGLE_ACTION_TRANSPORT_INFO[]; +extern const char JINGLE_ACTION_TRANSPORT_ACCEPT[]; +extern const char JINGLE_ACTION_DESCRIPTION_INFO[]; + +extern const buzz::StaticQName QN_GINGLE_SESSION; +extern const char GINGLE_ACTION_INITIATE[]; +extern const char GINGLE_ACTION_INFO[]; +extern const char GINGLE_ACTION_ACCEPT[]; +extern const char GINGLE_ACTION_REJECT[]; +extern const char GINGLE_ACTION_TERMINATE[]; +extern const char GINGLE_ACTION_CANDIDATES[]; +extern const char GINGLE_ACTION_UPDATE[]; + +extern const char LN_ERROR[]; +extern const buzz::StaticQName QN_GINGLE_REDIRECT; +extern const char STR_REDIRECT_PREFIX[]; + +// Session Contents (aka Gingle +// or Jingle ) +extern const char LN_DESCRIPTION[]; +extern const char LN_PAYLOADTYPE[]; +extern const buzz::StaticQName QN_ID; +extern const buzz::StaticQName QN_SID; +extern const buzz::StaticQName QN_NAME; +extern const buzz::StaticQName QN_CLOCKRATE; +extern const buzz::StaticQName QN_BITRATE; +extern const buzz::StaticQName QN_CHANNELS; +extern const buzz::StaticQName QN_PARAMETER; +extern const char LN_NAME[]; +extern const char LN_VALUE[]; +extern const buzz::StaticQName QN_PAYLOADTYPE_PARAMETER_NAME; +extern const buzz::StaticQName QN_PAYLOADTYPE_PARAMETER_VALUE; +extern const char PAYLOADTYPE_PARAMETER_BITRATE[]; +extern const char PAYLOADTYPE_PARAMETER_HEIGHT[]; +extern const char PAYLOADTYPE_PARAMETER_WIDTH[]; +extern const char PAYLOADTYPE_PARAMETER_FRAMERATE[]; +extern const char LN_BANDWIDTH[]; + +// CN_ == "content name". When we initiate a session, we choose the +// name, and when we receive a Gingle session, we provide default +// names (since Gingle has no content names). But when we receive a +// Jingle call, the content name can be anything, so don't rely on +// these values being the same as the ones received. +extern const char CN_AUDIO[]; +extern const char CN_VIDEO[]; +extern const char CN_DATA[]; +extern const char CN_OTHER[]; +// other SDP related strings +// GN stands for group name +extern const char GROUP_TYPE_BUNDLE[]; + +extern const char NS_JINGLE_RTP[]; +extern const buzz::StaticQName QN_JINGLE_RTP_CONTENT; +extern const buzz::StaticQName QN_SSRC; +extern const buzz::StaticQName QN_JINGLE_RTP_PAYLOADTYPE; +extern const buzz::StaticQName QN_JINGLE_RTP_BANDWIDTH; +extern const buzz::StaticQName QN_JINGLE_RTCP_MUX; +extern const buzz::StaticQName QN_JINGLE_RTCP_FB; +extern const buzz::StaticQName QN_SUBTYPE; +extern const buzz::StaticQName QN_JINGLE_RTP_HDREXT; +extern const buzz::StaticQName QN_URI; + +extern const char NS_JINGLE_DRAFT_SCTP[]; +extern const buzz::StaticQName QN_JINGLE_DRAFT_SCTP_CONTENT; +extern const buzz::StaticQName QN_JINGLE_DRAFT_SCTP_STREAM; + +extern const char NS_GINGLE_AUDIO[]; +extern const buzz::StaticQName QN_GINGLE_AUDIO_CONTENT; +extern const buzz::StaticQName QN_GINGLE_AUDIO_PAYLOADTYPE; +extern const buzz::StaticQName QN_GINGLE_AUDIO_SRCID; +extern const char NS_GINGLE_VIDEO[]; +extern const buzz::StaticQName QN_GINGLE_VIDEO_CONTENT; +extern const buzz::StaticQName QN_GINGLE_VIDEO_PAYLOADTYPE; +extern const buzz::StaticQName QN_GINGLE_VIDEO_SRCID; +extern const buzz::StaticQName QN_GINGLE_VIDEO_BANDWIDTH; + +// Crypto support. +extern const buzz::StaticQName QN_ENCRYPTION; +extern const buzz::StaticQName QN_ENCRYPTION_REQUIRED; +extern const buzz::StaticQName QN_CRYPTO; +extern const buzz::StaticQName QN_GINGLE_AUDIO_CRYPTO_USAGE; +extern const buzz::StaticQName QN_GINGLE_VIDEO_CRYPTO_USAGE; +extern const buzz::StaticQName QN_CRYPTO_SUITE; +extern const buzz::StaticQName QN_CRYPTO_KEY_PARAMS; +extern const buzz::StaticQName QN_CRYPTO_TAG; +extern const buzz::StaticQName QN_CRYPTO_SESSION_PARAMS; + +// Transports and candidates. +extern const char LN_TRANSPORT[]; +extern const char LN_CANDIDATE[]; +extern const buzz::StaticQName QN_JINGLE_P2P_TRANSPORT; +extern const buzz::StaticQName QN_JINGLE_P2P_CANDIDATE; +extern const buzz::StaticQName QN_UFRAG; +extern const buzz::StaticQName QN_COMPONENT; +extern const buzz::StaticQName QN_PWD; +extern const buzz::StaticQName QN_IP; +extern const buzz::StaticQName QN_PORT; +extern const buzz::StaticQName QN_NETWORK; +extern const buzz::StaticQName QN_GENERATION; +extern const buzz::StaticQName QN_PRIORITY; +extern const buzz::StaticQName QN_PROTOCOL; +extern const char ICE_CANDIDATE_TYPE_PEER_STUN[]; +extern const char ICE_CANDIDATE_TYPE_SERVER_STUN[]; +extern const int ICE_UFRAG_LENGTH; +extern const int ICE_PWD_LENGTH; +extern const size_t ICE_UFRAG_MIN_LENGTH; +extern const size_t ICE_PWD_MIN_LENGTH; +extern const size_t ICE_UFRAG_MAX_LENGTH; +extern const size_t ICE_PWD_MAX_LENGTH; +extern const int ICE_CANDIDATE_COMPONENT_RTP; +extern const int ICE_CANDIDATE_COMPONENT_RTCP; +extern const int ICE_CANDIDATE_COMPONENT_DEFAULT; + +extern const buzz::StaticQName QN_FINGERPRINT; +extern const buzz::StaticQName QN_FINGERPRINT_ALGORITHM; +extern const buzz::StaticQName QN_FINGERPRINT_DIGEST; + +extern const char NS_JINGLE_ICE_UDP[]; + +extern const char ICE_OPTION_GICE[]; +extern const char NS_GINGLE_P2P[]; +extern const buzz::StaticQName QN_GINGLE_P2P_TRANSPORT; +extern const buzz::StaticQName QN_GINGLE_P2P_CANDIDATE; +extern const buzz::StaticQName QN_GINGLE_P2P_UNKNOWN_CHANNEL_NAME; +extern const buzz::StaticQName QN_GINGLE_CANDIDATE; +extern const buzz::StaticQName QN_ADDRESS; +extern const buzz::StaticQName QN_USERNAME; +extern const buzz::StaticQName QN_PASSWORD; +extern const buzz::StaticQName QN_PREFERENCE; +extern const char GINGLE_CANDIDATE_TYPE_STUN[]; +extern const char GICE_CHANNEL_NAME_RTP[]; +extern const char GICE_CHANNEL_NAME_RTCP[]; +extern const char GICE_CHANNEL_NAME_VIDEO_RTP[]; +extern const char GICE_CHANNEL_NAME_VIDEO_RTCP[]; +extern const char GICE_CHANNEL_NAME_DATA_RTP[]; +extern const char GICE_CHANNEL_NAME_DATA_RTCP[]; + +extern const char NS_GINGLE_RAW[]; +extern const buzz::StaticQName QN_GINGLE_RAW_TRANSPORT; +extern const buzz::StaticQName QN_GINGLE_RAW_CHANNEL; + +// terminate reasons and errors: see http://xmpp.org/extensions/xep-0166.html +extern const char JINGLE_ERROR_BAD_REQUEST[]; // like parse error +// got transport-info before session-initiate, for example +extern const char JINGLE_ERROR_OUT_OF_ORDER[]; +extern const char JINGLE_ERROR_UNKNOWN_SESSION[]; + +// Call terminate reasons from XEP-166 +extern const char STR_TERMINATE_DECLINE[]; // polite reject +extern const char STR_TERMINATE_SUCCESS[]; // polite hangup +extern const char STR_TERMINATE_ERROR[]; // something bad happened +extern const char STR_TERMINATE_INCOMPATIBLE_PARAMETERS[]; // no codecs? + +// Old terminate reasons used by cricket +extern const char STR_TERMINATE_CALL_ENDED[]; +extern const char STR_TERMINATE_RECIPIENT_UNAVAILABLE[]; +extern const char STR_TERMINATE_RECIPIENT_BUSY[]; +extern const char STR_TERMINATE_INSUFFICIENT_FUNDS[]; +extern const char STR_TERMINATE_NUMBER_MALFORMED[]; +extern const char STR_TERMINATE_NUMBER_DISALLOWED[]; +extern const char STR_TERMINATE_PROTOCOL_ERROR[]; +extern const char STR_TERMINATE_INTERNAL_SERVER_ERROR[]; +extern const char STR_TERMINATE_UNKNOWN_ERROR[]; + +// Draft view and notify messages. +extern const char STR_JINGLE_DRAFT_CONTENT_NAME_VIDEO[]; +extern const char STR_JINGLE_DRAFT_CONTENT_NAME_AUDIO[]; +extern const buzz::StaticQName QN_NICK; +extern const buzz::StaticQName QN_TYPE; +extern const buzz::StaticQName QN_JINGLE_DRAFT_VIEW; +extern const char STR_JINGLE_DRAFT_VIEW_TYPE_NONE[]; +extern const char STR_JINGLE_DRAFT_VIEW_TYPE_STATIC[]; +extern const buzz::StaticQName QN_JINGLE_DRAFT_PARAMS; +extern const buzz::StaticQName QN_WIDTH; +extern const buzz::StaticQName QN_HEIGHT; +extern const buzz::StaticQName QN_FRAMERATE; +extern const buzz::StaticQName QN_JINGLE_DRAFT_STREAM; +extern const buzz::StaticQName QN_JINGLE_DRAFT_STREAMS; +extern const buzz::StaticQName QN_DISPLAY; +extern const buzz::StaticQName QN_CNAME; +extern const buzz::StaticQName QN_JINGLE_DRAFT_SSRC; +extern const buzz::StaticQName QN_JINGLE_DRAFT_SSRC_GROUP; +extern const buzz::StaticQName QN_SEMANTICS; +extern const buzz::StaticQName QN_JINGLE_LEGACY_NOTIFY; +extern const buzz::StaticQName QN_JINGLE_LEGACY_SOURCE; + +// old stuff +#ifdef FEATURE_ENABLE_VOICEMAIL +extern const char NS_VOICEMAIL[]; +extern const buzz::StaticQName QN_VOICEMAIL_REGARDING; +#endif + +// RFC 4145, SDP setup attribute values. +extern const char CONNECTIONROLE_ACTIVE_STR[]; +extern const char CONNECTIONROLE_PASSIVE_STR[]; +extern const char CONNECTIONROLE_ACTPASS_STR[]; +extern const char CONNECTIONROLE_HOLDCONN_STR[]; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_CONSTANTS_H_ diff --git a/webrtc/p2p/base/dtlstransport.h b/webrtc/p2p/base/dtlstransport.h new file mode 100644 index 000000000..bb80dc814 --- /dev/null +++ b/webrtc/p2p/base/dtlstransport.h @@ -0,0 +1,240 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_DTLSTRANSPORT_H_ +#define WEBRTC_P2P_BASE_DTLSTRANSPORT_H_ + +#include "webrtc/p2p/base/dtlstransportchannel.h" +#include "webrtc/p2p/base/transport.h" + +namespace rtc { +class SSLIdentity; +} + +namespace cricket { + +class PortAllocator; + +// Base should be a descendant of cricket::Transport +template +class DtlsTransport : public Base { + public: + DtlsTransport(rtc::Thread* signaling_thread, + rtc::Thread* worker_thread, + const std::string& content_name, + PortAllocator* allocator, + rtc::SSLIdentity* identity) + : Base(signaling_thread, worker_thread, content_name, allocator), + identity_(identity), + secure_role_(rtc::SSL_CLIENT) { + } + + ~DtlsTransport() { + Base::DestroyAllChannels(); + } + virtual void SetIdentity_w(rtc::SSLIdentity* identity) { + identity_ = identity; + } + virtual bool GetIdentity_w(rtc::SSLIdentity** identity) { + if (!identity_) + return false; + + *identity = identity_->GetReference(); + return true; + } + + virtual bool ApplyLocalTransportDescription_w(TransportChannelImpl* channel, + std::string* error_desc) { + rtc::SSLFingerprint* local_fp = + Base::local_description()->identity_fingerprint.get(); + + if (local_fp) { + // Sanity check local fingerprint. + if (identity_) { + rtc::scoped_ptr local_fp_tmp( + rtc::SSLFingerprint::Create(local_fp->algorithm, + identity_)); + ASSERT(local_fp_tmp.get() != NULL); + if (!(*local_fp_tmp == *local_fp)) { + std::ostringstream desc; + desc << "Local fingerprint does not match identity. Expected: "; + desc << local_fp_tmp->ToString(); + desc << " Got: " << local_fp->ToString(); + return BadTransportDescription(desc.str(), error_desc); + } + } else { + return BadTransportDescription( + "Local fingerprint provided but no identity available.", + error_desc); + } + } else { + identity_ = NULL; + } + + if (!channel->SetLocalIdentity(identity_)) { + return BadTransportDescription("Failed to set local identity.", + error_desc); + } + + // Apply the description in the base class. + return Base::ApplyLocalTransportDescription_w(channel, error_desc); + } + + virtual bool NegotiateTransportDescription_w(ContentAction local_role, + std::string* error_desc) { + if (!Base::local_description() || !Base::remote_description()) { + const std::string msg = "Local and Remote description must be set before " + "transport descriptions are negotiated"; + return BadTransportDescription(msg, error_desc); + } + + rtc::SSLFingerprint* local_fp = + Base::local_description()->identity_fingerprint.get(); + rtc::SSLFingerprint* remote_fp = + Base::remote_description()->identity_fingerprint.get(); + + if (remote_fp && local_fp) { + remote_fingerprint_.reset(new rtc::SSLFingerprint(*remote_fp)); + + // From RFC 4145, section-4.1, The following are the values that the + // 'setup' attribute can take in an offer/answer exchange: + // Offer Answer + // ________________ + // active passive / holdconn + // passive active / holdconn + // actpass active / passive / holdconn + // holdconn holdconn + // + // Set the role that is most conformant with RFC 5763, Section 5, bullet 1 + // The endpoint MUST use the setup attribute defined in [RFC4145]. + // The endpoint that is the offerer MUST use the setup attribute + // value of setup:actpass and be prepared to receive a client_hello + // before it receives the answer. The answerer MUST use either a + // setup attribute value of setup:active or setup:passive. Note that + // if the answerer uses setup:passive, then the DTLS handshake will + // not begin until the answerer is received, which adds additional + // latency. setup:active allows the answer and the DTLS handshake to + // occur in parallel. Thus, setup:active is RECOMMENDED. Whichever + // party is active MUST initiate a DTLS handshake by sending a + // ClientHello over each flow (host/port quartet). + // IOW - actpass and passive modes should be treated as server and + // active as client. + ConnectionRole local_connection_role = + Base::local_description()->connection_role; + ConnectionRole remote_connection_role = + Base::remote_description()->connection_role; + + bool is_remote_server = false; + if (local_role == CA_OFFER) { + if (local_connection_role != CONNECTIONROLE_ACTPASS) { + return BadTransportDescription( + "Offerer must use actpass value for setup attribute.", + error_desc); + } + + if (remote_connection_role == CONNECTIONROLE_ACTIVE || + remote_connection_role == CONNECTIONROLE_PASSIVE || + remote_connection_role == CONNECTIONROLE_NONE) { + is_remote_server = (remote_connection_role == CONNECTIONROLE_PASSIVE); + } else { + const std::string msg = + "Answerer must use either active or passive value " + "for setup attribute."; + return BadTransportDescription(msg, error_desc); + } + // If remote is NONE or ACTIVE it will act as client. + } else { + if (remote_connection_role != CONNECTIONROLE_ACTPASS && + remote_connection_role != CONNECTIONROLE_NONE) { + return BadTransportDescription( + "Offerer must use actpass value for setup attribute.", + error_desc); + } + + if (local_connection_role == CONNECTIONROLE_ACTIVE || + local_connection_role == CONNECTIONROLE_PASSIVE) { + is_remote_server = (local_connection_role == CONNECTIONROLE_ACTIVE); + } else { + const std::string msg = + "Answerer must use either active or passive value " + "for setup attribute."; + return BadTransportDescription(msg, error_desc); + } + + // If local is passive, local will act as server. + } + + secure_role_ = is_remote_server ? rtc::SSL_CLIENT : + rtc::SSL_SERVER; + + } else if (local_fp && (local_role == CA_ANSWER)) { + return BadTransportDescription( + "Local fingerprint supplied when caller didn't offer DTLS.", + error_desc); + } else { + // We are not doing DTLS + remote_fingerprint_.reset(new rtc::SSLFingerprint( + "", NULL, 0)); + } + + // Now run the negotiation for the base class. + return Base::NegotiateTransportDescription_w(local_role, error_desc); + } + + virtual DtlsTransportChannelWrapper* CreateTransportChannel(int component) { + return new DtlsTransportChannelWrapper( + this, Base::CreateTransportChannel(component)); + } + + virtual void DestroyTransportChannel(TransportChannelImpl* channel) { + // Kind of ugly, but this lets us do the exact inverse of the create. + DtlsTransportChannelWrapper* dtls_channel = + static_cast(channel); + TransportChannelImpl* base_channel = dtls_channel->channel(); + delete dtls_channel; + Base::DestroyTransportChannel(base_channel); + } + + virtual bool GetSslRole_w(rtc::SSLRole* ssl_role) const { + ASSERT(ssl_role != NULL); + *ssl_role = secure_role_; + return true; + } + + private: + virtual bool ApplyNegotiatedTransportDescription_w( + TransportChannelImpl* channel, + std::string* error_desc) { + // Set ssl role. Role must be set before fingerprint is applied, which + // initiates DTLS setup. + if (!channel->SetSslRole(secure_role_)) { + return BadTransportDescription("Failed to set ssl role for the channel.", + error_desc); + } + // Apply remote fingerprint. + if (!channel->SetRemoteFingerprint( + remote_fingerprint_->algorithm, + reinterpret_cast(remote_fingerprint_-> + digest.data()), + remote_fingerprint_->digest.length())) { + return BadTransportDescription("Failed to apply remote fingerprint.", + error_desc); + } + return Base::ApplyNegotiatedTransportDescription_w(channel, error_desc); + } + + rtc::SSLIdentity* identity_; + rtc::SSLRole secure_role_; + rtc::scoped_ptr remote_fingerprint_; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_DTLSTRANSPORT_H_ diff --git a/webrtc/p2p/base/dtlstransportchannel.cc b/webrtc/p2p/base/dtlstransportchannel.cc new file mode 100644 index 000000000..9cceca350 --- /dev/null +++ b/webrtc/p2p/base/dtlstransportchannel.cc @@ -0,0 +1,623 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/dtlstransportchannel.h" + +#include "webrtc/p2p/base/common.h" +#include "webrtc/base/buffer.h" +#include "webrtc/base/dscp.h" +#include "webrtc/base/messagequeue.h" +#include "webrtc/base/sslstreamadapter.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/thread.h" + +namespace cricket { + +// We don't pull the RTP constants from rtputils.h, to avoid a layer violation. +static const size_t kDtlsRecordHeaderLen = 13; +static const size_t kMaxDtlsPacketLen = 2048; +static const size_t kMinRtpPacketLen = 12; + +static bool IsDtlsPacket(const char* data, size_t len) { + const uint8* u = reinterpret_cast(data); + return (len >= kDtlsRecordHeaderLen && (u[0] > 19 && u[0] < 64)); +} +static bool IsRtpPacket(const char* data, size_t len) { + const uint8* u = reinterpret_cast(data); + return (len >= kMinRtpPacketLen && (u[0] & 0xC0) == 0x80); +} + +rtc::StreamResult StreamInterfaceChannel::Read(void* buffer, + size_t buffer_len, + size_t* read, + int* error) { + if (state_ == rtc::SS_CLOSED) + return rtc::SR_EOS; + if (state_ == rtc::SS_OPENING) + return rtc::SR_BLOCK; + + return fifo_.Read(buffer, buffer_len, read, error); +} + +rtc::StreamResult StreamInterfaceChannel::Write(const void* data, + size_t data_len, + size_t* written, + int* error) { + // Always succeeds, since this is an unreliable transport anyway. + // TODO: Should this block if channel_'s temporarily unwritable? + rtc::PacketOptions packet_options; + channel_->SendPacket(static_cast(data), data_len, + packet_options); + if (written) { + *written = data_len; + } + return rtc::SR_SUCCESS; +} + +bool StreamInterfaceChannel::OnPacketReceived(const char* data, size_t size) { + // We force a read event here to ensure that we don't overflow our FIFO. + // Under high packet rate this can occur if we wait for the FIFO to post its + // own SE_READ. + bool ret = (fifo_.WriteAll(data, size, NULL, NULL) == rtc::SR_SUCCESS); + if (ret) { + SignalEvent(this, rtc::SE_READ, 0); + } + return ret; +} + +void StreamInterfaceChannel::OnEvent(rtc::StreamInterface* stream, + int sig, int err) { + SignalEvent(this, sig, err); +} + +DtlsTransportChannelWrapper::DtlsTransportChannelWrapper( + Transport* transport, + TransportChannelImpl* channel) + : TransportChannelImpl(channel->content_name(), channel->component()), + transport_(transport), + worker_thread_(rtc::Thread::Current()), + channel_(channel), + downward_(NULL), + dtls_state_(STATE_NONE), + local_identity_(NULL), + ssl_role_(rtc::SSL_CLIENT) { + channel_->SignalReadableState.connect(this, + &DtlsTransportChannelWrapper::OnReadableState); + channel_->SignalWritableState.connect(this, + &DtlsTransportChannelWrapper::OnWritableState); + channel_->SignalReadPacket.connect(this, + &DtlsTransportChannelWrapper::OnReadPacket); + channel_->SignalReadyToSend.connect(this, + &DtlsTransportChannelWrapper::OnReadyToSend); + channel_->SignalRequestSignaling.connect(this, + &DtlsTransportChannelWrapper::OnRequestSignaling); + channel_->SignalCandidateReady.connect(this, + &DtlsTransportChannelWrapper::OnCandidateReady); + channel_->SignalCandidatesAllocationDone.connect(this, + &DtlsTransportChannelWrapper::OnCandidatesAllocationDone); + channel_->SignalRoleConflict.connect(this, + &DtlsTransportChannelWrapper::OnRoleConflict); + channel_->SignalRouteChange.connect(this, + &DtlsTransportChannelWrapper::OnRouteChange); + channel_->SignalConnectionRemoved.connect(this, + &DtlsTransportChannelWrapper::OnConnectionRemoved); +} + +DtlsTransportChannelWrapper::~DtlsTransportChannelWrapper() { +} + +void DtlsTransportChannelWrapper::Connect() { + // We should only get a single call to Connect. + ASSERT(dtls_state_ == STATE_NONE || + dtls_state_ == STATE_OFFERED || + dtls_state_ == STATE_ACCEPTED); + channel_->Connect(); +} + +void DtlsTransportChannelWrapper::Reset() { + channel_->Reset(); + set_writable(false); + set_readable(false); + + // Re-call SetupDtls() + if (!SetupDtls()) { + LOG_J(LS_ERROR, this) << "Error re-initializing DTLS"; + dtls_state_ = STATE_CLOSED; + return; + } + + dtls_state_ = STATE_ACCEPTED; +} + +bool DtlsTransportChannelWrapper::SetLocalIdentity( + rtc::SSLIdentity* identity) { + if (dtls_state_ != STATE_NONE) { + if (identity == local_identity_) { + // This may happen during renegotiation. + LOG_J(LS_INFO, this) << "Ignoring identical DTLS identity"; + return true; + } else { + LOG_J(LS_ERROR, this) << "Can't change DTLS local identity in this state"; + return false; + } + } + + if (identity) { + local_identity_ = identity; + dtls_state_ = STATE_OFFERED; + } else { + LOG_J(LS_INFO, this) << "NULL DTLS identity supplied. Not doing DTLS"; + } + + return true; +} + +bool DtlsTransportChannelWrapper::GetLocalIdentity( + rtc::SSLIdentity** identity) const { + if (!local_identity_) + return false; + + *identity = local_identity_->GetReference(); + return true; +} + +bool DtlsTransportChannelWrapper::SetSslRole(rtc::SSLRole role) { + if (dtls_state_ == STATE_OPEN) { + if (ssl_role_ != role) { + LOG(LS_ERROR) << "SSL Role can't be reversed after the session is setup."; + return false; + } + return true; + } + + ssl_role_ = role; + return true; +} + +bool DtlsTransportChannelWrapper::GetSslRole(rtc::SSLRole* role) const { + *role = ssl_role_; + return true; +} + +bool DtlsTransportChannelWrapper::SetRemoteFingerprint( + const std::string& digest_alg, + const uint8* digest, + size_t digest_len) { + + rtc::Buffer remote_fingerprint_value(digest, digest_len); + + if (dtls_state_ != STATE_NONE && + remote_fingerprint_value_ == remote_fingerprint_value && + !digest_alg.empty()) { + // This may happen during renegotiation. + LOG_J(LS_INFO, this) << "Ignoring identical remote DTLS fingerprint"; + return true; + } + + // Allow SetRemoteFingerprint with a NULL digest even if SetLocalIdentity + // hasn't been called. + if (dtls_state_ > STATE_OFFERED || + (dtls_state_ == STATE_NONE && !digest_alg.empty())) { + LOG_J(LS_ERROR, this) << "Can't set DTLS remote settings in this state."; + return false; + } + + if (digest_alg.empty()) { + LOG_J(LS_INFO, this) << "Other side didn't support DTLS."; + dtls_state_ = STATE_NONE; + return true; + } + + // At this point we know we are doing DTLS + remote_fingerprint_value.TransferTo(&remote_fingerprint_value_); + remote_fingerprint_algorithm_ = digest_alg; + + if (!SetupDtls()) { + dtls_state_ = STATE_CLOSED; + return false; + } + + dtls_state_ = STATE_ACCEPTED; + return true; +} + +bool DtlsTransportChannelWrapper::GetRemoteCertificate( + rtc::SSLCertificate** cert) const { + if (!dtls_) + return false; + + return dtls_->GetPeerCertificate(cert); +} + +bool DtlsTransportChannelWrapper::SetupDtls() { + StreamInterfaceChannel* downward = + new StreamInterfaceChannel(worker_thread_, channel_); + + dtls_.reset(rtc::SSLStreamAdapter::Create(downward)); + if (!dtls_) { + LOG_J(LS_ERROR, this) << "Failed to create DTLS adapter."; + delete downward; + return false; + } + + downward_ = downward; + + dtls_->SetIdentity(local_identity_->GetReference()); + dtls_->SetMode(rtc::SSL_MODE_DTLS); + dtls_->SetServerRole(ssl_role_); + dtls_->SignalEvent.connect(this, &DtlsTransportChannelWrapper::OnDtlsEvent); + if (!dtls_->SetPeerCertificateDigest( + remote_fingerprint_algorithm_, + reinterpret_cast(remote_fingerprint_value_.data()), + remote_fingerprint_value_.length())) { + LOG_J(LS_ERROR, this) << "Couldn't set DTLS certificate digest."; + return false; + } + + // Set up DTLS-SRTP, if it's been enabled. + if (!srtp_ciphers_.empty()) { + if (!dtls_->SetDtlsSrtpCiphers(srtp_ciphers_)) { + LOG_J(LS_ERROR, this) << "Couldn't set DTLS-SRTP ciphers."; + return false; + } + } else { + LOG_J(LS_INFO, this) << "Not using DTLS."; + } + + LOG_J(LS_INFO, this) << "DTLS setup complete."; + return true; +} + +bool DtlsTransportChannelWrapper::SetSrtpCiphers( + const std::vector& ciphers) { + if (srtp_ciphers_ == ciphers) + return true; + + if (dtls_state_ == STATE_STARTED) { + LOG(LS_WARNING) << "Ignoring new SRTP ciphers while DTLS is negotiating"; + return true; + } + + if (dtls_state_ == STATE_OPEN) { + // We don't support DTLS renegotiation currently. If new set of srtp ciphers + // are different than what's being used currently, we will not use it. + // So for now, let's be happy (or sad) with a warning message. + std::string current_srtp_cipher; + if (!dtls_->GetDtlsSrtpCipher(¤t_srtp_cipher)) { + LOG(LS_ERROR) << "Failed to get the current SRTP cipher for DTLS channel"; + return false; + } + const std::vector::const_iterator iter = + std::find(ciphers.begin(), ciphers.end(), current_srtp_cipher); + if (iter == ciphers.end()) { + std::string requested_str; + for (size_t i = 0; i < ciphers.size(); ++i) { + requested_str.append(" "); + requested_str.append(ciphers[i]); + requested_str.append(" "); + } + LOG(LS_WARNING) << "Ignoring new set of SRTP ciphers, as DTLS " + << "renegotiation is not supported currently " + << "current cipher = " << current_srtp_cipher << " and " + << "requested = " << "[" << requested_str << "]"; + } + return true; + } + + if (dtls_state_ != STATE_NONE && + dtls_state_ != STATE_OFFERED && + dtls_state_ != STATE_ACCEPTED) { + ASSERT(false); + return false; + } + + srtp_ciphers_ = ciphers; + return true; +} + +bool DtlsTransportChannelWrapper::GetSrtpCipher(std::string* cipher) { + if (dtls_state_ != STATE_OPEN) { + return false; + } + + return dtls_->GetDtlsSrtpCipher(cipher); +} + + +// Called from upper layers to send a media packet. +int DtlsTransportChannelWrapper::SendPacket( + const char* data, size_t size, + const rtc::PacketOptions& options, int flags) { + int result = -1; + + switch (dtls_state_) { + case STATE_OFFERED: + // We don't know if we are doing DTLS yet, so we can't send a packet. + // TODO(ekr@rtfm.com): assert here? + result = -1; + break; + + case STATE_STARTED: + case STATE_ACCEPTED: + // Can't send data until the connection is active + result = -1; + break; + + case STATE_OPEN: + if (flags & PF_SRTP_BYPASS) { + ASSERT(!srtp_ciphers_.empty()); + if (!IsRtpPacket(data, size)) { + result = -1; + break; + } + + result = channel_->SendPacket(data, size, options); + } else { + result = (dtls_->WriteAll(data, size, NULL, NULL) == + rtc::SR_SUCCESS) ? static_cast(size) : -1; + } + break; + // Not doing DTLS. + case STATE_NONE: + result = channel_->SendPacket(data, size, options); + break; + + case STATE_CLOSED: // Can't send anything when we're closed. + return -1; + } + + return result; +} + +// The state transition logic here is as follows: +// (1) If we're not doing DTLS-SRTP, then the state is just the +// state of the underlying impl() +// (2) If we're doing DTLS-SRTP: +// - Prior to the DTLS handshake, the state is neither readable or +// writable +// - When the impl goes writable for the first time we +// start the DTLS handshake +// - Once the DTLS handshake completes, the state is that of the +// impl again +void DtlsTransportChannelWrapper::OnReadableState(TransportChannel* channel) { + ASSERT(rtc::Thread::Current() == worker_thread_); + ASSERT(channel == channel_); + LOG_J(LS_VERBOSE, this) + << "DTLSTransportChannelWrapper: channel readable state changed."; + + if (dtls_state_ == STATE_NONE || dtls_state_ == STATE_OPEN) { + set_readable(channel_->readable()); + // Note: SignalReadableState fired by set_readable. + } +} + +void DtlsTransportChannelWrapper::OnWritableState(TransportChannel* channel) { + ASSERT(rtc::Thread::Current() == worker_thread_); + ASSERT(channel == channel_); + LOG_J(LS_VERBOSE, this) + << "DTLSTransportChannelWrapper: channel writable state changed."; + + switch (dtls_state_) { + case STATE_NONE: + case STATE_OPEN: + set_writable(channel_->writable()); + // Note: SignalWritableState fired by set_writable. + break; + + case STATE_OFFERED: + // Do nothing + break; + + case STATE_ACCEPTED: + if (!MaybeStartDtls()) { + // This should never happen: + // Because we are operating in a nonblocking mode and all + // incoming packets come in via OnReadPacket(), which rejects + // packets in this state, the incoming queue must be empty. We + // ignore write errors, thus any errors must be because of + // configuration and therefore are our fault. + // Note that in non-debug configurations, failure in + // MaybeStartDtls() changes the state to STATE_CLOSED. + ASSERT(false); + } + break; + + case STATE_STARTED: + // Do nothing + break; + + case STATE_CLOSED: + // Should not happen. Do nothing + break; + } +} + +void DtlsTransportChannelWrapper::OnReadPacket( + TransportChannel* channel, const char* data, size_t size, + const rtc::PacketTime& packet_time, int flags) { + ASSERT(rtc::Thread::Current() == worker_thread_); + ASSERT(channel == channel_); + ASSERT(flags == 0); + + switch (dtls_state_) { + case STATE_NONE: + // We are not doing DTLS + SignalReadPacket(this, data, size, packet_time, 0); + break; + + case STATE_OFFERED: + // Currently drop the packet, but we might in future + // decide to take this as evidence that the other + // side is ready to do DTLS and start the handshake + // on our end + LOG_J(LS_WARNING, this) << "Received packet before we know if we are " + << "doing DTLS or not; dropping."; + break; + + case STATE_ACCEPTED: + // Drop packets received before DTLS has actually started + LOG_J(LS_INFO, this) << "Dropping packet received before DTLS started."; + break; + + case STATE_STARTED: + case STATE_OPEN: + // We should only get DTLS or SRTP packets; STUN's already been demuxed. + // Is this potentially a DTLS packet? + if (IsDtlsPacket(data, size)) { + if (!HandleDtlsPacket(data, size)) { + LOG_J(LS_ERROR, this) << "Failed to handle DTLS packet."; + return; + } + } else { + // Not a DTLS packet; our handshake should be complete by now. + if (dtls_state_ != STATE_OPEN) { + LOG_J(LS_ERROR, this) << "Received non-DTLS packet before DTLS " + << "complete."; + return; + } + + // And it had better be a SRTP packet. + if (!IsRtpPacket(data, size)) { + LOG_J(LS_ERROR, this) << "Received unexpected non-DTLS packet."; + return; + } + + // Sanity check. + ASSERT(!srtp_ciphers_.empty()); + + // Signal this upwards as a bypass packet. + SignalReadPacket(this, data, size, packet_time, PF_SRTP_BYPASS); + } + break; + case STATE_CLOSED: + // This shouldn't be happening. Drop the packet + break; + } +} + +void DtlsTransportChannelWrapper::OnReadyToSend(TransportChannel* channel) { + if (writable()) { + SignalReadyToSend(this); + } +} + +void DtlsTransportChannelWrapper::OnDtlsEvent(rtc::StreamInterface* dtls, + int sig, int err) { + ASSERT(rtc::Thread::Current() == worker_thread_); + ASSERT(dtls == dtls_.get()); + if (sig & rtc::SE_OPEN) { + // This is the first time. + LOG_J(LS_INFO, this) << "DTLS handshake complete."; + if (dtls_->GetState() == rtc::SS_OPEN) { + // The check for OPEN shouldn't be necessary but let's make + // sure we don't accidentally frob the state if it's closed. + dtls_state_ = STATE_OPEN; + + set_readable(true); + set_writable(true); + } + } + if (sig & rtc::SE_READ) { + char buf[kMaxDtlsPacketLen]; + size_t read; + if (dtls_->Read(buf, sizeof(buf), &read, NULL) == rtc::SR_SUCCESS) { + SignalReadPacket(this, buf, read, rtc::CreatePacketTime(0), 0); + } + } + if (sig & rtc::SE_CLOSE) { + ASSERT(sig == rtc::SE_CLOSE); // SE_CLOSE should be by itself. + if (!err) { + LOG_J(LS_INFO, this) << "DTLS channel closed"; + } else { + LOG_J(LS_INFO, this) << "DTLS channel error, code=" << err; + } + + set_readable(false); + set_writable(false); + dtls_state_ = STATE_CLOSED; + } +} + +bool DtlsTransportChannelWrapper::MaybeStartDtls() { + if (channel_->writable()) { + if (dtls_->StartSSLWithPeer()) { + LOG_J(LS_ERROR, this) << "Couldn't start DTLS handshake"; + dtls_state_ = STATE_CLOSED; + return false; + } + LOG_J(LS_INFO, this) + << "DtlsTransportChannelWrapper: Started DTLS handshake"; + + dtls_state_ = STATE_STARTED; + } + return true; +} + +// Called from OnReadPacket when a DTLS packet is received. +bool DtlsTransportChannelWrapper::HandleDtlsPacket(const char* data, + size_t size) { + // Sanity check we're not passing junk that + // just looks like DTLS. + const uint8* tmp_data = reinterpret_cast(data); + size_t tmp_size = size; + while (tmp_size > 0) { + if (tmp_size < kDtlsRecordHeaderLen) + return false; // Too short for the header + + size_t record_len = (tmp_data[11] << 8) | (tmp_data[12]); + if ((record_len + kDtlsRecordHeaderLen) > tmp_size) + return false; // Body too short + + tmp_data += record_len + kDtlsRecordHeaderLen; + tmp_size -= record_len + kDtlsRecordHeaderLen; + } + + // Looks good. Pass to the SIC which ends up being passed to + // the DTLS stack. + return downward_->OnPacketReceived(data, size); +} + +void DtlsTransportChannelWrapper::OnRequestSignaling( + TransportChannelImpl* channel) { + ASSERT(channel == channel_); + SignalRequestSignaling(this); +} + +void DtlsTransportChannelWrapper::OnCandidateReady( + TransportChannelImpl* channel, const Candidate& c) { + ASSERT(channel == channel_); + SignalCandidateReady(this, c); +} + +void DtlsTransportChannelWrapper::OnCandidatesAllocationDone( + TransportChannelImpl* channel) { + ASSERT(channel == channel_); + SignalCandidatesAllocationDone(this); +} + +void DtlsTransportChannelWrapper::OnRoleConflict( + TransportChannelImpl* channel) { + ASSERT(channel == channel_); + SignalRoleConflict(this); +} + +void DtlsTransportChannelWrapper::OnRouteChange( + TransportChannel* channel, const Candidate& candidate) { + ASSERT(channel == channel_); + SignalRouteChange(this, candidate); +} + +void DtlsTransportChannelWrapper::OnConnectionRemoved( + TransportChannelImpl* channel) { + ASSERT(channel == channel_); + SignalConnectionRemoved(this); +} + +} // namespace cricket diff --git a/webrtc/p2p/base/dtlstransportchannel.h b/webrtc/p2p/base/dtlstransportchannel.h new file mode 100644 index 000000000..d12f724dd --- /dev/null +++ b/webrtc/p2p/base/dtlstransportchannel.h @@ -0,0 +1,246 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_DTLSTRANSPORTCHANNEL_H_ +#define WEBRTC_P2P_BASE_DTLSTRANSPORTCHANNEL_H_ + +#include +#include + +#include "webrtc/p2p/base/transportchannelimpl.h" +#include "webrtc/base/buffer.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sslstreamadapter.h" +#include "webrtc/base/stream.h" + +namespace cricket { + +// A bridge between a packet-oriented/channel-type interface on +// the bottom and a StreamInterface on the top. +class StreamInterfaceChannel : public rtc::StreamInterface, + public sigslot::has_slots<> { + public: + StreamInterfaceChannel(rtc::Thread* owner, TransportChannel* channel) + : channel_(channel), + state_(rtc::SS_OPEN), + fifo_(kFifoSize, owner) { + fifo_.SignalEvent.connect(this, &StreamInterfaceChannel::OnEvent); + } + + // Push in a packet; this gets pulled out from Read(). + bool OnPacketReceived(const char* data, size_t size); + + // Implementations of StreamInterface + virtual rtc::StreamState GetState() const { return state_; } + virtual void Close() { state_ = rtc::SS_CLOSED; } + virtual rtc::StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual rtc::StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + + private: + static const size_t kFifoSize = 8192; + + // Forward events + virtual void OnEvent(rtc::StreamInterface* stream, int sig, int err); + + TransportChannel* channel_; // owned by DtlsTransportChannelWrapper + rtc::StreamState state_; + rtc::FifoBuffer fifo_; + + DISALLOW_COPY_AND_ASSIGN(StreamInterfaceChannel); +}; + + +// This class provides a DTLS SSLStreamAdapter inside a TransportChannel-style +// packet-based interface, wrapping an existing TransportChannel instance +// (e.g a P2PTransportChannel) +// Here's the way this works: +// +// DtlsTransportChannelWrapper { +// SSLStreamAdapter* dtls_ { +// StreamInterfaceChannel downward_ { +// TransportChannelImpl* channel_; +// } +// } +// } +// +// - Data which comes into DtlsTransportChannelWrapper from the underlying +// channel_ via OnReadPacket() is checked for whether it is DTLS +// or not, and if it is, is passed to DtlsTransportChannelWrapper:: +// HandleDtlsPacket, which pushes it into to downward_. +// dtls_ is listening for events on downward_, so it immediately calls +// downward_->Read(). +// +// - Data written to DtlsTransportChannelWrapper is passed either to +// downward_ or directly to channel_, depending on whether DTLS is +// negotiated and whether the flags include PF_SRTP_BYPASS +// +// - The SSLStreamAdapter writes to downward_->Write() +// which translates it into packet writes on channel_. +class DtlsTransportChannelWrapper : public TransportChannelImpl { + public: + enum State { + STATE_NONE, // No state or rejected. + STATE_OFFERED, // Our identity has been set. + STATE_ACCEPTED, // The other side sent a fingerprint. + STATE_STARTED, // We are negotiating. + STATE_OPEN, // Negotiation complete. + STATE_CLOSED // Connection closed. + }; + + // The parameters here are: + // transport -- the DtlsTransport that created us + // channel -- the TransportChannel we are wrapping + DtlsTransportChannelWrapper(Transport* transport, + TransportChannelImpl* channel); + virtual ~DtlsTransportChannelWrapper(); + + virtual void SetIceRole(IceRole role) { + channel_->SetIceRole(role); + } + virtual IceRole GetIceRole() const { + return channel_->GetIceRole(); + } + virtual size_t GetConnectionCount() const { + return channel_->GetConnectionCount(); + } + virtual bool SetLocalIdentity(rtc::SSLIdentity *identity); + virtual bool GetLocalIdentity(rtc::SSLIdentity** identity) const; + + virtual bool SetRemoteFingerprint(const std::string& digest_alg, + const uint8* digest, + size_t digest_len); + virtual bool IsDtlsActive() const { return dtls_state_ != STATE_NONE; } + + // Called to send a packet (via DTLS, if turned on). + virtual int SendPacket(const char* data, size_t size, + const rtc::PacketOptions& options, + int flags); + + // TransportChannel calls that we forward to the wrapped transport. + virtual int SetOption(rtc::Socket::Option opt, int value) { + return channel_->SetOption(opt, value); + } + virtual int GetError() { + return channel_->GetError(); + } + virtual bool GetStats(ConnectionInfos* infos) { + return channel_->GetStats(infos); + } + virtual const std::string SessionId() const { + return channel_->SessionId(); + } + + // Set up the ciphers to use for DTLS-SRTP. If this method is not called + // before DTLS starts, or |ciphers| is empty, SRTP keys won't be negotiated. + // This method should be called before SetupDtls. + virtual bool SetSrtpCiphers(const std::vector& ciphers); + + // Find out which DTLS-SRTP cipher was negotiated + virtual bool GetSrtpCipher(std::string* cipher); + + virtual bool GetSslRole(rtc::SSLRole* role) const; + virtual bool SetSslRole(rtc::SSLRole role); + + // Once DTLS has been established, this method retrieves the certificate in + // use by the remote peer, for use in external identity verification. + virtual bool GetRemoteCertificate(rtc::SSLCertificate** cert) const; + + // Once DTLS has established (i.e., this channel is writable), this method + // extracts the keys negotiated during the DTLS handshake, for use in external + // encryption. DTLS-SRTP uses this to extract the needed SRTP keys. + // See the SSLStreamAdapter documentation for info on the specific parameters. + virtual bool ExportKeyingMaterial(const std::string& label, + const uint8* context, + size_t context_len, + bool use_context, + uint8* result, + size_t result_len) { + return (dtls_.get()) ? dtls_->ExportKeyingMaterial(label, context, + context_len, + use_context, + result, result_len) + : false; + } + + // TransportChannelImpl calls. + virtual Transport* GetTransport() { + return transport_; + } + virtual void SetIceTiebreaker(uint64 tiebreaker) { + channel_->SetIceTiebreaker(tiebreaker); + } + virtual bool GetIceProtocolType(IceProtocolType* type) const { + return channel_->GetIceProtocolType(type); + } + virtual void SetIceProtocolType(IceProtocolType type) { + channel_->SetIceProtocolType(type); + } + virtual void SetIceCredentials(const std::string& ice_ufrag, + const std::string& ice_pwd) { + channel_->SetIceCredentials(ice_ufrag, ice_pwd); + } + virtual void SetRemoteIceCredentials(const std::string& ice_ufrag, + const std::string& ice_pwd) { + channel_->SetRemoteIceCredentials(ice_ufrag, ice_pwd); + } + virtual void SetRemoteIceMode(IceMode mode) { + channel_->SetRemoteIceMode(mode); + } + + virtual void Connect(); + virtual void Reset(); + + virtual void OnSignalingReady() { + channel_->OnSignalingReady(); + } + virtual void OnCandidate(const Candidate& candidate) { + channel_->OnCandidate(candidate); + } + + // Needed by DtlsTransport. + TransportChannelImpl* channel() { return channel_; } + + private: + void OnReadableState(TransportChannel* channel); + void OnWritableState(TransportChannel* channel); + void OnReadPacket(TransportChannel* channel, const char* data, size_t size, + const rtc::PacketTime& packet_time, int flags); + void OnReadyToSend(TransportChannel* channel); + void OnDtlsEvent(rtc::StreamInterface* stream_, int sig, int err); + bool SetupDtls(); + bool MaybeStartDtls(); + bool HandleDtlsPacket(const char* data, size_t size); + void OnRequestSignaling(TransportChannelImpl* channel); + void OnCandidateReady(TransportChannelImpl* channel, const Candidate& c); + void OnCandidatesAllocationDone(TransportChannelImpl* channel); + void OnRoleConflict(TransportChannelImpl* channel); + void OnRouteChange(TransportChannel* channel, const Candidate& candidate); + void OnConnectionRemoved(TransportChannelImpl* channel); + + Transport* transport_; // The transport_ that created us. + rtc::Thread* worker_thread_; // Everything should occur on this thread. + TransportChannelImpl* channel_; // Underlying channel, owned by transport_. + rtc::scoped_ptr dtls_; // The DTLS stream + StreamInterfaceChannel* downward_; // Wrapper for channel_, owned by dtls_. + std::vector srtp_ciphers_; // SRTP ciphers to use with DTLS. + State dtls_state_; + rtc::SSLIdentity* local_identity_; + rtc::SSLRole ssl_role_; + rtc::Buffer remote_fingerprint_value_; + std::string remote_fingerprint_algorithm_; + + DISALLOW_COPY_AND_ASSIGN(DtlsTransportChannelWrapper); +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_DTLSTRANSPORTCHANNEL_H_ diff --git a/webrtc/p2p/base/dtlstransportchannel_unittest.cc b/webrtc/p2p/base/dtlstransportchannel_unittest.cc new file mode 100644 index 000000000..52f8c1e72 --- /dev/null +++ b/webrtc/p2p/base/dtlstransportchannel_unittest.cc @@ -0,0 +1,823 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/p2p/base/dtlstransport.h" +#include "webrtc/p2p/base/fakesession.h" +#include "webrtc/base/common.h" +#include "webrtc/base/dscp.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/ssladapter.h" +#include "webrtc/base/sslidentity.h" +#include "webrtc/base/sslstreamadapter.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/thread.h" + +#define MAYBE_SKIP_TEST(feature) \ + if (!(rtc::SSLStreamAdapter::feature())) { \ + LOG(LS_INFO) << "Feature disabled... skipping"; \ + return; \ + } + +static const char AES_CM_128_HMAC_SHA1_80[] = "AES_CM_128_HMAC_SHA1_80"; +static const char kIceUfrag1[] = "TESTICEUFRAG0001"; +static const char kIcePwd1[] = "TESTICEPWD00000000000001"; +static const size_t kPacketNumOffset = 8; +static const size_t kPacketHeaderLen = 12; + +static bool IsRtpLeadByte(uint8 b) { + return ((b & 0xC0) == 0x80); +} + +using cricket::ConnectionRole; + +enum Flags { NF_REOFFER = 0x1, NF_EXPECT_FAILURE = 0x2 }; + +class DtlsTestClient : public sigslot::has_slots<> { + public: + DtlsTestClient(const std::string& name, + rtc::Thread* signaling_thread, + rtc::Thread* worker_thread) : + name_(name), + signaling_thread_(signaling_thread), + worker_thread_(worker_thread), + protocol_(cricket::ICEPROTO_GOOGLE), + packet_size_(0), + use_dtls_srtp_(false), + negotiated_dtls_(false), + received_dtls_client_hello_(false), + received_dtls_server_hello_(false) { + } + void SetIceProtocol(cricket::TransportProtocol proto) { + protocol_ = proto; + } + void CreateIdentity() { + identity_.reset(rtc::SSLIdentity::Generate(name_)); + } + rtc::SSLIdentity* identity() { return identity_.get(); } + void SetupSrtp() { + ASSERT(identity_.get() != NULL); + use_dtls_srtp_ = true; + } + void SetupChannels(int count, cricket::IceRole role) { + transport_.reset(new cricket::DtlsTransport( + signaling_thread_, worker_thread_, "dtls content name", NULL, + identity_.get())); + transport_->SetAsync(true); + transport_->SetIceRole(role); + transport_->SetIceTiebreaker( + (role == cricket::ICEROLE_CONTROLLING) ? 1 : 2); + transport_->SignalWritableState.connect(this, + &DtlsTestClient::OnTransportWritableState); + + for (int i = 0; i < count; ++i) { + cricket::DtlsTransportChannelWrapper* channel = + static_cast( + transport_->CreateChannel(i)); + ASSERT_TRUE(channel != NULL); + channel->SignalWritableState.connect(this, + &DtlsTestClient::OnTransportChannelWritableState); + channel->SignalReadPacket.connect(this, + &DtlsTestClient::OnTransportChannelReadPacket); + channels_.push_back(channel); + + // Hook the raw packets so that we can verify they are encrypted. + channel->channel()->SignalReadPacket.connect( + this, &DtlsTestClient::OnFakeTransportChannelReadPacket); + } + } + + cricket::Transport* transport() { return transport_.get(); } + + cricket::FakeTransportChannel* GetFakeChannel(int component) { + cricket::TransportChannelImpl* ch = transport_->GetChannel(component); + cricket::DtlsTransportChannelWrapper* wrapper = + static_cast(ch); + return (wrapper) ? + static_cast(wrapper->channel()) : NULL; + } + + // Offer DTLS if we have an identity; pass in a remote fingerprint only if + // both sides support DTLS. + void Negotiate(DtlsTestClient* peer, cricket::ContentAction action, + ConnectionRole local_role, ConnectionRole remote_role, + int flags) { + Negotiate(identity_.get(), (identity_) ? peer->identity_.get() : NULL, + action, local_role, remote_role, flags); + } + + // Allow any DTLS configuration to be specified (including invalid ones). + void Negotiate(rtc::SSLIdentity* local_identity, + rtc::SSLIdentity* remote_identity, + cricket::ContentAction action, + ConnectionRole local_role, + ConnectionRole remote_role, + int flags) { + rtc::scoped_ptr local_fingerprint; + rtc::scoped_ptr remote_fingerprint; + if (local_identity) { + local_fingerprint.reset(rtc::SSLFingerprint::Create( + rtc::DIGEST_SHA_1, local_identity)); + ASSERT_TRUE(local_fingerprint.get() != NULL); + } + if (remote_identity) { + remote_fingerprint.reset(rtc::SSLFingerprint::Create( + rtc::DIGEST_SHA_1, remote_identity)); + ASSERT_TRUE(remote_fingerprint.get() != NULL); + } + + if (use_dtls_srtp_ && !(flags & NF_REOFFER)) { + // SRTP ciphers will be set only in the beginning. + for (std::vector::iterator it = + channels_.begin(); it != channels_.end(); ++it) { + std::vector ciphers; + ciphers.push_back(AES_CM_128_HMAC_SHA1_80); + ASSERT_TRUE((*it)->SetSrtpCiphers(ciphers)); + } + } + + std::string transport_type = (protocol_ == cricket::ICEPROTO_GOOGLE) ? + cricket::NS_GINGLE_P2P : cricket::NS_JINGLE_ICE_UDP; + cricket::TransportDescription local_desc( + transport_type, std::vector(), kIceUfrag1, kIcePwd1, + cricket::ICEMODE_FULL, local_role, + // If remote if the offerer and has no DTLS support, answer will be + // without any fingerprint. + (action == cricket::CA_ANSWER && !remote_identity) ? + NULL : local_fingerprint.get(), + cricket::Candidates()); + + cricket::TransportDescription remote_desc( + transport_type, std::vector(), kIceUfrag1, kIcePwd1, + cricket::ICEMODE_FULL, remote_role, remote_fingerprint.get(), + cricket::Candidates()); + + bool expect_success = (flags & NF_EXPECT_FAILURE) ? false : true; + // If |expect_success| is false, expect SRTD or SLTD to fail when + // content action is CA_ANSWER. + if (action == cricket::CA_OFFER) { + ASSERT_TRUE(transport_->SetLocalTransportDescription( + local_desc, cricket::CA_OFFER, NULL)); + ASSERT_EQ(expect_success, transport_->SetRemoteTransportDescription( + remote_desc, cricket::CA_ANSWER, NULL)); + } else { + ASSERT_TRUE(transport_->SetRemoteTransportDescription( + remote_desc, cricket::CA_OFFER, NULL)); + ASSERT_EQ(expect_success, transport_->SetLocalTransportDescription( + local_desc, cricket::CA_ANSWER, NULL)); + } + negotiated_dtls_ = (local_identity && remote_identity); + } + + bool Connect(DtlsTestClient* peer) { + transport_->ConnectChannels(); + transport_->SetDestination(peer->transport_.get()); + return true; + } + + bool writable() const { return transport_->writable(); } + + void CheckRole(rtc::SSLRole role) { + if (role == rtc::SSL_CLIENT) { + ASSERT_FALSE(received_dtls_client_hello_); + ASSERT_TRUE(received_dtls_server_hello_); + } else { + ASSERT_TRUE(received_dtls_client_hello_); + ASSERT_FALSE(received_dtls_server_hello_); + } + } + + void CheckSrtp(const std::string& expected_cipher) { + for (std::vector::iterator it = + channels_.begin(); it != channels_.end(); ++it) { + std::string cipher; + + bool rv = (*it)->GetSrtpCipher(&cipher); + if (negotiated_dtls_ && !expected_cipher.empty()) { + ASSERT_TRUE(rv); + + ASSERT_EQ(cipher, expected_cipher); + } else { + ASSERT_FALSE(rv); + } + } + } + + void SendPackets(size_t channel, size_t size, size_t count, bool srtp) { + ASSERT(channel < channels_.size()); + rtc::scoped_ptr packet(new char[size]); + size_t sent = 0; + do { + // Fill the packet with a known value and a sequence number to check + // against, and make sure that it doesn't look like DTLS. + memset(packet.get(), sent & 0xff, size); + packet[0] = (srtp) ? 0x80 : 0x00; + rtc::SetBE32(packet.get() + kPacketNumOffset, + static_cast(sent)); + + // Only set the bypass flag if we've activated DTLS. + int flags = (identity_.get() && srtp) ? cricket::PF_SRTP_BYPASS : 0; + rtc::PacketOptions packet_options; + int rv = channels_[channel]->SendPacket( + packet.get(), size, packet_options, flags); + ASSERT_GT(rv, 0); + ASSERT_EQ(size, static_cast(rv)); + ++sent; + } while (sent < count); + } + + int SendInvalidSrtpPacket(size_t channel, size_t size) { + ASSERT(channel < channels_.size()); + rtc::scoped_ptr packet(new char[size]); + // Fill the packet with 0 to form an invalid SRTP packet. + memset(packet.get(), 0, size); + + rtc::PacketOptions packet_options; + return channels_[channel]->SendPacket( + packet.get(), size, packet_options, cricket::PF_SRTP_BYPASS); + } + + void ExpectPackets(size_t channel, size_t size) { + packet_size_ = size; + received_.clear(); + } + + size_t NumPacketsReceived() { + return received_.size(); + } + + bool VerifyPacket(const char* data, size_t size, uint32* out_num) { + if (size != packet_size_ || + (data[0] != 0 && static_cast(data[0]) != 0x80)) { + return false; + } + uint32 packet_num = rtc::GetBE32(data + kPacketNumOffset); + for (size_t i = kPacketHeaderLen; i < size; ++i) { + if (static_cast(data[i]) != (packet_num & 0xff)) { + return false; + } + } + if (out_num) { + *out_num = packet_num; + } + return true; + } + bool VerifyEncryptedPacket(const char* data, size_t size) { + // This is an encrypted data packet; let's make sure it's mostly random; + // less than 10% of the bytes should be equal to the cleartext packet. + if (size <= packet_size_) { + return false; + } + uint32 packet_num = rtc::GetBE32(data + kPacketNumOffset); + int num_matches = 0; + for (size_t i = kPacketNumOffset; i < size; ++i) { + if (static_cast(data[i]) == (packet_num & 0xff)) { + ++num_matches; + } + } + return (num_matches < ((static_cast(size) - 5) / 10)); + } + + // Transport callbacks + void OnTransportWritableState(cricket::Transport* transport) { + LOG(LS_INFO) << name_ << ": is writable"; + } + + // Transport channel callbacks + void OnTransportChannelWritableState(cricket::TransportChannel* channel) { + LOG(LS_INFO) << name_ << ": Channel '" << channel->component() + << "' is writable"; + } + + void OnTransportChannelReadPacket(cricket::TransportChannel* channel, + const char* data, size_t size, + const rtc::PacketTime& packet_time, + int flags) { + uint32 packet_num = 0; + ASSERT_TRUE(VerifyPacket(data, size, &packet_num)); + received_.insert(packet_num); + // Only DTLS-SRTP packets should have the bypass flag set. + int expected_flags = (identity_.get() && IsRtpLeadByte(data[0])) ? + cricket::PF_SRTP_BYPASS : 0; + ASSERT_EQ(expected_flags, flags); + } + + // Hook into the raw packet stream to make sure DTLS packets are encrypted. + void OnFakeTransportChannelReadPacket(cricket::TransportChannel* channel, + const char* data, size_t size, + const rtc::PacketTime& time, + int flags) { + // Flags shouldn't be set on the underlying TransportChannel packets. + ASSERT_EQ(0, flags); + + // Look at the handshake packets to see what role we played. + // Check that non-handshake packets are DTLS data or SRTP bypass. + if (negotiated_dtls_) { + if (data[0] == 22 && size > 17) { + if (data[13] == 1) { + received_dtls_client_hello_ = true; + } else if (data[13] == 2) { + received_dtls_server_hello_ = true; + } + } else if (!(data[0] >= 20 && data[0] <= 22)) { + ASSERT_TRUE(data[0] == 23 || IsRtpLeadByte(data[0])); + if (data[0] == 23) { + ASSERT_TRUE(VerifyEncryptedPacket(data, size)); + } else if (IsRtpLeadByte(data[0])) { + ASSERT_TRUE(VerifyPacket(data, size, NULL)); + } + } + } + } + + private: + std::string name_; + rtc::Thread* signaling_thread_; + rtc::Thread* worker_thread_; + cricket::TransportProtocol protocol_; + rtc::scoped_ptr identity_; + rtc::scoped_ptr transport_; + std::vector channels_; + size_t packet_size_; + std::set received_; + bool use_dtls_srtp_; + bool negotiated_dtls_; + bool received_dtls_client_hello_; + bool received_dtls_server_hello_; +}; + + +class DtlsTransportChannelTest : public testing::Test { + public: + DtlsTransportChannelTest() : + client1_("P1", rtc::Thread::Current(), + rtc::Thread::Current()), + client2_("P2", rtc::Thread::Current(), + rtc::Thread::Current()), + channel_ct_(1), + use_dtls_(false), + use_dtls_srtp_(false) { + } + + void SetChannelCount(size_t channel_ct) { + channel_ct_ = static_cast(channel_ct); + } + void PrepareDtls(bool c1, bool c2) { + if (c1) { + client1_.CreateIdentity(); + } + if (c2) { + client2_.CreateIdentity(); + } + if (c1 && c2) + use_dtls_ = true; + } + void PrepareDtlsSrtp(bool c1, bool c2) { + if (!use_dtls_) + return; + + if (c1) + client1_.SetupSrtp(); + if (c2) + client2_.SetupSrtp(); + + if (c1 && c2) + use_dtls_srtp_ = true; + } + + bool Connect(ConnectionRole client1_role, ConnectionRole client2_role) { + Negotiate(client1_role, client2_role); + + bool rv = client1_.Connect(&client2_); + EXPECT_TRUE(rv); + if (!rv) + return false; + + EXPECT_TRUE_WAIT(client1_.writable() && client2_.writable(), 10000); + if (!client1_.writable() || !client2_.writable()) + return false; + + // Check that we used the right roles. + if (use_dtls_) { + rtc::SSLRole client1_ssl_role = + (client1_role == cricket::CONNECTIONROLE_ACTIVE || + (client2_role == cricket::CONNECTIONROLE_PASSIVE && + client1_role == cricket::CONNECTIONROLE_ACTPASS)) ? + rtc::SSL_CLIENT : rtc::SSL_SERVER; + + rtc::SSLRole client2_ssl_role = + (client2_role == cricket::CONNECTIONROLE_ACTIVE || + (client1_role == cricket::CONNECTIONROLE_PASSIVE && + client2_role == cricket::CONNECTIONROLE_ACTPASS)) ? + rtc::SSL_CLIENT : rtc::SSL_SERVER; + + client1_.CheckRole(client1_ssl_role); + client2_.CheckRole(client2_ssl_role); + } + + // Check that we negotiated the right ciphers. + if (use_dtls_srtp_) { + client1_.CheckSrtp(AES_CM_128_HMAC_SHA1_80); + client2_.CheckSrtp(AES_CM_128_HMAC_SHA1_80); + } else { + client1_.CheckSrtp(""); + client2_.CheckSrtp(""); + } + + return true; + } + + bool Connect() { + // By default, Client1 will be Server and Client2 will be Client. + return Connect(cricket::CONNECTIONROLE_ACTPASS, + cricket::CONNECTIONROLE_ACTIVE); + } + + void Negotiate() { + Negotiate(cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_ACTIVE); + } + + void Negotiate(ConnectionRole client1_role, ConnectionRole client2_role) { + client1_.SetupChannels(channel_ct_, cricket::ICEROLE_CONTROLLING); + client2_.SetupChannels(channel_ct_, cricket::ICEROLE_CONTROLLED); + // Expect success from SLTD and SRTD. + client1_.Negotiate(&client2_, cricket::CA_OFFER, + client1_role, client2_role, 0); + client2_.Negotiate(&client1_, cricket::CA_ANSWER, + client2_role, client1_role, 0); + } + + // Negotiate with legacy client |client2|. Legacy client doesn't use setup + // attributes, except NONE. + void NegotiateWithLegacy() { + client1_.SetupChannels(channel_ct_, cricket::ICEROLE_CONTROLLING); + client2_.SetupChannels(channel_ct_, cricket::ICEROLE_CONTROLLED); + // Expect success from SLTD and SRTD. + client1_.Negotiate(&client2_, cricket::CA_OFFER, + cricket::CONNECTIONROLE_ACTPASS, + cricket::CONNECTIONROLE_NONE, 0); + client2_.Negotiate(&client1_, cricket::CA_ANSWER, + cricket::CONNECTIONROLE_ACTIVE, + cricket::CONNECTIONROLE_NONE, 0); + } + + void Renegotiate(DtlsTestClient* reoffer_initiator, + ConnectionRole client1_role, ConnectionRole client2_role, + int flags) { + if (reoffer_initiator == &client1_) { + client1_.Negotiate(&client2_, cricket::CA_OFFER, + client1_role, client2_role, flags); + client2_.Negotiate(&client1_, cricket::CA_ANSWER, + client2_role, client1_role, flags); + } else { + client2_.Negotiate(&client1_, cricket::CA_OFFER, + client2_role, client1_role, flags); + client1_.Negotiate(&client2_, cricket::CA_ANSWER, + client1_role, client2_role, flags); + } + } + + void TestTransfer(size_t channel, size_t size, size_t count, bool srtp) { + LOG(LS_INFO) << "Expect packets, size=" << size; + client2_.ExpectPackets(channel, size); + client1_.SendPackets(channel, size, count, srtp); + EXPECT_EQ_WAIT(count, client2_.NumPacketsReceived(), 10000); + } + + protected: + DtlsTestClient client1_; + DtlsTestClient client2_; + int channel_ct_; + bool use_dtls_; + bool use_dtls_srtp_; +}; + +// Test that transport negotiation of ICE, no DTLS works properly. +TEST_F(DtlsTransportChannelTest, TestChannelSetupIce) { + client1_.SetIceProtocol(cricket::ICEPROTO_RFC5245); + client2_.SetIceProtocol(cricket::ICEPROTO_RFC5245); + Negotiate(); + cricket::FakeTransportChannel* channel1 = client1_.GetFakeChannel(0); + cricket::FakeTransportChannel* channel2 = client2_.GetFakeChannel(0); + ASSERT_TRUE(channel1 != NULL); + ASSERT_TRUE(channel2 != NULL); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel1->GetIceRole()); + EXPECT_EQ(1U, channel1->IceTiebreaker()); + EXPECT_EQ(cricket::ICEPROTO_RFC5245, channel1->protocol()); + EXPECT_EQ(kIceUfrag1, channel1->ice_ufrag()); + EXPECT_EQ(kIcePwd1, channel1->ice_pwd()); + EXPECT_EQ(cricket::ICEROLE_CONTROLLED, channel2->GetIceRole()); + EXPECT_EQ(2U, channel2->IceTiebreaker()); + EXPECT_EQ(cricket::ICEPROTO_RFC5245, channel2->protocol()); +} + +// Test that transport negotiation of GICE, no DTLS works properly. +TEST_F(DtlsTransportChannelTest, TestChannelSetupGice) { + client1_.SetIceProtocol(cricket::ICEPROTO_GOOGLE); + client2_.SetIceProtocol(cricket::ICEPROTO_GOOGLE); + Negotiate(); + cricket::FakeTransportChannel* channel1 = client1_.GetFakeChannel(0); + cricket::FakeTransportChannel* channel2 = client2_.GetFakeChannel(0); + ASSERT_TRUE(channel1 != NULL); + ASSERT_TRUE(channel2 != NULL); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel1->GetIceRole()); + EXPECT_EQ(1U, channel1->IceTiebreaker()); + EXPECT_EQ(cricket::ICEPROTO_GOOGLE, channel1->protocol()); + EXPECT_EQ(kIceUfrag1, channel1->ice_ufrag()); + EXPECT_EQ(kIcePwd1, channel1->ice_pwd()); + EXPECT_EQ(cricket::ICEROLE_CONTROLLED, channel2->GetIceRole()); + EXPECT_EQ(2U, channel2->IceTiebreaker()); + EXPECT_EQ(cricket::ICEPROTO_GOOGLE, channel2->protocol()); +} + +// Connect without DTLS, and transfer some data. +TEST_F(DtlsTransportChannelTest, TestTransfer) { + ASSERT_TRUE(Connect()); + TestTransfer(0, 1000, 100, false); +} + +// Create two channels without DTLS, and transfer some data. +TEST_F(DtlsTransportChannelTest, TestTransferTwoChannels) { + SetChannelCount(2); + ASSERT_TRUE(Connect()); + TestTransfer(0, 1000, 100, false); + TestTransfer(1, 1000, 100, false); +} + +// Connect without DTLS, and transfer SRTP data. +TEST_F(DtlsTransportChannelTest, TestTransferSrtp) { + ASSERT_TRUE(Connect()); + TestTransfer(0, 1000, 100, true); +} + +// Create two channels without DTLS, and transfer SRTP data. +TEST_F(DtlsTransportChannelTest, TestTransferSrtpTwoChannels) { + SetChannelCount(2); + ASSERT_TRUE(Connect()); + TestTransfer(0, 1000, 100, true); + TestTransfer(1, 1000, 100, true); +} + +// Connect with DTLS, and transfer some data. +TEST_F(DtlsTransportChannelTest, TestTransferDtls) { + MAYBE_SKIP_TEST(HaveDtls); + PrepareDtls(true, true); + ASSERT_TRUE(Connect()); + TestTransfer(0, 1000, 100, false); +} + +// Create two channels with DTLS, and transfer some data. +TEST_F(DtlsTransportChannelTest, TestTransferDtlsTwoChannels) { + MAYBE_SKIP_TEST(HaveDtls); + SetChannelCount(2); + PrepareDtls(true, true); + ASSERT_TRUE(Connect()); + TestTransfer(0, 1000, 100, false); + TestTransfer(1, 1000, 100, false); +} + +// Connect with A doing DTLS and B not, and transfer some data. +TEST_F(DtlsTransportChannelTest, TestTransferDtlsRejected) { + PrepareDtls(true, false); + ASSERT_TRUE(Connect()); + TestTransfer(0, 1000, 100, false); +} + +// Connect with B doing DTLS and A not, and transfer some data. +TEST_F(DtlsTransportChannelTest, TestTransferDtlsNotOffered) { + PrepareDtls(false, true); + ASSERT_TRUE(Connect()); + TestTransfer(0, 1000, 100, false); +} + +// Connect with DTLS, negotiate DTLS-SRTP, and transfer SRTP using bypass. +TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtp) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + PrepareDtls(true, true); + PrepareDtlsSrtp(true, true); + ASSERT_TRUE(Connect()); + TestTransfer(0, 1000, 100, true); +} + +// Connect with DTLS-SRTP, transfer an invalid SRTP packet, and expects -1 +// returned. +TEST_F(DtlsTransportChannelTest, TestTransferDtlsInvalidSrtpPacket) { + MAYBE_SKIP_TEST(HaveDtls); + PrepareDtls(true, true); + PrepareDtlsSrtp(true, true); + ASSERT_TRUE(Connect()); + int result = client1_.SendInvalidSrtpPacket(0, 100); + ASSERT_EQ(-1, result); +} + +// Connect with DTLS. A does DTLS-SRTP but B does not. +TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpRejected) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + PrepareDtls(true, true); + PrepareDtlsSrtp(true, false); + ASSERT_TRUE(Connect()); +} + +// Connect with DTLS. B does DTLS-SRTP but A does not. +TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpNotOffered) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + PrepareDtls(true, true); + PrepareDtlsSrtp(false, true); + ASSERT_TRUE(Connect()); +} + +// Create two channels with DTLS, negotiate DTLS-SRTP, and transfer bypass SRTP. +TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpTwoChannels) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + SetChannelCount(2); + PrepareDtls(true, true); + PrepareDtlsSrtp(true, true); + ASSERT_TRUE(Connect()); + TestTransfer(0, 1000, 100, true); + TestTransfer(1, 1000, 100, true); +} + +// Create a single channel with DTLS, and send normal data and SRTP data on it. +TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpDemux) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + PrepareDtls(true, true); + PrepareDtlsSrtp(true, true); + ASSERT_TRUE(Connect()); + TestTransfer(0, 1000, 100, false); + TestTransfer(0, 1000, 100, true); +} + +// Testing when the remote is passive. +TEST_F(DtlsTransportChannelTest, TestTransferDtlsAnswererIsPassive) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + SetChannelCount(2); + PrepareDtls(true, true); + PrepareDtlsSrtp(true, true); + ASSERT_TRUE(Connect(cricket::CONNECTIONROLE_ACTPASS, + cricket::CONNECTIONROLE_PASSIVE)); + TestTransfer(0, 1000, 100, true); + TestTransfer(1, 1000, 100, true); +} + +// Testing with the legacy DTLS client which doesn't use setup attribute. +// In this case legacy is the answerer. +TEST_F(DtlsTransportChannelTest, TestDtlsSetupWithLegacyAsAnswerer) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + PrepareDtls(true, true); + NegotiateWithLegacy(); + rtc::SSLRole channel1_role; + rtc::SSLRole channel2_role; + EXPECT_TRUE(client1_.transport()->GetSslRole(&channel1_role)); + EXPECT_TRUE(client2_.transport()->GetSslRole(&channel2_role)); + EXPECT_EQ(rtc::SSL_SERVER, channel1_role); + EXPECT_EQ(rtc::SSL_CLIENT, channel2_role); +} + +// Testing re offer/answer after the session is estbalished. Roles will be +// kept same as of the previous negotiation. +TEST_F(DtlsTransportChannelTest, TestDtlsReOfferFromOfferer) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + SetChannelCount(2); + PrepareDtls(true, true); + PrepareDtlsSrtp(true, true); + // Initial role for client1 is ACTPASS and client2 is ACTIVE. + ASSERT_TRUE(Connect(cricket::CONNECTIONROLE_ACTPASS, + cricket::CONNECTIONROLE_ACTIVE)); + TestTransfer(0, 1000, 100, true); + TestTransfer(1, 1000, 100, true); + // Using input roles for the re-offer. + Renegotiate(&client1_, cricket::CONNECTIONROLE_ACTPASS, + cricket::CONNECTIONROLE_ACTIVE, NF_REOFFER); + TestTransfer(0, 1000, 100, true); + TestTransfer(1, 1000, 100, true); +} + +TEST_F(DtlsTransportChannelTest, TestDtlsReOfferFromAnswerer) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + SetChannelCount(2); + PrepareDtls(true, true); + PrepareDtlsSrtp(true, true); + // Initial role for client1 is ACTPASS and client2 is ACTIVE. + ASSERT_TRUE(Connect(cricket::CONNECTIONROLE_ACTPASS, + cricket::CONNECTIONROLE_ACTIVE)); + TestTransfer(0, 1000, 100, true); + TestTransfer(1, 1000, 100, true); + // Using input roles for the re-offer. + Renegotiate(&client2_, cricket::CONNECTIONROLE_PASSIVE, + cricket::CONNECTIONROLE_ACTPASS, NF_REOFFER); + TestTransfer(0, 1000, 100, true); + TestTransfer(1, 1000, 100, true); +} + +// Test that any change in role after the intial setup will result in failure. +TEST_F(DtlsTransportChannelTest, TestDtlsRoleReversal) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + SetChannelCount(2); + PrepareDtls(true, true); + PrepareDtlsSrtp(true, true); + ASSERT_TRUE(Connect(cricket::CONNECTIONROLE_ACTPASS, + cricket::CONNECTIONROLE_PASSIVE)); + + // Renegotiate from client2 with actpass and client1 as active. + Renegotiate(&client2_, cricket::CONNECTIONROLE_ACTPASS, + cricket::CONNECTIONROLE_ACTIVE, + NF_REOFFER | NF_EXPECT_FAILURE); +} + +// Test that using different setup attributes which results in similar ssl +// role as the initial negotiation will result in success. +TEST_F(DtlsTransportChannelTest, TestDtlsReOfferWithDifferentSetupAttr) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + SetChannelCount(2); + PrepareDtls(true, true); + PrepareDtlsSrtp(true, true); + ASSERT_TRUE(Connect(cricket::CONNECTIONROLE_ACTPASS, + cricket::CONNECTIONROLE_PASSIVE)); + // Renegotiate from client2 with actpass and client1 as active. + Renegotiate(&client2_, cricket::CONNECTIONROLE_ACTIVE, + cricket::CONNECTIONROLE_ACTPASS, NF_REOFFER); + TestTransfer(0, 1000, 100, true); + TestTransfer(1, 1000, 100, true); +} + +// Test that re-negotiation can be started before the clients become connected +// in the first negotiation. +TEST_F(DtlsTransportChannelTest, TestRenegotiateBeforeConnect) { + MAYBE_SKIP_TEST(HaveDtlsSrtp); + SetChannelCount(2); + PrepareDtls(true, true); + PrepareDtlsSrtp(true, true); + Negotiate(); + + Renegotiate(&client1_, cricket::CONNECTIONROLE_ACTPASS, + cricket::CONNECTIONROLE_ACTIVE, NF_REOFFER); + bool rv = client1_.Connect(&client2_); + EXPECT_TRUE(rv); + EXPECT_TRUE_WAIT(client1_.writable() && client2_.writable(), 10000); + + TestTransfer(0, 1000, 100, true); + TestTransfer(1, 1000, 100, true); +} + +// Test Certificates state after negotiation but before connection. +TEST_F(DtlsTransportChannelTest, TestCertificatesBeforeConnect) { + MAYBE_SKIP_TEST(HaveDtls); + PrepareDtls(true, true); + Negotiate(); + + rtc::scoped_ptr identity1; + rtc::scoped_ptr identity2; + rtc::scoped_ptr remote_cert1; + rtc::scoped_ptr remote_cert2; + + // After negotiation, each side has a distinct local certificate, but still no + // remote certificate, because connection has not yet occurred. + ASSERT_TRUE(client1_.transport()->GetIdentity(identity1.accept())); + ASSERT_TRUE(client2_.transport()->GetIdentity(identity2.accept())); + ASSERT_NE(identity1->certificate().ToPEMString(), + identity2->certificate().ToPEMString()); + ASSERT_FALSE( + client1_.transport()->GetRemoteCertificate(remote_cert1.accept())); + ASSERT_FALSE(remote_cert1 != NULL); + ASSERT_FALSE( + client2_.transport()->GetRemoteCertificate(remote_cert2.accept())); + ASSERT_FALSE(remote_cert2 != NULL); +} + +// Test Certificates state after connection. +TEST_F(DtlsTransportChannelTest, TestCertificatesAfterConnect) { + MAYBE_SKIP_TEST(HaveDtls); + PrepareDtls(true, true); + ASSERT_TRUE(Connect()); + + rtc::scoped_ptr identity1; + rtc::scoped_ptr identity2; + rtc::scoped_ptr remote_cert1; + rtc::scoped_ptr remote_cert2; + + // After connection, each side has a distinct local certificate. + ASSERT_TRUE(client1_.transport()->GetIdentity(identity1.accept())); + ASSERT_TRUE(client2_.transport()->GetIdentity(identity2.accept())); + ASSERT_NE(identity1->certificate().ToPEMString(), + identity2->certificate().ToPEMString()); + + // Each side's remote certificate is the other side's local certificate. + ASSERT_TRUE( + client1_.transport()->GetRemoteCertificate(remote_cert1.accept())); + ASSERT_EQ(remote_cert1->ToPEMString(), + identity2->certificate().ToPEMString()); + ASSERT_TRUE( + client2_.transport()->GetRemoteCertificate(remote_cert2.accept())); + ASSERT_EQ(remote_cert2->ToPEMString(), + identity1->certificate().ToPEMString()); +} diff --git a/webrtc/p2p/base/fakesession.h b/webrtc/p2p/base/fakesession.h new file mode 100644 index 000000000..30bc98162 --- /dev/null +++ b/webrtc/p2p/base/fakesession.h @@ -0,0 +1,492 @@ +/* + * Copyright 2009 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_FAKESESSION_H_ +#define WEBRTC_P2P_BASE_FAKESESSION_H_ + +#include +#include +#include + +#include "webrtc/p2p/base/session.h" +#include "webrtc/p2p/base/transport.h" +#include "webrtc/p2p/base/transportchannel.h" +#include "webrtc/p2p/base/transportchannelimpl.h" +#include "webrtc/base/buffer.h" +#include "webrtc/base/fakesslidentity.h" +#include "webrtc/base/messagequeue.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/sslfingerprint.h" + +namespace cricket { + +class FakeTransport; + +struct PacketMessageData : public rtc::MessageData { + PacketMessageData(const char* data, size_t len) : packet(data, len) { + } + rtc::Buffer packet; +}; + +// Fake transport channel class, which can be passed to anything that needs a +// transport channel. Can be informed of another FakeTransportChannel via +// SetDestination. +class FakeTransportChannel : public TransportChannelImpl, + public rtc::MessageHandler { + public: + explicit FakeTransportChannel(Transport* transport, + const std::string& content_name, + int component) + : TransportChannelImpl(content_name, component), + transport_(transport), + dest_(NULL), + state_(STATE_INIT), + async_(false), + identity_(NULL), + do_dtls_(false), + role_(ICEROLE_UNKNOWN), + tiebreaker_(0), + ice_proto_(ICEPROTO_HYBRID), + remote_ice_mode_(ICEMODE_FULL), + dtls_fingerprint_("", NULL, 0), + ssl_role_(rtc::SSL_CLIENT), + connection_count_(0) { + } + ~FakeTransportChannel() { + Reset(); + } + + uint64 IceTiebreaker() const { return tiebreaker_; } + TransportProtocol protocol() const { return ice_proto_; } + IceMode remote_ice_mode() const { return remote_ice_mode_; } + const std::string& ice_ufrag() const { return ice_ufrag_; } + const std::string& ice_pwd() const { return ice_pwd_; } + const std::string& remote_ice_ufrag() const { return remote_ice_ufrag_; } + const std::string& remote_ice_pwd() const { return remote_ice_pwd_; } + const rtc::SSLFingerprint& dtls_fingerprint() const { + return dtls_fingerprint_; + } + + void SetAsync(bool async) { + async_ = async; + } + + virtual Transport* GetTransport() { + return transport_; + } + + virtual void SetIceRole(IceRole role) { role_ = role; } + virtual IceRole GetIceRole() const { return role_; } + virtual size_t GetConnectionCount() const { return connection_count_; } + virtual void SetIceTiebreaker(uint64 tiebreaker) { tiebreaker_ = tiebreaker; } + virtual bool GetIceProtocolType(IceProtocolType* type) const { + *type = ice_proto_; + return true; + } + virtual void SetIceProtocolType(IceProtocolType type) { ice_proto_ = type; } + virtual void SetIceCredentials(const std::string& ice_ufrag, + const std::string& ice_pwd) { + ice_ufrag_ = ice_ufrag; + ice_pwd_ = ice_pwd; + } + virtual void SetRemoteIceCredentials(const std::string& ice_ufrag, + const std::string& ice_pwd) { + remote_ice_ufrag_ = ice_ufrag; + remote_ice_pwd_ = ice_pwd; + } + + virtual void SetRemoteIceMode(IceMode mode) { remote_ice_mode_ = mode; } + virtual bool SetRemoteFingerprint(const std::string& alg, const uint8* digest, + size_t digest_len) { + dtls_fingerprint_ = rtc::SSLFingerprint(alg, digest, digest_len); + return true; + } + virtual bool SetSslRole(rtc::SSLRole role) { + ssl_role_ = role; + return true; + } + virtual bool GetSslRole(rtc::SSLRole* role) const { + *role = ssl_role_; + return true; + } + + virtual void Connect() { + if (state_ == STATE_INIT) { + state_ = STATE_CONNECTING; + } + } + virtual void Reset() { + if (state_ != STATE_INIT) { + state_ = STATE_INIT; + if (dest_) { + dest_->state_ = STATE_INIT; + dest_->dest_ = NULL; + dest_ = NULL; + } + } + } + + void SetWritable(bool writable) { + set_writable(writable); + } + + void SetDestination(FakeTransportChannel* dest) { + if (state_ == STATE_CONNECTING && dest) { + // This simulates the delivery of candidates. + dest_ = dest; + dest_->dest_ = this; + if (identity_ && dest_->identity_) { + do_dtls_ = true; + dest_->do_dtls_ = true; + NegotiateSrtpCiphers(); + } + state_ = STATE_CONNECTED; + dest_->state_ = STATE_CONNECTED; + set_writable(true); + dest_->set_writable(true); + } else if (state_ == STATE_CONNECTED && !dest) { + // Simulates loss of connectivity, by asymmetrically forgetting dest_. + dest_ = NULL; + state_ = STATE_CONNECTING; + set_writable(false); + } + } + + void SetConnectionCount(size_t connection_count) { + size_t old_connection_count = connection_count_; + connection_count_ = connection_count; + if (connection_count_ < old_connection_count) + SignalConnectionRemoved(this); + } + + virtual int SendPacket(const char* data, size_t len, + const rtc::PacketOptions& options, int flags) { + if (state_ != STATE_CONNECTED) { + return -1; + } + + if (flags != PF_SRTP_BYPASS && flags != 0) { + return -1; + } + + PacketMessageData* packet = new PacketMessageData(data, len); + if (async_) { + rtc::Thread::Current()->Post(this, 0, packet); + } else { + rtc::Thread::Current()->Send(this, 0, packet); + } + return static_cast(len); + } + virtual int SetOption(rtc::Socket::Option opt, int value) { + return true; + } + virtual int GetError() { + return 0; + } + + virtual void OnSignalingReady() { + } + virtual void OnCandidate(const Candidate& candidate) { + } + + virtual void OnMessage(rtc::Message* msg) { + PacketMessageData* data = static_cast( + msg->pdata); + dest_->SignalReadPacket(dest_, data->packet.data(), + data->packet.length(), + rtc::CreatePacketTime(0), 0); + delete data; + } + + bool SetLocalIdentity(rtc::SSLIdentity* identity) { + identity_ = identity; + return true; + } + + + void SetRemoteCertificate(rtc::FakeSSLCertificate* cert) { + remote_cert_ = cert; + } + + virtual bool IsDtlsActive() const { + return do_dtls_; + } + + virtual bool SetSrtpCiphers(const std::vector& ciphers) { + srtp_ciphers_ = ciphers; + return true; + } + + virtual bool GetSrtpCipher(std::string* cipher) { + if (!chosen_srtp_cipher_.empty()) { + *cipher = chosen_srtp_cipher_; + return true; + } + return false; + } + + virtual bool GetLocalIdentity(rtc::SSLIdentity** identity) const { + if (!identity_) + return false; + + *identity = identity_->GetReference(); + return true; + } + + virtual bool GetRemoteCertificate(rtc::SSLCertificate** cert) const { + if (!remote_cert_) + return false; + + *cert = remote_cert_->GetReference(); + return true; + } + + virtual bool ExportKeyingMaterial(const std::string& label, + const uint8* context, + size_t context_len, + bool use_context, + uint8* result, + size_t result_len) { + if (!chosen_srtp_cipher_.empty()) { + memset(result, 0xff, result_len); + return true; + } + + return false; + } + + virtual void NegotiateSrtpCiphers() { + for (std::vector::const_iterator it1 = srtp_ciphers_.begin(); + it1 != srtp_ciphers_.end(); ++it1) { + for (std::vector::const_iterator it2 = + dest_->srtp_ciphers_.begin(); + it2 != dest_->srtp_ciphers_.end(); ++it2) { + if (*it1 == *it2) { + chosen_srtp_cipher_ = *it1; + dest_->chosen_srtp_cipher_ = *it2; + return; + } + } + } + } + + virtual bool GetStats(ConnectionInfos* infos) OVERRIDE { + ConnectionInfo info; + infos->clear(); + infos->push_back(info); + return true; + } + + private: + enum State { STATE_INIT, STATE_CONNECTING, STATE_CONNECTED }; + Transport* transport_; + FakeTransportChannel* dest_; + State state_; + bool async_; + rtc::SSLIdentity* identity_; + rtc::FakeSSLCertificate* remote_cert_; + bool do_dtls_; + std::vector srtp_ciphers_; + std::string chosen_srtp_cipher_; + IceRole role_; + uint64 tiebreaker_; + IceProtocolType ice_proto_; + std::string ice_ufrag_; + std::string ice_pwd_; + std::string remote_ice_ufrag_; + std::string remote_ice_pwd_; + IceMode remote_ice_mode_; + rtc::SSLFingerprint dtls_fingerprint_; + rtc::SSLRole ssl_role_; + size_t connection_count_; +}; + +// Fake transport class, which can be passed to anything that needs a Transport. +// Can be informed of another FakeTransport via SetDestination (low-tech way +// of doing candidates) +class FakeTransport : public Transport { + public: + typedef std::map ChannelMap; + FakeTransport(rtc::Thread* signaling_thread, + rtc::Thread* worker_thread, + const std::string& content_name, + PortAllocator* alllocator = NULL) + : Transport(signaling_thread, worker_thread, + content_name, "test_type", NULL), + dest_(NULL), + async_(false), + identity_(NULL) { + } + ~FakeTransport() { + DestroyAllChannels(); + } + + const ChannelMap& channels() const { return channels_; } + + void SetAsync(bool async) { async_ = async; } + void SetDestination(FakeTransport* dest) { + dest_ = dest; + for (ChannelMap::iterator it = channels_.begin(); it != channels_.end(); + ++it) { + it->second->SetLocalIdentity(identity_); + SetChannelDestination(it->first, it->second); + } + } + + void SetWritable(bool writable) { + for (ChannelMap::iterator it = channels_.begin(); it != channels_.end(); + ++it) { + it->second->SetWritable(writable); + } + } + + void set_identity(rtc::SSLIdentity* identity) { + identity_ = identity; + } + + using Transport::local_description; + using Transport::remote_description; + + protected: + virtual TransportChannelImpl* CreateTransportChannel(int component) { + if (channels_.find(component) != channels_.end()) { + return NULL; + } + FakeTransportChannel* channel = + new FakeTransportChannel(this, content_name(), component); + channel->SetAsync(async_); + SetChannelDestination(component, channel); + channels_[component] = channel; + return channel; + } + virtual void DestroyTransportChannel(TransportChannelImpl* channel) { + channels_.erase(channel->component()); + delete channel; + } + virtual void SetIdentity_w(rtc::SSLIdentity* identity) { + identity_ = identity; + } + virtual bool GetIdentity_w(rtc::SSLIdentity** identity) { + if (!identity_) + return false; + + *identity = identity_->GetReference(); + return true; + } + + private: + FakeTransportChannel* GetFakeChannel(int component) { + ChannelMap::iterator it = channels_.find(component); + return (it != channels_.end()) ? it->second : NULL; + } + void SetChannelDestination(int component, + FakeTransportChannel* channel) { + FakeTransportChannel* dest_channel = NULL; + if (dest_) { + dest_channel = dest_->GetFakeChannel(component); + if (dest_channel) { + dest_channel->SetLocalIdentity(dest_->identity_); + } + } + channel->SetDestination(dest_channel); + } + + // Note, this is distinct from the Channel map owned by Transport. + // This map just tracks the FakeTransportChannels created by this class. + ChannelMap channels_; + FakeTransport* dest_; + bool async_; + rtc::SSLIdentity* identity_; +}; + +// Fake session class, which can be passed into a BaseChannel object for +// test purposes. Can be connected to other FakeSessions via Connect(). +class FakeSession : public BaseSession { + public: + explicit FakeSession() + : BaseSession(rtc::Thread::Current(), + rtc::Thread::Current(), + NULL, "", "", true), + fail_create_channel_(false) { + } + explicit FakeSession(bool initiator) + : BaseSession(rtc::Thread::Current(), + rtc::Thread::Current(), + NULL, "", "", initiator), + fail_create_channel_(false) { + } + FakeSession(rtc::Thread* worker_thread, bool initiator) + : BaseSession(rtc::Thread::Current(), + worker_thread, + NULL, "", "", initiator), + fail_create_channel_(false) { + } + + FakeTransport* GetTransport(const std::string& content_name) { + return static_cast( + BaseSession::GetTransport(content_name)); + } + + void Connect(FakeSession* dest) { + // Simulate the exchange of candidates. + CompleteNegotiation(); + dest->CompleteNegotiation(); + for (TransportMap::const_iterator it = transport_proxies().begin(); + it != transport_proxies().end(); ++it) { + static_cast(it->second->impl())->SetDestination( + dest->GetTransport(it->first)); + } + } + + virtual TransportChannel* CreateChannel( + const std::string& content_name, + const std::string& channel_name, + int component) { + if (fail_create_channel_) { + return NULL; + } + return BaseSession::CreateChannel(content_name, channel_name, component); + } + + void set_fail_channel_creation(bool fail_channel_creation) { + fail_create_channel_ = fail_channel_creation; + } + + // TODO: Hoist this into Session when we re-work the Session code. + void set_ssl_identity(rtc::SSLIdentity* identity) { + for (TransportMap::const_iterator it = transport_proxies().begin(); + it != transport_proxies().end(); ++it) { + // We know that we have a FakeTransport* + + static_cast(it->second->impl())->set_identity + (identity); + } + } + + protected: + virtual Transport* CreateTransport(const std::string& content_name) { + return new FakeTransport(signaling_thread(), worker_thread(), content_name); + } + + void CompleteNegotiation() { + for (TransportMap::const_iterator it = transport_proxies().begin(); + it != transport_proxies().end(); ++it) { + it->second->CompleteNegotiation(); + it->second->ConnectChannels(); + } + } + + private: + bool fail_create_channel_; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_FAKESESSION_H_ diff --git a/webrtc/p2p/base/p2ptransport.cc b/webrtc/p2p/base/p2ptransport.cc new file mode 100644 index 000000000..e873756ff --- /dev/null +++ b/webrtc/p2p/base/p2ptransport.cc @@ -0,0 +1,246 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/p2ptransport.h" + +#include +#include + +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/p2ptransportchannel.h" +#include "webrtc/p2p/base/parsing.h" +#include "webrtc/p2p/base/sessionmanager.h" +#include "webrtc/p2p/base/sessionmessages.h" +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/base/base64.h" +#include "webrtc/base/common.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/stringutils.h" + +namespace { + +// Limits for GICE and ICE username sizes. +const size_t kMaxGiceUsernameSize = 16; +const size_t kMaxIceUsernameSize = 512; + +} // namespace + +namespace cricket { + +static buzz::XmlElement* NewTransportElement(const std::string& name) { + return new buzz::XmlElement(buzz::QName(name, LN_TRANSPORT), true); +} + +P2PTransport::P2PTransport(rtc::Thread* signaling_thread, + rtc::Thread* worker_thread, + const std::string& content_name, + PortAllocator* allocator) + : Transport(signaling_thread, worker_thread, + content_name, NS_GINGLE_P2P, allocator) { +} + +P2PTransport::~P2PTransport() { + DestroyAllChannels(); +} + +TransportChannelImpl* P2PTransport::CreateTransportChannel(int component) { + return new P2PTransportChannel(content_name(), component, this, + port_allocator()); +} + +void P2PTransport::DestroyTransportChannel(TransportChannelImpl* channel) { + delete channel; +} + +bool P2PTransportParser::ParseTransportDescription( + const buzz::XmlElement* elem, + const CandidateTranslator* translator, + TransportDescription* desc, + ParseError* error) { + ASSERT(elem->Name().LocalPart() == LN_TRANSPORT); + desc->transport_type = elem->Name().Namespace(); + if (desc->transport_type != NS_GINGLE_P2P) + return BadParse("Unsupported transport type", error); + + for (const buzz::XmlElement* candidate_elem = elem->FirstElement(); + candidate_elem != NULL; + candidate_elem = candidate_elem->NextElement()) { + // Only look at local part because the namespace might (eventually) + // be NS_GINGLE_P2P or NS_JINGLE_ICE_UDP. + if (candidate_elem->Name().LocalPart() == LN_CANDIDATE) { + Candidate candidate; + if (!ParseCandidate(ICEPROTO_GOOGLE, candidate_elem, translator, + &candidate, error)) { + return false; + } + + desc->candidates.push_back(candidate); + } + } + return true; +} + +bool P2PTransportParser::WriteTransportDescription( + const TransportDescription& desc, + const CandidateTranslator* translator, + buzz::XmlElement** out_elem, + WriteError* error) { + TransportProtocol proto = TransportProtocolFromDescription(&desc); + rtc::scoped_ptr trans_elem( + NewTransportElement(desc.transport_type)); + + // Fail if we get HYBRID or ICE right now. + // TODO(juberti): Add ICE and HYBRID serialization. + if (proto != ICEPROTO_GOOGLE) { + LOG(LS_ERROR) << "Failed to serialize non-GICE TransportDescription"; + return false; + } + + for (std::vector::const_iterator iter = desc.candidates.begin(); + iter != desc.candidates.end(); ++iter) { + rtc::scoped_ptr cand_elem( + new buzz::XmlElement(QN_GINGLE_P2P_CANDIDATE)); + if (!WriteCandidate(proto, *iter, translator, cand_elem.get(), error)) { + return false; + } + trans_elem->AddElement(cand_elem.release()); + } + + *out_elem = trans_elem.release(); + return true; +} + +bool P2PTransportParser::ParseGingleCandidate( + const buzz::XmlElement* elem, + const CandidateTranslator* translator, + Candidate* candidate, + ParseError* error) { + return ParseCandidate(ICEPROTO_GOOGLE, elem, translator, candidate, error); +} + +bool P2PTransportParser::WriteGingleCandidate( + const Candidate& candidate, + const CandidateTranslator* translator, + buzz::XmlElement** out_elem, + WriteError* error) { + rtc::scoped_ptr elem( + new buzz::XmlElement(QN_GINGLE_CANDIDATE)); + bool ret = WriteCandidate(ICEPROTO_GOOGLE, candidate, translator, elem.get(), + error); + if (ret) { + *out_elem = elem.release(); + } + return ret; +} + +bool P2PTransportParser::VerifyUsernameFormat(TransportProtocol proto, + const std::string& username, + ParseError* error) { + if (proto == ICEPROTO_GOOGLE || proto == ICEPROTO_HYBRID) { + if (username.size() > kMaxGiceUsernameSize) + return BadParse("candidate username is too long", error); + if (!rtc::Base64::IsBase64Encoded(username)) + return BadParse("candidate username has non-base64 encoded characters", + error); + } else if (proto == ICEPROTO_RFC5245) { + if (username.size() > kMaxIceUsernameSize) + return BadParse("candidate username is too long", error); + } + return true; +} + +bool P2PTransportParser::ParseCandidate(TransportProtocol proto, + const buzz::XmlElement* elem, + const CandidateTranslator* translator, + Candidate* candidate, + ParseError* error) { + ASSERT(proto == ICEPROTO_GOOGLE); + ASSERT(translator != NULL); + + if (!elem->HasAttr(buzz::QN_NAME) || + !elem->HasAttr(QN_ADDRESS) || + !elem->HasAttr(QN_PORT) || + !elem->HasAttr(QN_USERNAME) || + !elem->HasAttr(QN_PROTOCOL) || + !elem->HasAttr(QN_GENERATION)) { + return BadParse("candidate missing required attribute", error); + } + + rtc::SocketAddress address; + if (!ParseAddress(elem, QN_ADDRESS, QN_PORT, &address, error)) + return false; + + std::string channel_name = elem->Attr(buzz::QN_NAME); + int component = 0; + if (!translator || + !translator->GetComponentFromChannelName(channel_name, &component)) { + return BadParse("candidate has unknown channel name " + channel_name, + error); + } + + float preference = 0.0; + if (!GetXmlAttr(elem, QN_PREFERENCE, 0.0f, &preference)) { + return BadParse("candidate has unknown preference", error); + } + + candidate->set_component(component); + candidate->set_address(address); + candidate->set_username(elem->Attr(QN_USERNAME)); + candidate->set_preference(preference); + candidate->set_protocol(elem->Attr(QN_PROTOCOL)); + candidate->set_generation_str(elem->Attr(QN_GENERATION)); + if (elem->HasAttr(QN_PASSWORD)) + candidate->set_password(elem->Attr(QN_PASSWORD)); + if (elem->HasAttr(buzz::QN_TYPE)) + candidate->set_type(elem->Attr(buzz::QN_TYPE)); + if (elem->HasAttr(QN_NETWORK)) + candidate->set_network_name(elem->Attr(QN_NETWORK)); + + if (!VerifyUsernameFormat(proto, candidate->username(), error)) + return false; + + return true; +} + +bool P2PTransportParser::WriteCandidate(TransportProtocol proto, + const Candidate& candidate, + const CandidateTranslator* translator, + buzz::XmlElement* elem, + WriteError* error) { + ASSERT(proto == ICEPROTO_GOOGLE); + ASSERT(translator != NULL); + + std::string channel_name; + if (!translator || + !translator->GetChannelNameFromComponent( + candidate.component(), &channel_name)) { + return BadWrite("Cannot write candidate because of unknown component.", + error); + } + + elem->SetAttr(buzz::QN_NAME, channel_name); + elem->SetAttr(QN_ADDRESS, candidate.address().ipaddr().ToString()); + elem->SetAttr(QN_PORT, candidate.address().PortAsString()); + AddXmlAttr(elem, QN_PREFERENCE, candidate.preference()); + elem->SetAttr(QN_USERNAME, candidate.username()); + elem->SetAttr(QN_PROTOCOL, candidate.protocol()); + elem->SetAttr(QN_GENERATION, candidate.generation_str()); + if (!candidate.password().empty()) + elem->SetAttr(QN_PASSWORD, candidate.password()); + elem->SetAttr(buzz::QN_TYPE, candidate.type()); + if (!candidate.network_name().empty()) + elem->SetAttr(QN_NETWORK, candidate.network_name()); + + return true; +} + +} // namespace cricket diff --git a/webrtc/p2p/base/p2ptransport.h b/webrtc/p2p/base/p2ptransport.h new file mode 100644 index 000000000..efc659911 --- /dev/null +++ b/webrtc/p2p/base/p2ptransport.h @@ -0,0 +1,86 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_P2PTRANSPORT_H_ +#define WEBRTC_P2P_BASE_P2PTRANSPORT_H_ + +#include +#include +#include "webrtc/p2p/base/transport.h" + +namespace cricket { + +class P2PTransport : public Transport { + public: + P2PTransport(rtc::Thread* signaling_thread, + rtc::Thread* worker_thread, + const std::string& content_name, + PortAllocator* allocator); + virtual ~P2PTransport(); + + protected: + // Creates and destroys P2PTransportChannel. + virtual TransportChannelImpl* CreateTransportChannel(int component); + virtual void DestroyTransportChannel(TransportChannelImpl* channel); + + friend class P2PTransportChannel; + + DISALLOW_EVIL_CONSTRUCTORS(P2PTransport); +}; + +class P2PTransportParser : public TransportParser { + public: + P2PTransportParser() {} + // Translator may be null, in which case ParseCandidates should + // return false if there are candidates to parse. We can't not call + // ParseCandidates because there's no way to know ahead of time if + // there are candidates or not. + + // Jingle-specific functions; can be used with either ICE, GICE, or HYBRID. + virtual bool ParseTransportDescription(const buzz::XmlElement* elem, + const CandidateTranslator* translator, + TransportDescription* desc, + ParseError* error); + virtual bool WriteTransportDescription(const TransportDescription& desc, + const CandidateTranslator* translator, + buzz::XmlElement** elem, + WriteError* error); + + // Legacy Gingle functions; only can be used with GICE. + virtual bool ParseGingleCandidate(const buzz::XmlElement* elem, + const CandidateTranslator* translator, + Candidate* candidate, + ParseError* error); + virtual bool WriteGingleCandidate(const Candidate& candidate, + const CandidateTranslator* translator, + buzz::XmlElement** elem, + WriteError* error); + + private: + bool ParseCandidate(TransportProtocol proto, + const buzz::XmlElement* elem, + const CandidateTranslator* translator, + Candidate* candidate, + ParseError* error); + bool WriteCandidate(TransportProtocol proto, + const Candidate& candidate, + const CandidateTranslator* translator, + buzz::XmlElement* elem, + WriteError* error); + bool VerifyUsernameFormat(TransportProtocol proto, + const std::string& username, + ParseError* error); + + DISALLOW_EVIL_CONSTRUCTORS(P2PTransportParser); +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_P2PTRANSPORT_H_ diff --git a/webrtc/p2p/base/p2ptransportchannel.cc b/webrtc/p2p/base/p2ptransportchannel.cc new file mode 100644 index 000000000..84a2420bf --- /dev/null +++ b/webrtc/p2p/base/p2ptransportchannel.cc @@ -0,0 +1,1274 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/p2ptransportchannel.h" + +#include +#include "webrtc/p2p/base/common.h" +#include "webrtc/p2p/base/relayport.h" // For RELAY_PORT_TYPE. +#include "webrtc/p2p/base/stunport.h" // For STUN_PORT_TYPE. +#include "webrtc/base/common.h" +#include "webrtc/base/crc32.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/stringencode.h" + +namespace { + +// messages for queuing up work for ourselves +enum { + MSG_SORT = 1, + MSG_PING, +}; + +// 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 RTT that justifies a switch. +static const double kMinImprovement = 10; + +cricket::PortInterface::CandidateOrigin GetOrigin(cricket::PortInterface* port, + cricket::PortInterface* origin_port) { + if (!origin_port) + return cricket::PortInterface::ORIGIN_MESSAGE; + else if (port == origin_port) + return cricket::PortInterface::ORIGIN_THIS_PORT; + else + return cricket::PortInterface::ORIGIN_OTHER_PORT; +} + +// Compares two connections based only on static information about them. +int CompareConnectionCandidates(cricket::Connection* a, + cricket::Connection* b) { + // Compare connection priority. Lower values get sorted last. + if (a->priority() > b->priority()) + return 1; + if (a->priority() < b->priority()) + return -1; + + // If we're still tied at this point, prefer a younger generation. + return (a->remote_candidate().generation() + a->port()->generation()) - + (b->remote_candidate().generation() + b->port()->generation()); +} + +// 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); + + ASSERT(a->port()->IceProtocol() == b->port()->IceProtocol()); + + // 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 || !b_conn) // 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 { + +P2PTransportChannel::P2PTransportChannel(const std::string& content_name, + int component, + P2PTransport* transport, + PortAllocator *allocator) : + TransportChannelImpl(content_name, component), + transport_(transport), + allocator_(allocator), + worker_thread_(rtc::Thread::Current()), + incoming_only_(false), + waiting_for_signaling_(false), + error_(0), + best_connection_(NULL), + pending_best_connection_(NULL), + sort_dirty_(false), + was_writable_(false), + protocol_type_(ICEPROTO_HYBRID), + remote_ice_mode_(ICEMODE_FULL), + ice_role_(ICEROLE_UNKNOWN), + tiebreaker_(0), + remote_candidate_generation_(0) { +} + +P2PTransportChannel::~P2PTransportChannel() { + ASSERT(worker_thread_ == rtc::Thread::Current()); + + 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 P2PTransportChannel::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, &P2PTransportChannel::OnPortReady); + session->SignalCandidatesReady.connect( + this, &P2PTransportChannel::OnCandidatesReady); + session->SignalCandidatesAllocationDone.connect( + this, &P2PTransportChannel::OnCandidatesAllocationDone); + session->StartGettingPorts(); +} + +void P2PTransportChannel::AddConnection(Connection* connection) { + connections_.push_back(connection); + connection->set_remote_ice_mode(remote_ice_mode_); + connection->SignalReadPacket.connect( + this, &P2PTransportChannel::OnReadPacket); + connection->SignalReadyToSend.connect( + this, &P2PTransportChannel::OnReadyToSend); + connection->SignalStateChange.connect( + this, &P2PTransportChannel::OnConnectionStateChange); + connection->SignalDestroyed.connect( + this, &P2PTransportChannel::OnConnectionDestroyed); + connection->SignalUseCandidate.connect( + this, &P2PTransportChannel::OnUseCandidate); +} + +void P2PTransportChannel::SetIceRole(IceRole ice_role) { + ASSERT(worker_thread_ == rtc::Thread::Current()); + if (ice_role_ != ice_role) { + ice_role_ = ice_role; + for (std::vector::iterator it = ports_.begin(); + it != ports_.end(); ++it) { + (*it)->SetIceRole(ice_role); + } + } +} + +void P2PTransportChannel::SetIceTiebreaker(uint64 tiebreaker) { + ASSERT(worker_thread_ == rtc::Thread::Current()); + if (!ports_.empty()) { + LOG(LS_ERROR) + << "Attempt to change tiebreaker after Port has been allocated."; + return; + } + + tiebreaker_ = tiebreaker; +} + +bool P2PTransportChannel::GetIceProtocolType(IceProtocolType* type) const { + *type = protocol_type_; + return true; +} + +void P2PTransportChannel::SetIceProtocolType(IceProtocolType type) { + ASSERT(worker_thread_ == rtc::Thread::Current()); + + protocol_type_ = type; + for (std::vector::iterator it = ports_.begin(); + it != ports_.end(); ++it) { + (*it)->SetIceProtocolType(protocol_type_); + } +} + +void P2PTransportChannel::SetIceCredentials(const std::string& ice_ufrag, + const std::string& ice_pwd) { + ASSERT(worker_thread_ == rtc::Thread::Current()); + bool ice_restart = false; + if (!ice_ufrag_.empty() && !ice_pwd_.empty()) { + // Restart candidate allocation if there is any change in either + // ice ufrag or password. + ice_restart = + IceCredentialsChanged(ice_ufrag_, ice_pwd_, ice_ufrag, ice_pwd); + } + + ice_ufrag_ = ice_ufrag; + ice_pwd_ = ice_pwd; + + if (ice_restart) { + // Restart candidate gathering. + Allocate(); + } +} + +void P2PTransportChannel::SetRemoteIceCredentials(const std::string& ice_ufrag, + const std::string& ice_pwd) { + ASSERT(worker_thread_ == rtc::Thread::Current()); + bool ice_restart = false; + if (!remote_ice_ufrag_.empty() && !remote_ice_pwd_.empty()) { + ice_restart = (remote_ice_ufrag_ != ice_ufrag) || + (remote_ice_pwd_!= ice_pwd); + } + + remote_ice_ufrag_ = ice_ufrag; + remote_ice_pwd_ = ice_pwd; + + if (ice_restart) { + // |candidate.generation()| is not signaled in ICEPROTO_RFC5245. + // Therefore we need to keep track of the remote ice restart so + // newer connections are prioritized over the older. + ++remote_candidate_generation_; + } +} + +void P2PTransportChannel::SetRemoteIceMode(IceMode mode) { + remote_ice_mode_ = mode; +} + +// Go into the state of processing candidates, and running in general +void P2PTransportChannel::Connect() { + ASSERT(worker_thread_ == rtc::Thread::Current()); + if (ice_ufrag_.empty() || ice_pwd_.empty()) { + ASSERT(false); + LOG(LS_ERROR) << "P2PTransportChannel::Connect: The ice_ufrag_ and the " + << "ice_pwd_ are not set."; + return; + } + + // Kick off an allocator session + Allocate(); + + // Start pinging as the ports come in. + thread()->Post(this, MSG_PING); +} + +// Reset the socket, clear up any previous allocations and start over +void P2PTransportChannel::Reset() { + ASSERT(worker_thread_ == rtc::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 initial state. + set_readable(false); + set_writable(false); + + // Reinitialize the rest of our state. + waiting_for_signaling_ = false; + sort_dirty_ = false; + + // If we allocated before, start a new one now. + if (transport_->connect_requested()) + Allocate(); + + // 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 P2PTransportChannel::OnPortReady(PortAllocatorSession *session, + PortInterface* port) { + ASSERT(worker_thread_ == rtc::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_J(LS_WARNING, port) << "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. + + port->SetIceProtocolType(protocol_type_); + port->SetIceRole(ice_role_); + port->SetIceTiebreaker(tiebreaker_); + ports_.push_back(port); + port->SignalUnknownAddress.connect( + this, &P2PTransportChannel::OnUnknownAddress); + port->SignalDestroyed.connect(this, &P2PTransportChannel::OnPortDestroyed); + port->SignalRoleConflict.connect( + this, &P2PTransportChannel::OnRoleConflict); + + // 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 P2PTransportChannel::OnCandidatesReady( + PortAllocatorSession *session, const std::vector& candidates) { + ASSERT(worker_thread_ == rtc::Thread::Current()); + for (size_t i = 0; i < candidates.size(); ++i) { + SignalCandidateReady(this, candidates[i]); + } +} + +void P2PTransportChannel::OnCandidatesAllocationDone( + PortAllocatorSession* session) { + ASSERT(worker_thread_ == rtc::Thread::Current()); + SignalCandidatesAllocationDone(this); +} + +// Handle stun packets +void P2PTransportChannel::OnUnknownAddress( + PortInterface* port, + const rtc::SocketAddress& address, ProtocolType proto, + IceMessage* stun_msg, const std::string &remote_username, + bool port_muxed) { + ASSERT(worker_thread_ == rtc::Thread::Current()); + + // Port has received a valid stun packet from an address that no Connection + // is currently available for. See if we already have a candidate with the + // address. If it isn't we need to create new candidate for it. + + // Determine if the remote candidates use shared ufrag. + bool ufrag_per_port = false; + std::vector::iterator it; + if (remote_candidates_.size() > 0) { + it = remote_candidates_.begin(); + std::string username = it->username(); + for (; it != remote_candidates_.end(); ++it) { + if (it->username() != username) { + ufrag_per_port = true; + break; + } + } + } + + const Candidate* candidate = NULL; + bool known_username = false; + std::string remote_password; + for (it = remote_candidates_.begin(); it != remote_candidates_.end(); ++it) { + if (it->username() == remote_username) { + remote_password = it->password(); + known_username = true; + if (ufrag_per_port || + (it->address() == address && + it->protocol() == ProtoToString(proto))) { + candidate = &(*it); + break; + } + // We don't want to break here because we may find a match of the address + // later. + } + } + + if (!known_username) { + if (port_muxed) { + // When Ports are muxed, SignalUnknownAddress is delivered to all + // P2PTransportChannel belong to a session. Return from here will + // save us from sending stun binding error message from incorrect channel. + return; + } + // 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); + return; + } + + Candidate new_remote_candidate; + if (candidate != NULL) { + new_remote_candidate = *candidate; + if (ufrag_per_port) { + new_remote_candidate.set_address(address); + } + } else { + // Create a new candidate with this address. + std::string type; + if (port->IceProtocol() == ICEPROTO_RFC5245) { + type = PRFLX_PORT_TYPE; + } else { + // G-ICE doesn't support prflx candidate. + // We set candidate type to STUN_PORT_TYPE if the binding request comes + // from a relay port or the shared socket is used. Otherwise we use the + // port's type as the candidate type. + if (port->Type() == RELAY_PORT_TYPE || port->SharedSocket()) { + type = STUN_PORT_TYPE; + } else { + type = port->Type(); + } + } + + std::string id = rtc::CreateRandomString(8); + new_remote_candidate = Candidate( + id, component(), ProtoToString(proto), address, + 0, remote_username, remote_password, type, + port->Network()->name(), 0U, + rtc::ToString(rtc::ComputeCrc32(id))); + new_remote_candidate.set_priority( + new_remote_candidate.GetPriority(ICE_TYPE_PREFERENCE_SRFLX, + port->Network()->preference(), 0)); + } + + if (port->IceProtocol() == ICEPROTO_RFC5245) { + // RFC 5245 + // If the source transport address of the request does not match any + // existing remote candidates, it represents a new peer reflexive remote + // candidate. + + // The priority of the candidate is set to the PRIORITY attribute + // from the request. + const StunUInt32Attribute* priority_attr = + stun_msg->GetUInt32(STUN_ATTR_PRIORITY); + if (!priority_attr) { + LOG(LS_WARNING) << "P2PTransportChannel::OnUnknownAddress - " + << "No STUN_ATTR_PRIORITY found in the " + << "stun request message"; + port->SendBindingErrorResponse(stun_msg, address, + STUN_ERROR_BAD_REQUEST, + STUN_ERROR_REASON_BAD_REQUEST); + return; + } + new_remote_candidate.set_priority(priority_attr->value()); + + // RFC5245, the agent constructs a pair whose local candidate is equal to + // the transport address on which the STUN request was received, and a + // remote candidate equal to the source transport address where the + // request came from. + + // There shouldn't be an existing connection with this remote address. + // When ports are muxed, this channel might get multiple unknown address + // signals. In that case if the connection is already exists, we should + // simply ignore the signal othewise send server error. + if (port->GetConnection(new_remote_candidate.address())) { + if (port_muxed) { + LOG(LS_INFO) << "Connection already exists for peer reflexive " + << "candidate: " << new_remote_candidate.ToString(); + return; + } else { + ASSERT(false); + port->SendBindingErrorResponse(stun_msg, address, + STUN_ERROR_SERVER_ERROR, + STUN_ERROR_REASON_SERVER_ERROR); + return; + } + } + + Connection* connection = port->CreateConnection( + new_remote_candidate, cricket::PortInterface::ORIGIN_THIS_PORT); + if (!connection) { + ASSERT(false); + port->SendBindingErrorResponse(stun_msg, address, + STUN_ERROR_SERVER_ERROR, + STUN_ERROR_REASON_SERVER_ERROR); + return; + } + + AddConnection(connection); + connection->ReceivedPing(); + + // 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 { + // Check for connectivity to this address. Create connections + // to this address across all local ports. First, add this as a new remote + // address + if (!CreateConnections(new_remote_candidate, port, true)) { + // 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); + return; + } + + // 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(); + } +} + +void P2PTransportChannel::OnRoleConflict(PortInterface* port) { + SignalRoleConflict(this); // STUN ping will be sent when SetRole is called + // from Transport. +} + +// When the signalling channel is ready, we can really kick off the allocator +void P2PTransportChannel::OnSignalingReady() { + ASSERT(worker_thread_ == rtc::Thread::Current()); + if (waiting_for_signaling_) { + waiting_for_signaling_ = false; + AddAllocatorSession(allocator_->CreateSession( + SessionId(), content_name(), component(), ice_ufrag_, ice_pwd_)); + } +} + +void P2PTransportChannel::OnUseCandidate(Connection* conn) { + ASSERT(worker_thread_ == rtc::Thread::Current()); + ASSERT(ice_role_ == ICEROLE_CONTROLLED); + ASSERT(protocol_type_ == ICEPROTO_RFC5245); + if (conn->write_state() == Connection::STATE_WRITABLE) { + if (best_connection_ != conn) { + pending_best_connection_ = NULL; + SwitchBestConnectionTo(conn); + // Now we have selected the best connection, time to prune other existing + // connections and update the read/write state of the channel. + RequestSort(); + } + } else { + pending_best_connection_ = conn; + } +} + +void P2PTransportChannel::OnCandidate(const Candidate& candidate) { + ASSERT(worker_thread_ == rtc::Thread::Current()); + + // Create connections to this remote candidate. + CreateConnections(candidate, NULL, false); + + // Resort the connections list, which may have new elements. + SortConnections(); +} + +// Creates connections from all of the ports that we care about to the given +// remote candidate. The return value is true if we created a connection from +// the origin port. +bool P2PTransportChannel::CreateConnections(const Candidate& remote_candidate, + PortInterface* origin_port, + bool readable) { + ASSERT(worker_thread_ == rtc::Thread::Current()); + + Candidate new_remote_candidate(remote_candidate); + new_remote_candidate.set_generation( + GetRemoteCandidateGeneration(remote_candidate)); + // ICE candidates don't need to have username and password set, but + // the code below this (specifically, ConnectionRequest::Prepare in + // port.cc) uses the remote candidates's username. So, we set it + // here. + if (remote_candidate.username().empty()) { + new_remote_candidate.set_username(remote_ice_ufrag_); + } + if (remote_candidate.password().empty()) { + new_remote_candidate.set_password(remote_ice_pwd_); + } + + // If we've already seen the new remote candidate (in the current candidate + // generation), then we shouldn't try creating connections for it. + // We either already have a connection for it, or we previously created one + // and then later pruned it. If we don't return, the channel will again + // re-create any connections that were previously pruned, which will then + // immediately be re-pruned, churning the network for no purpose. + // This only applies to candidates received over signaling (i.e. origin_port + // is NULL). + if (!origin_port && IsDuplicateRemoteCandidate(new_remote_candidate)) { + // return true to indicate success, without creating any new connections. + return true; + } + + // 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, new_remote_candidate, origin_port, readable)) { + if (*it == origin_port) + created = true; + } + } + + if ((origin_port != NULL) && + std::find(ports_.begin(), ports_.end(), origin_port) == ports_.end()) { + if (CreateConnection( + origin_port, new_remote_candidate, origin_port, readable)) + created = true; + } + + // Remember this remote candidate so that we can add it to future ports. + RememberRemoteCandidate(new_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 P2PTransportChannel::CreateConnection(PortInterface* port, + const Candidate& remote_candidate, + PortInterface* 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." + << " Existing remote candidate: " + << connection->remote_candidate().ToString() + << "New remote candidate: " + << remote_candidate.ToString(); + return false; + } + } else { + PortInterface::CandidateOrigin origin = GetOrigin(port, origin_port); + + // Don't create connection if this is a candidate we received in a + // message and we are not allowed to make outgoing connections. + if (origin == cricket::PortInterface::ORIGIN_MESSAGE && incoming_only_) + return false; + + connection = port->CreateConnection(remote_candidate, origin); + if (!connection) + return false; + + AddConnection(connection); + + LOG_J(LS_INFO, this) << "Created connection with origin=" << origin << ", (" + << connections_.size() << " total)"; + } + + // 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; +} + +bool P2PTransportChannel::FindConnection( + cricket::Connection* connection) const { + std::vector::const_iterator citer = + std::find(connections_.begin(), connections_.end(), connection); + return citer != connections_.end(); +} + +uint32 P2PTransportChannel::GetRemoteCandidateGeneration( + const Candidate& candidate) { + if (protocol_type_ == ICEPROTO_GOOGLE) { + // The Candidate.generation() can be trusted. Nothing needs to be done. + return candidate.generation(); + } + // |candidate.generation()| is not signaled in ICEPROTO_RFC5245. + // Therefore we need to keep track of the remote ice restart so + // newer connections are prioritized over the older. + ASSERT(candidate.generation() == 0 || + candidate.generation() == remote_candidate_generation_); + return remote_candidate_generation_; +} + +// Check if remote candidate is already cached. +bool P2PTransportChannel::IsDuplicateRemoteCandidate( + const Candidate& candidate) { + for (uint32 i = 0; i < remote_candidates_.size(); ++i) { + if (remote_candidates_[i].IsEquivalent(candidate)) { + return true; + } + } + return false; +} + +// Maintain our remote candidate list, adding this new remote one. +void P2PTransportChannel::RememberRemoteCandidate( + const Candidate& remote_candidate, PortInterface* 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()) { + LOG(INFO) << "Pruning candidate from old generation: " + << remote_candidates_[i].address().ToSensitiveString(); + remote_candidates_.erase(remote_candidates_.begin() + i); + } else { + i += 1; + } + } + + // Make sure this candidate is not a duplicate. + if (IsDuplicateRemoteCandidate(remote_candidate)) { + LOG(INFO) << "Duplicate candidate: " << remote_candidate.ToString(); + return; + } + + // Try this candidate for all future ports. + remote_candidates_.push_back(RemoteCandidate(remote_candidate, origin_port)); +} + +// Set options on ourselves is simply setting options on all of our available +// port objects. +int P2PTransportChannel::SetOption(rtc::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; +} + +// Send data to the other side, using our best connection. +int P2PTransportChannel::SendPacket(const char *data, size_t len, + const rtc::PacketOptions& options, + int flags) { + ASSERT(worker_thread_ == rtc::Thread::Current()); + if (flags != 0) { + error_ = EINVAL; + return -1; + } + if (best_connection_ == NULL) { + error_ = EWOULDBLOCK; + return -1; + } + + int sent = best_connection_->Send(data, len, options); + if (sent <= 0) { + ASSERT(sent < 0); + error_ = best_connection_->GetError(); + } + return sent; +} + +bool P2PTransportChannel::GetStats(ConnectionInfos *infos) { + ASSERT(worker_thread_ == rtc::Thread::Current()); + // Gather connection infos. + infos->clear(); + + std::vector::const_iterator it; + for (it = connections_.begin(); it != connections_.end(); ++it) { + Connection *connection = *it; + ConnectionInfo info; + info.best_connection = (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 = !connection->reported(); + connection->set_reported(true); + 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.key = connection; + infos->push_back(info); + } + + return true; +} + +rtc::DiffServCodePoint P2PTransportChannel::DefaultDscpValue() const { + OptionMap::const_iterator it = options_.find(rtc::Socket::OPT_DSCP); + if (it == options_.end()) { + return rtc::DSCP_NO_CHANGE; + } + return static_cast (it->second); +} + +// Begin allocate (or immediately re-allocate, if MSG_ALLOCATE pending) +void P2PTransportChannel::Allocate() { + // Time for a new allocator, lets make sure we have a signalling channel + // to communicate candidates through first. + waiting_for_signaling_ = true; + SignalRequestSignaling(this); +} + +// Monitor connection states. +void P2PTransportChannel::UpdateConnectionStates() { + uint32 now = rtc::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 P2PTransportChannel::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. +void P2PTransportChannel::SortConnections() { + ASSERT(worker_thread_ == rtc::Thread::Current()); + + // Make sure the connection states are up-to-date since this affects how they + // will be sorted. + UpdateConnectionStates(); + + if (protocol_type_ == ICEPROTO_HYBRID) { + // If we are in hybrid mode, we are not sending any ping requests, so there + // is no point in sorting the connections. In hybrid state, ports can have + // different protocol than hybrid and protocol may differ from one another. + // Instead just update the state of this channel + UpdateChannelState(); + return; + } + + // 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); + LOG(LS_VERBOSE) << "Sorting available connections:"; + for (uint32 i = 0; i < connections_.size(); ++i) { + LOG(LS_VERBOSE) << connections_[i]->ToString(); + } + + Connection* top_connection = NULL; + if (connections_.size() > 0) + top_connection = connections_[0]; + + // We don't want to pick the best connections if channel is using RFC5245 + // and it's mode is CONTROLLED, as connections will be selected by the + // CONTROLLING agent. + + // If necessary, switch to the new choice. + if (protocol_type_ != ICEPROTO_RFC5245 || ice_role_ == ICEROLE_CONTROLLING) { + 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 priority. We leave those with + // better priority 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(); + } + } + } + + // Check if all connections are timedout. + bool all_connections_timedout = true; + for (uint32 i = 0; i < connections_.size(); ++i) { + if (connections_[i]->write_state() != Connection::STATE_WRITE_TIMEOUT) { + all_connections_timedout = false; + break; + } + } + + // Now update the writable state of the channel with the information we have + // so far. + if (best_connection_ && best_connection_->writable()) { + HandleWritable(); + } else if (all_connections_timedout) { + HandleAllTimedOut(); + } else { + HandleNotWritable(); + } + + // Update the state of this channel. This method is called whenever the + // state of any connection changes, so this is a good place to do this. + UpdateChannelState(); +} + + +// Track the best connection, and let listeners know +void P2PTransportChannel::SwitchBestConnectionTo(Connection* conn) { + // Note: if conn is NULL, the previous best_connection_ has been destroyed, + // so don't use it. + Connection* old_best_connection = best_connection_; + best_connection_ = conn; + if (best_connection_) { + if (old_best_connection) { + LOG_J(LS_INFO, this) << "Previous best connection: " + << old_best_connection->ToString(); + } + LOG_J(LS_INFO, this) << "New best connection: " + << best_connection_->ToString(); + SignalRouteChange(this, best_connection_->remote_candidate()); + } else { + LOG_J(LS_INFO, this) << "No best connection"; + } +} + +void P2PTransportChannel::UpdateChannelState() { + // The Handle* functions already set the writable state. We'll just double- + // check it here. + bool writable = ((best_connection_ != NULL) && + (best_connection_->write_state() == + Connection::STATE_WRITABLE)); + ASSERT(writable == this->writable()); + if (writable != this->writable()) + LOG(LS_ERROR) << "UpdateChannelState: writable state mismatch"; + + bool readable = false; + for (uint32 i = 0; i < connections_.size(); ++i) { + if (connections_[i]->read_state() == Connection::STATE_READABLE) { + readable = true; + break; + } + } + set_readable(readable); +} + +// We checked the status of our connections and we had at least one that +// was writable, go into the writable state. +void P2PTransportChannel::HandleWritable() { + ASSERT(worker_thread_ == rtc::Thread::Current()); + if (!writable()) { + for (uint32 i = 0; i < allocator_sessions_.size(); ++i) { + if (allocator_sessions_[i]->IsGettingPorts()) { + allocator_sessions_[i]->StopGettingPorts(); + } + } + } + + was_writable_ = true; + set_writable(true); +} + +// Notify upper layer about channel not writable state, if it was before. +void P2PTransportChannel::HandleNotWritable() { + ASSERT(worker_thread_ == rtc::Thread::Current()); + if (was_writable_) { + was_writable_ = false; + set_writable(false); + } +} + +void P2PTransportChannel::HandleAllTimedOut() { + // Currently we are treating this as channel not writable. + HandleNotWritable(); +} + +// If we have a best connection, return it, otherwise return top one in the +// list (later we will mark it best). +Connection* P2PTransportChannel::GetBestConnectionOnNetwork( + rtc::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 P2PTransportChannel::OnMessage(rtc::Message *pmsg) { + switch (pmsg->message_id) { + case MSG_SORT: + OnSort(); + break; + case MSG_PING: + OnPing(); + break; + default: + ASSERT(false); + break; + } +} + +// Handle queued up sort request +void P2PTransportChannel::OnSort() { + // Resort the connections based on the new statistics. + SortConnections(); +} + +// Handle queued up ping request +void P2PTransportChannel::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) + PingConnection(conn); + + // Post ourselves a message to perform the next ping. + uint32 delay = 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 P2PTransportChannel::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 (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* P2PTransportChannel::FindNextPingableConnection() { + uint32 now = rtc::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; +} + +// Apart from sending ping from |conn| this method also updates +// |use_candidate_attr| flag. The criteria to update this flag is +// explained below. +// Set USE-CANDIDATE if doing ICE AND this channel is in CONTROLLING AND +// a) Channel is in FULL ICE AND +// a.1) |conn| is the best connection OR +// a.2) there is no best connection OR +// a.3) the best connection is unwritable OR +// a.4) |conn| has higher priority than best_connection. +// b) we're doing LITE ICE AND +// b.1) |conn| is the best_connection AND +// b.2) |conn| is writable. +void P2PTransportChannel::PingConnection(Connection* conn) { + bool use_candidate = false; + if (protocol_type_ == ICEPROTO_RFC5245) { + if (remote_ice_mode_ == ICEMODE_FULL && ice_role_ == ICEROLE_CONTROLLING) { + use_candidate = (conn == best_connection_) || + (best_connection_ == NULL) || + (!best_connection_->writable()) || + (conn->priority() > best_connection_->priority()); + } else if (remote_ice_mode_ == ICEMODE_LITE && conn == best_connection_) { + use_candidate = best_connection_->writable(); + } + } + conn->set_use_candidate_attr(use_candidate); + conn->Ping(rtc::Time()); +} + +// 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 P2PTransportChannel::OnConnectionStateChange(Connection* connection) { + ASSERT(worker_thread_ == rtc::Thread::Current()); + + // Update the best connection if the state change is from pending best + // connection and role is controlled. + if (protocol_type_ == ICEPROTO_RFC5245 && ice_role_ == ICEROLE_CONTROLLED) { + if (connection == pending_best_connection_ && connection->writable()) { + pending_best_connection_ = NULL; + SwitchBestConnectionTo(connection); + } + } + + // 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 P2PTransportChannel::OnConnectionDestroyed(Connection* connection) { + ASSERT(worker_thread_ == rtc::Thread::Current()); + + // Note: the previous best_connection_ may be destroyed by now, so don't + // use it. + + // Remove this connection from the list. + std::vector::iterator iter = + std::find(connections_.begin(), connections_.end(), connection); + ASSERT(iter != connections_.end()); + connections_.erase(iter); + + LOG_J(LS_INFO, this) << "Removed connection (" + << static_cast(connections_.size()) << " remaining)"; + + if (pending_best_connection_ == connection) { + pending_best_connection_ = NULL; + } + + // 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(); + } + + SignalConnectionRemoved(this); +} + +// When a port is destroyed remove it from our list of ports to use for +// connection attempts. +void P2PTransportChannel::OnPortDestroyed(PortInterface* port) { + ASSERT(worker_thread_ == rtc::Thread::Current()); + + // Remove this port from the list (if we didn't drop it already). + std::vector::iterator iter = + std::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 P2PTransportChannel::OnReadPacket( + Connection *connection, const char *data, size_t len, + const rtc::PacketTime& packet_time) { + ASSERT(worker_thread_ == rtc::Thread::Current()); + + // Do not deliver, if packet doesn't belong to the correct transport channel. + if (!FindConnection(connection)) + return; + + // Let the client know of an incoming packet + SignalReadPacket(this, data, len, packet_time, 0); +} + +void P2PTransportChannel::OnReadyToSend(Connection* connection) { + if (connection == best_connection_ && writable()) { + SignalReadyToSend(this); + } +} + +} // namespace cricket diff --git a/webrtc/p2p/base/p2ptransportchannel.h b/webrtc/p2p/base/p2ptransportchannel.h new file mode 100644 index 000000000..8e3d50de3 --- /dev/null +++ b/webrtc/p2p/base/p2ptransportchannel.h @@ -0,0 +1,242 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// P2PTransportChannel 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 WEBRTC_P2P_BASE_P2PTRANSPORTCHANNEL_H_ +#define WEBRTC_P2P_BASE_P2PTRANSPORTCHANNEL_H_ + +#include +#include +#include +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/p2ptransport.h" +#include "webrtc/p2p/base/portallocator.h" +#include "webrtc/p2p/base/portinterface.h" +#include "webrtc/p2p/base/transport.h" +#include "webrtc/p2p/base/transportchannelimpl.h" +#include "webrtc/base/asyncpacketsocket.h" +#include "webrtc/base/sigslot.h" + +namespace cricket { + +// Adds the port on which the candidate originated. +class RemoteCandidate : public Candidate { + public: + RemoteCandidate(const Candidate& c, PortInterface* origin_port) + : Candidate(c), origin_port_(origin_port) {} + + PortInterface* origin_port() { return origin_port_; } + + private: + PortInterface* origin_port_; +}; + +// P2PTransportChannel manages the candidates and connection process to keep +// two P2P clients connected to each other. +class P2PTransportChannel : public TransportChannelImpl, + public rtc::MessageHandler { + public: + P2PTransportChannel(const std::string& content_name, + int component, + P2PTransport* transport, + PortAllocator *allocator); + virtual ~P2PTransportChannel(); + + // From TransportChannelImpl: + virtual Transport* GetTransport() { return transport_; } + virtual void SetIceRole(IceRole role); + virtual IceRole GetIceRole() const { return ice_role_; } + virtual void SetIceTiebreaker(uint64 tiebreaker); + virtual size_t GetConnectionCount() const { return connections_.size(); } + virtual bool GetIceProtocolType(IceProtocolType* type) const; + virtual void SetIceProtocolType(IceProtocolType type); + virtual void SetIceCredentials(const std::string& ice_ufrag, + const std::string& ice_pwd); + virtual void SetRemoteIceCredentials(const std::string& ice_ufrag, + const std::string& ice_pwd); + virtual void SetRemoteIceMode(IceMode mode); + virtual void Connect(); + virtual void Reset(); + virtual void OnSignalingReady(); + virtual void OnCandidate(const Candidate& candidate); + + // From TransportChannel: + virtual int SendPacket(const char *data, size_t len, + const rtc::PacketOptions& options, int flags); + virtual int SetOption(rtc::Socket::Option opt, int value); + virtual int GetError() { return error_; } + virtual bool GetStats(std::vector* stats); + + const Connection* best_connection() const { return best_connection_; } + void set_incoming_only(bool value) { incoming_only_ = value; } + + // Note: This is only for testing purpose. + // |ports_| should not be changed from outside. + const std::vector& ports() { return ports_; } + + IceMode remote_ice_mode() const { return remote_ice_mode_; } + + // DTLS methods. + virtual bool IsDtlsActive() const { return false; } + + // Default implementation. + virtual bool GetSslRole(rtc::SSLRole* role) const { + return false; + } + + virtual bool SetSslRole(rtc::SSLRole role) { + return false; + } + + // Set up the ciphers to use for DTLS-SRTP. + virtual bool SetSrtpCiphers(const std::vector& ciphers) { + return false; + } + + // Find out which DTLS-SRTP cipher was negotiated + virtual bool GetSrtpCipher(std::string* cipher) { + return false; + } + + // Returns false because the channel is not encrypted by default. + virtual bool GetLocalIdentity(rtc::SSLIdentity** identity) const { + return false; + } + + virtual bool GetRemoteCertificate(rtc::SSLCertificate** cert) const { + return false; + } + + // Allows key material to be extracted for external encryption. + virtual bool ExportKeyingMaterial( + const std::string& label, + const uint8* context, + size_t context_len, + bool use_context, + uint8* result, + size_t result_len) { + return false; + } + + virtual bool SetLocalIdentity(rtc::SSLIdentity* identity) { + return false; + } + + // Set DTLS Remote fingerprint. Must be after local identity set. + virtual bool SetRemoteFingerprint( + const std::string& digest_alg, + const uint8* digest, + size_t digest_len) { + return false; + } + + // Helper method used only in unittest. + rtc::DiffServCodePoint DefaultDscpValue() const; + + private: + rtc::Thread* thread() { return worker_thread_; } + PortAllocatorSession* allocator_session() { + return allocator_sessions_.back(); + } + + void Allocate(); + void UpdateConnectionStates(); + void RequestSort(); + void SortConnections(); + void SwitchBestConnectionTo(Connection* conn); + void UpdateChannelState(); + void HandleWritable(); + void HandleNotWritable(); + void HandleAllTimedOut(); + + Connection* GetBestConnectionOnNetwork(rtc::Network* network); + bool CreateConnections(const Candidate &remote_candidate, + PortInterface* origin_port, bool readable); + bool CreateConnection(PortInterface* port, const Candidate& remote_candidate, + PortInterface* origin_port, bool readable); + bool FindConnection(cricket::Connection* connection) const; + + uint32 GetRemoteCandidateGeneration(const Candidate& candidate); + bool IsDuplicateRemoteCandidate(const Candidate& candidate); + void RememberRemoteCandidate(const Candidate& remote_candidate, + PortInterface* origin_port); + bool IsPingable(Connection* conn); + Connection* FindNextPingableConnection(); + void PingConnection(Connection* conn); + void AddAllocatorSession(PortAllocatorSession* session); + void AddConnection(Connection* connection); + + void OnPortReady(PortAllocatorSession *session, PortInterface* port); + void OnCandidatesReady(PortAllocatorSession *session, + const std::vector& candidates); + void OnCandidatesAllocationDone(PortAllocatorSession* session); + void OnUnknownAddress(PortInterface* port, + const rtc::SocketAddress& addr, + ProtocolType proto, + IceMessage* stun_msg, + const std::string& remote_username, + bool port_muxed); + void OnPortDestroyed(PortInterface* port); + void OnRoleConflict(PortInterface* port); + + void OnConnectionStateChange(Connection* connection); + void OnReadPacket(Connection *connection, const char *data, size_t len, + const rtc::PacketTime& packet_time); + void OnReadyToSend(Connection* connection); + void OnConnectionDestroyed(Connection *connection); + + void OnUseCandidate(Connection* conn); + + virtual void OnMessage(rtc::Message *pmsg); + void OnSort(); + void OnPing(); + + P2PTransport* transport_; + PortAllocator *allocator_; + rtc::Thread *worker_thread_; + bool incoming_only_; + bool waiting_for_signaling_; + int error_; + std::vector allocator_sessions_; + std::vector ports_; + std::vector connections_; + Connection* best_connection_; + // Connection selected by the controlling agent. This should be used only + // at controlled side when protocol type is RFC5245. + Connection* pending_best_connection_; + std::vector remote_candidates_; + bool sort_dirty_; // indicates whether another sort is needed right now + bool was_writable_; + typedef std::map OptionMap; + OptionMap options_; + std::string ice_ufrag_; + std::string ice_pwd_; + std::string remote_ice_ufrag_; + std::string remote_ice_pwd_; + IceProtocolType protocol_type_; + IceMode remote_ice_mode_; + IceRole ice_role_; + uint64 tiebreaker_; + uint32 remote_candidate_generation_; + + DISALLOW_EVIL_CONSTRUCTORS(P2PTransportChannel); +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_P2PTRANSPORTCHANNEL_H_ diff --git a/webrtc/p2p/base/p2ptransportchannel_unittest.cc b/webrtc/p2p/base/p2ptransportchannel_unittest.cc new file mode 100644 index 000000000..4f32719eb --- /dev/null +++ b/webrtc/p2p/base/p2ptransportchannel_unittest.cc @@ -0,0 +1,1702 @@ +/* + * Copyright 2009 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/p2ptransportchannel.h" +#include "webrtc/p2p/base/testrelayserver.h" +#include "webrtc/p2p/base/teststunserver.h" +#include "webrtc/p2p/base/testturnserver.h" +#include "webrtc/p2p/client/basicportallocator.h" +#include "webrtc/base/dscp.h" +#include "webrtc/base/fakenetwork.h" +#include "webrtc/base/firewallsocketserver.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/natserver.h" +#include "webrtc/base/natsocketfactory.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/proxyserver.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/ssladapter.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/virtualsocketserver.h" + +using cricket::kDefaultPortAllocatorFlags; +using cricket::kMinimumStepDelay; +using cricket::kDefaultStepDelay; +using cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG; +using cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET; +using cricket::ServerAddresses; +using rtc::SocketAddress; + +static const int kDefaultTimeout = 1000; +static const int kOnlyLocalPorts = cricket::PORTALLOCATOR_DISABLE_STUN | + cricket::PORTALLOCATOR_DISABLE_RELAY | + cricket::PORTALLOCATOR_DISABLE_TCP; +// Addresses on the public internet. +static const SocketAddress kPublicAddrs[2] = + { SocketAddress("11.11.11.11", 0), SocketAddress("22.22.22.22", 0) }; +// IPv6 Addresses on the public internet. +static const SocketAddress kIPv6PublicAddrs[2] = { + SocketAddress("2400:4030:1:2c00:be30:abcd:efab:cdef", 0), + SocketAddress("2620:0:1000:1b03:2e41:38ff:fea6:f2a4", 0) +}; +// For configuring multihomed clients. +static const SocketAddress kAlternateAddrs[2] = + { SocketAddress("11.11.11.101", 0), SocketAddress("22.22.22.202", 0) }; +// Addresses for HTTP proxy servers. +static const SocketAddress kHttpsProxyAddrs[2] = + { SocketAddress("11.11.11.1", 443), SocketAddress("22.22.22.1", 443) }; +// Addresses for SOCKS proxy servers. +static const SocketAddress kSocksProxyAddrs[2] = + { SocketAddress("11.11.11.1", 1080), SocketAddress("22.22.22.1", 1080) }; +// Internal addresses for NAT boxes. +static const SocketAddress kNatAddrs[2] = + { SocketAddress("192.168.1.1", 0), SocketAddress("192.168.2.1", 0) }; +// Private addresses inside the NAT private networks. +static const SocketAddress kPrivateAddrs[2] = + { SocketAddress("192.168.1.11", 0), SocketAddress("192.168.2.22", 0) }; +// For cascaded NATs, the internal addresses of the inner NAT boxes. +static const SocketAddress kCascadedNatAddrs[2] = + { SocketAddress("192.168.10.1", 0), SocketAddress("192.168.20.1", 0) }; +// For cascaded NATs, private addresses inside the inner private networks. +static const SocketAddress kCascadedPrivateAddrs[2] = + { SocketAddress("192.168.10.11", 0), SocketAddress("192.168.20.22", 0) }; +// The address of the public STUN server. +static const SocketAddress kStunAddr("99.99.99.1", cricket::STUN_SERVER_PORT); +// The addresses for the public relay server. +static const SocketAddress kRelayUdpIntAddr("99.99.99.2", 5000); +static const SocketAddress kRelayUdpExtAddr("99.99.99.3", 5001); +static const SocketAddress kRelayTcpIntAddr("99.99.99.2", 5002); +static const SocketAddress kRelayTcpExtAddr("99.99.99.3", 5003); +static const SocketAddress kRelaySslTcpIntAddr("99.99.99.2", 5004); +static const SocketAddress kRelaySslTcpExtAddr("99.99.99.3", 5005); +// The addresses for the public turn server. +static const SocketAddress kTurnUdpIntAddr("99.99.99.4", + cricket::STUN_SERVER_PORT); +static const SocketAddress kTurnUdpExtAddr("99.99.99.5", 0); +static const cricket::RelayCredentials kRelayCredentials("test", "test"); + +// Based on ICE_UFRAG_LENGTH +static const char* kIceUfrag[4] = {"TESTICEUFRAG0000", "TESTICEUFRAG0001", + "TESTICEUFRAG0002", "TESTICEUFRAG0003"}; +// Based on ICE_PWD_LENGTH +static const char* kIcePwd[4] = {"TESTICEPWD00000000000000", + "TESTICEPWD00000000000001", + "TESTICEPWD00000000000002", + "TESTICEPWD00000000000003"}; + +static const uint64 kTiebreaker1 = 11111; +static const uint64 kTiebreaker2 = 22222; + +// This test simulates 2 P2P endpoints that want to establish connectivity +// with each other over various network topologies and conditions, which can be +// specified in each individial test. +// A virtual network (via VirtualSocketServer) along with virtual firewalls and +// NATs (via Firewall/NATSocketServer) are used to simulate the various network +// conditions. We can configure the IP addresses of the endpoints, +// block various types of connectivity, or add arbitrary levels of NAT. +// We also run a STUN server and a relay server on the virtual network to allow +// our typical P2P mechanisms to do their thing. +// For each case, we expect the P2P stack to eventually settle on a specific +// form of connectivity to the other side. The test checks that the P2P +// negotiation successfully establishes connectivity within a certain time, +// and that the result is what we expect. +// Note that this class is a base class for use by other tests, who will provide +// specialized test behavior. +class P2PTransportChannelTestBase : public testing::Test, + public rtc::MessageHandler, + public sigslot::has_slots<> { + public: + P2PTransportChannelTestBase() + : main_(rtc::Thread::Current()), + pss_(new rtc::PhysicalSocketServer), + vss_(new rtc::VirtualSocketServer(pss_.get())), + nss_(new rtc::NATSocketServer(vss_.get())), + ss_(new rtc::FirewallSocketServer(nss_.get())), + ss_scope_(ss_.get()), + stun_server_(cricket::TestStunServer::Create(main_, kStunAddr)), + turn_server_(main_, kTurnUdpIntAddr, kTurnUdpExtAddr), + relay_server_(main_, kRelayUdpIntAddr, kRelayUdpExtAddr, + kRelayTcpIntAddr, kRelayTcpExtAddr, + kRelaySslTcpIntAddr, kRelaySslTcpExtAddr), + socks_server1_(ss_.get(), kSocksProxyAddrs[0], + ss_.get(), kSocksProxyAddrs[0]), + socks_server2_(ss_.get(), kSocksProxyAddrs[1], + ss_.get(), kSocksProxyAddrs[1]), + clear_remote_candidates_ufrag_pwd_(false), + force_relay_(false) { + ep1_.role_ = cricket::ICEROLE_CONTROLLING; + ep2_.role_ = cricket::ICEROLE_CONTROLLED; + + ServerAddresses stun_servers; + stun_servers.insert(kStunAddr); + ep1_.allocator_.reset(new cricket::BasicPortAllocator( + &ep1_.network_manager_, + stun_servers, kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr)); + ep2_.allocator_.reset(new cricket::BasicPortAllocator( + &ep2_.network_manager_, + stun_servers, kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr)); + } + + protected: + enum Config { + OPEN, // Open to the Internet + NAT_FULL_CONE, // NAT, no filtering + NAT_ADDR_RESTRICTED, // NAT, must send to an addr to recv + NAT_PORT_RESTRICTED, // NAT, must send to an addr+port to recv + NAT_SYMMETRIC, // NAT, endpoint-dependent bindings + NAT_DOUBLE_CONE, // Double NAT, both cone + NAT_SYMMETRIC_THEN_CONE, // Double NAT, symmetric outer, cone inner + BLOCK_UDP, // Firewall, UDP in/out blocked + BLOCK_UDP_AND_INCOMING_TCP, // Firewall, UDP in/out and TCP in blocked + BLOCK_ALL_BUT_OUTGOING_HTTP, // Firewall, only TCP out on 80/443 + PROXY_HTTPS, // All traffic through HTTPS proxy + PROXY_SOCKS, // All traffic through SOCKS proxy + NUM_CONFIGS + }; + + struct Result { + Result(const std::string& lt, const std::string& lp, + const std::string& rt, const std::string& rp, + const std::string& lt2, const std::string& lp2, + const std::string& rt2, const std::string& rp2, int wait) + : local_type(lt), local_proto(lp), remote_type(rt), remote_proto(rp), + local_type2(lt2), local_proto2(lp2), remote_type2(rt2), + remote_proto2(rp2), connect_wait(wait) { + } + std::string local_type; + std::string local_proto; + std::string remote_type; + std::string remote_proto; + std::string local_type2; + std::string local_proto2; + std::string remote_type2; + std::string remote_proto2; + int connect_wait; + }; + + struct ChannelData { + bool CheckData(const char* data, int len) { + bool ret = false; + if (!ch_packets_.empty()) { + std::string packet = ch_packets_.front(); + ret = (packet == std::string(data, len)); + ch_packets_.pop_front(); + } + return ret; + } + + std::string name_; // TODO - Currently not used. + std::list ch_packets_; + rtc::scoped_ptr ch_; + }; + + struct Endpoint { + Endpoint() : signaling_delay_(0), role_(cricket::ICEROLE_UNKNOWN), + tiebreaker_(0), role_conflict_(false), + protocol_type_(cricket::ICEPROTO_GOOGLE) {} + bool HasChannel(cricket::TransportChannel* ch) { + return (ch == cd1_.ch_.get() || ch == cd2_.ch_.get()); + } + ChannelData* GetChannelData(cricket::TransportChannel* ch) { + if (!HasChannel(ch)) return NULL; + if (cd1_.ch_.get() == ch) + return &cd1_; + else + return &cd2_; + } + void SetSignalingDelay(int delay) { signaling_delay_ = delay; } + + void SetIceRole(cricket::IceRole role) { role_ = role; } + cricket::IceRole ice_role() { return role_; } + void SetIceProtocolType(cricket::IceProtocolType type) { + protocol_type_ = type; + } + cricket::IceProtocolType protocol_type() { return protocol_type_; } + void SetIceTiebreaker(uint64 tiebreaker) { tiebreaker_ = tiebreaker; } + uint64 GetIceTiebreaker() { return tiebreaker_; } + void OnRoleConflict(bool role_conflict) { role_conflict_ = role_conflict; } + bool role_conflict() { return role_conflict_; } + void SetAllocationStepDelay(uint32 delay) { + allocator_->set_step_delay(delay); + } + void SetAllowTcpListen(bool allow_tcp_listen) { + allocator_->set_allow_tcp_listen(allow_tcp_listen); + } + + rtc::FakeNetworkManager network_manager_; + rtc::scoped_ptr allocator_; + ChannelData cd1_; + ChannelData cd2_; + int signaling_delay_; + cricket::IceRole role_; + uint64 tiebreaker_; + bool role_conflict_; + cricket::IceProtocolType protocol_type_; + }; + + struct CandidateData : public rtc::MessageData { + CandidateData(cricket::TransportChannel* ch, const cricket::Candidate& c) + : channel(ch), candidate(c) { + } + cricket::TransportChannel* channel; + cricket::Candidate candidate; + }; + + ChannelData* GetChannelData(cricket::TransportChannel* channel) { + if (ep1_.HasChannel(channel)) + return ep1_.GetChannelData(channel); + else + return ep2_.GetChannelData(channel); + } + + void CreateChannels(int num) { + std::string ice_ufrag_ep1_cd1_ch = kIceUfrag[0]; + std::string ice_pwd_ep1_cd1_ch = kIcePwd[0]; + std::string ice_ufrag_ep2_cd1_ch = kIceUfrag[1]; + std::string ice_pwd_ep2_cd1_ch = kIcePwd[1]; + ep1_.cd1_.ch_.reset(CreateChannel( + 0, cricket::ICE_CANDIDATE_COMPONENT_DEFAULT, + ice_ufrag_ep1_cd1_ch, ice_pwd_ep1_cd1_ch, + ice_ufrag_ep2_cd1_ch, ice_pwd_ep2_cd1_ch)); + ep2_.cd1_.ch_.reset(CreateChannel( + 1, cricket::ICE_CANDIDATE_COMPONENT_DEFAULT, + ice_ufrag_ep2_cd1_ch, ice_pwd_ep2_cd1_ch, + ice_ufrag_ep1_cd1_ch, ice_pwd_ep1_cd1_ch)); + if (num == 2) { + std::string ice_ufrag_ep1_cd2_ch = kIceUfrag[2]; + std::string ice_pwd_ep1_cd2_ch = kIcePwd[2]; + std::string ice_ufrag_ep2_cd2_ch = kIceUfrag[3]; + std::string ice_pwd_ep2_cd2_ch = kIcePwd[3]; + // In BUNDLE each endpoint must share common ICE credentials. + if (ep1_.allocator_->flags() & cricket::PORTALLOCATOR_ENABLE_BUNDLE) { + ice_ufrag_ep1_cd2_ch = ice_ufrag_ep1_cd1_ch; + ice_pwd_ep1_cd2_ch = ice_pwd_ep1_cd1_ch; + } + if (ep2_.allocator_->flags() & cricket::PORTALLOCATOR_ENABLE_BUNDLE) { + ice_ufrag_ep2_cd2_ch = ice_ufrag_ep2_cd1_ch; + ice_pwd_ep2_cd2_ch = ice_pwd_ep2_cd1_ch; + } + ep1_.cd2_.ch_.reset(CreateChannel( + 0, cricket::ICE_CANDIDATE_COMPONENT_DEFAULT, + ice_ufrag_ep1_cd2_ch, ice_pwd_ep1_cd2_ch, + ice_ufrag_ep2_cd2_ch, ice_pwd_ep2_cd2_ch)); + ep2_.cd2_.ch_.reset(CreateChannel( + 1, cricket::ICE_CANDIDATE_COMPONENT_DEFAULT, + ice_ufrag_ep2_cd2_ch, ice_pwd_ep2_cd2_ch, + ice_ufrag_ep1_cd2_ch, ice_pwd_ep1_cd2_ch)); + } + } + cricket::P2PTransportChannel* CreateChannel( + int endpoint, + int component, + const std::string& local_ice_ufrag, + const std::string& local_ice_pwd, + const std::string& remote_ice_ufrag, + const std::string& remote_ice_pwd) { + cricket::P2PTransportChannel* channel = new cricket::P2PTransportChannel( + "test content name", component, NULL, GetAllocator(endpoint)); + channel->SignalRequestSignaling.connect( + this, &P2PTransportChannelTestBase::OnChannelRequestSignaling); + channel->SignalCandidateReady.connect(this, + &P2PTransportChannelTestBase::OnCandidate); + channel->SignalReadPacket.connect( + this, &P2PTransportChannelTestBase::OnReadPacket); + channel->SignalRoleConflict.connect( + this, &P2PTransportChannelTestBase::OnRoleConflict); + channel->SetIceProtocolType(GetEndpoint(endpoint)->protocol_type()); + channel->SetIceCredentials(local_ice_ufrag, local_ice_pwd); + if (clear_remote_candidates_ufrag_pwd_) { + // This only needs to be set if we're clearing them from the + // candidates. Some unit tests rely on this not being set. + channel->SetRemoteIceCredentials(remote_ice_ufrag, remote_ice_pwd); + } + channel->SetIceRole(GetEndpoint(endpoint)->ice_role()); + channel->SetIceTiebreaker(GetEndpoint(endpoint)->GetIceTiebreaker()); + channel->Connect(); + return channel; + } + void DestroyChannels() { + ep1_.cd1_.ch_.reset(); + ep2_.cd1_.ch_.reset(); + ep1_.cd2_.ch_.reset(); + ep2_.cd2_.ch_.reset(); + } + cricket::P2PTransportChannel* ep1_ch1() { return ep1_.cd1_.ch_.get(); } + cricket::P2PTransportChannel* ep1_ch2() { return ep1_.cd2_.ch_.get(); } + cricket::P2PTransportChannel* ep2_ch1() { return ep2_.cd1_.ch_.get(); } + cricket::P2PTransportChannel* ep2_ch2() { return ep2_.cd2_.ch_.get(); } + + // Common results. + static const Result kLocalUdpToLocalUdp; + static const Result kLocalUdpToStunUdp; + static const Result kLocalUdpToPrflxUdp; + static const Result kPrflxUdpToLocalUdp; + static const Result kStunUdpToLocalUdp; + static const Result kStunUdpToStunUdp; + static const Result kPrflxUdpToStunUdp; + static const Result kLocalUdpToRelayUdp; + static const Result kPrflxUdpToRelayUdp; + static const Result kLocalTcpToLocalTcp; + static const Result kLocalTcpToPrflxTcp; + static const Result kPrflxTcpToLocalTcp; + + rtc::NATSocketServer* nat() { return nss_.get(); } + rtc::FirewallSocketServer* fw() { return ss_.get(); } + + Endpoint* GetEndpoint(int endpoint) { + if (endpoint == 0) { + return &ep1_; + } else if (endpoint == 1) { + return &ep2_; + } else { + return NULL; + } + } + cricket::PortAllocator* GetAllocator(int endpoint) { + return GetEndpoint(endpoint)->allocator_.get(); + } + void AddAddress(int endpoint, const SocketAddress& addr) { + GetEndpoint(endpoint)->network_manager_.AddInterface(addr); + } + void RemoveAddress(int endpoint, const SocketAddress& addr) { + GetEndpoint(endpoint)->network_manager_.RemoveInterface(addr); + } + void SetProxy(int endpoint, rtc::ProxyType type) { + rtc::ProxyInfo info; + info.type = type; + info.address = (type == rtc::PROXY_HTTPS) ? + kHttpsProxyAddrs[endpoint] : kSocksProxyAddrs[endpoint]; + GetAllocator(endpoint)->set_proxy("unittest/1.0", info); + } + void SetAllocatorFlags(int endpoint, int flags) { + GetAllocator(endpoint)->set_flags(flags); + } + void SetSignalingDelay(int endpoint, int delay) { + GetEndpoint(endpoint)->SetSignalingDelay(delay); + } + void SetIceProtocol(int endpoint, cricket::IceProtocolType type) { + GetEndpoint(endpoint)->SetIceProtocolType(type); + } + void SetIceRole(int endpoint, cricket::IceRole role) { + GetEndpoint(endpoint)->SetIceRole(role); + } + void SetIceTiebreaker(int endpoint, uint64 tiebreaker) { + GetEndpoint(endpoint)->SetIceTiebreaker(tiebreaker); + } + bool GetRoleConflict(int endpoint) { + return GetEndpoint(endpoint)->role_conflict(); + } + void SetAllocationStepDelay(int endpoint, uint32 delay) { + return GetEndpoint(endpoint)->SetAllocationStepDelay(delay); + } + void SetAllowTcpListen(int endpoint, bool allow_tcp_listen) { + return GetEndpoint(endpoint)->SetAllowTcpListen(allow_tcp_listen); + } + + void Test(const Result& expected) { + int32 connect_start = rtc::Time(), connect_time; + + // Create the channels and wait for them to connect. + CreateChannels(1); + EXPECT_TRUE_WAIT_MARGIN(ep1_ch1() != NULL && + ep2_ch1() != NULL && + ep1_ch1()->readable() && + ep1_ch1()->writable() && + ep2_ch1()->readable() && + ep2_ch1()->writable(), + expected.connect_wait, + 1000); + connect_time = rtc::TimeSince(connect_start); + if (connect_time < expected.connect_wait) { + LOG(LS_INFO) << "Connect time: " << connect_time << " ms"; + } else { + LOG(LS_INFO) << "Connect time: " << "TIMEOUT (" + << expected.connect_wait << " ms)"; + } + + // Allow a few turns of the crank for the best connections to emerge. + // This may take up to 2 seconds. + if (ep1_ch1()->best_connection() && + ep2_ch1()->best_connection()) { + int32 converge_start = rtc::Time(), converge_time; + int converge_wait = 2000; + EXPECT_TRUE_WAIT_MARGIN( + LocalCandidate(ep1_ch1())->type() == expected.local_type && + LocalCandidate(ep1_ch1())->protocol() == expected.local_proto && + RemoteCandidate(ep1_ch1())->type() == expected.remote_type && + RemoteCandidate(ep1_ch1())->protocol() == expected.remote_proto, + converge_wait, + converge_wait); + + // Also do EXPECT_EQ on each part so that failures are more verbose. + EXPECT_EQ(expected.local_type, LocalCandidate(ep1_ch1())->type()); + EXPECT_EQ(expected.local_proto, LocalCandidate(ep1_ch1())->protocol()); + EXPECT_EQ(expected.remote_type, RemoteCandidate(ep1_ch1())->type()); + EXPECT_EQ(expected.remote_proto, RemoteCandidate(ep1_ch1())->protocol()); + + // Verifying remote channel best connection information. This is done + // only for the RFC 5245 as controlled agent will use USE-CANDIDATE + // from controlling (ep1) agent. We can easily predict from EP1 result + // matrix. + if (ep2_.protocol_type_ == cricket::ICEPROTO_RFC5245) { + // Checking for best connection candidates information at remote. + EXPECT_TRUE_WAIT( + LocalCandidate(ep2_ch1())->type() == expected.local_type2 && + LocalCandidate(ep2_ch1())->protocol() == expected.local_proto2 && + RemoteCandidate(ep2_ch1())->protocol() == expected.remote_proto2, + kDefaultTimeout); + + // For verbose + EXPECT_EQ(expected.local_type2, LocalCandidate(ep2_ch1())->type()); + EXPECT_EQ(expected.local_proto2, LocalCandidate(ep2_ch1())->protocol()); + EXPECT_EQ(expected.remote_proto2, + RemoteCandidate(ep2_ch1())->protocol()); + // Removed remote_type comparision aginst best connection remote + // candidate. This is done to handle remote type discrepancy from + // local to stun based on the test type. + // For example in case of Open -> NAT, ep2 channels will have LULU + // and in other cases like NAT -> NAT it will be LUSU. To avoid these + // mismatches and we are doing comparision in different way. + // i.e. when don't match its remote type is either local or stun. + // TODO(ronghuawu): Refine the test criteria. + // https://code.google.com/p/webrtc/issues/detail?id=1953 + if (expected.remote_type2 != RemoteCandidate(ep2_ch1())->type()) { + EXPECT_TRUE(expected.remote_type2 == cricket::LOCAL_PORT_TYPE || + expected.remote_type2 == cricket::STUN_PORT_TYPE); + EXPECT_TRUE( + RemoteCandidate(ep2_ch1())->type() == cricket::LOCAL_PORT_TYPE || + RemoteCandidate(ep2_ch1())->type() == cricket::STUN_PORT_TYPE || + RemoteCandidate(ep2_ch1())->type() == cricket::PRFLX_PORT_TYPE); + } + } + + converge_time = rtc::TimeSince(converge_start); + if (converge_time < converge_wait) { + LOG(LS_INFO) << "Converge time: " << converge_time << " ms"; + } else { + LOG(LS_INFO) << "Converge time: " << "TIMEOUT (" + << converge_wait << " ms)"; + } + } + // Try sending some data to other end. + TestSendRecv(1); + + // Destroy the channels, and wait for them to be fully cleaned up. + DestroyChannels(); + } + + void TestSendRecv(int channels) { + for (int i = 0; i < 10; ++i) { + const char* data = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; + int len = static_cast(strlen(data)); + // local_channel1 <==> remote_channel1 + EXPECT_EQ_WAIT(len, SendData(ep1_ch1(), data, len), 1000); + EXPECT_TRUE_WAIT(CheckDataOnChannel(ep2_ch1(), data, len), 1000); + EXPECT_EQ_WAIT(len, SendData(ep2_ch1(), data, len), 1000); + EXPECT_TRUE_WAIT(CheckDataOnChannel(ep1_ch1(), data, len), 1000); + if (channels == 2 && ep1_ch2() && ep2_ch2()) { + // local_channel2 <==> remote_channel2 + EXPECT_EQ_WAIT(len, SendData(ep1_ch2(), data, len), 1000); + EXPECT_TRUE_WAIT(CheckDataOnChannel(ep2_ch2(), data, len), 1000); + EXPECT_EQ_WAIT(len, SendData(ep2_ch2(), data, len), 1000); + EXPECT_TRUE_WAIT(CheckDataOnChannel(ep1_ch2(), data, len), 1000); + } + } + } + + // This test waits for the transport to become readable and writable on both + // end points. Once they are, the end points set new local ice credentials to + // restart the ice gathering. Finally it waits for the transport to select a + // new connection using the newly generated ice candidates. + // Before calling this function the end points must be configured. + void TestHandleIceUfragPasswordChanged() { + ep1_ch1()->SetRemoteIceCredentials(kIceUfrag[1], kIcePwd[1]); + ep2_ch1()->SetRemoteIceCredentials(kIceUfrag[0], kIcePwd[0]); + EXPECT_TRUE_WAIT_MARGIN(ep1_ch1()->readable() && ep1_ch1()->writable() && + ep2_ch1()->readable() && ep2_ch1()->writable(), + 1000, 1000); + + const cricket::Candidate* old_local_candidate1 = LocalCandidate(ep1_ch1()); + const cricket::Candidate* old_local_candidate2 = LocalCandidate(ep2_ch1()); + const cricket::Candidate* old_remote_candidate1 = + RemoteCandidate(ep1_ch1()); + const cricket::Candidate* old_remote_candidate2 = + RemoteCandidate(ep2_ch1()); + + ep1_ch1()->SetIceCredentials(kIceUfrag[2], kIcePwd[2]); + ep1_ch1()->SetRemoteIceCredentials(kIceUfrag[3], kIcePwd[3]); + ep2_ch1()->SetIceCredentials(kIceUfrag[3], kIcePwd[3]); + ep2_ch1()->SetRemoteIceCredentials(kIceUfrag[2], kIcePwd[2]); + + EXPECT_TRUE_WAIT_MARGIN(LocalCandidate(ep1_ch1())->generation() != + old_local_candidate1->generation(), + 1000, 1000); + EXPECT_TRUE_WAIT_MARGIN(LocalCandidate(ep2_ch1())->generation() != + old_local_candidate2->generation(), + 1000, 1000); + EXPECT_TRUE_WAIT_MARGIN(RemoteCandidate(ep1_ch1())->generation() != + old_remote_candidate1->generation(), + 1000, 1000); + EXPECT_TRUE_WAIT_MARGIN(RemoteCandidate(ep2_ch1())->generation() != + old_remote_candidate2->generation(), + 1000, 1000); + EXPECT_EQ(1u, RemoteCandidate(ep2_ch1())->generation()); + EXPECT_EQ(1u, RemoteCandidate(ep1_ch1())->generation()); + } + + void TestSignalRoleConflict() { + SetIceProtocol(0, cricket::ICEPROTO_RFC5245); + SetIceTiebreaker(0, kTiebreaker1); // Default EP1 is in controlling state. + + SetIceProtocol(1, cricket::ICEPROTO_RFC5245); + SetIceRole(1, cricket::ICEROLE_CONTROLLING); + SetIceTiebreaker(1, kTiebreaker2); + + // Creating channels with both channels role set to CONTROLLING. + CreateChannels(1); + // Since both the channels initiated with controlling state and channel2 + // has higher tiebreaker value, channel1 should receive SignalRoleConflict. + EXPECT_TRUE_WAIT(GetRoleConflict(0), 1000); + EXPECT_FALSE(GetRoleConflict(1)); + + EXPECT_TRUE_WAIT(ep1_ch1()->readable() && + ep1_ch1()->writable() && + ep2_ch1()->readable() && + ep2_ch1()->writable(), + 1000); + + EXPECT_TRUE(ep1_ch1()->best_connection() && + ep2_ch1()->best_connection()); + + TestSendRecv(1); + } + + void TestHybridConnectivity(cricket::IceProtocolType proto) { + AddAddress(0, kPublicAddrs[0]); + AddAddress(1, kPublicAddrs[1]); + + SetAllocationStepDelay(0, kMinimumStepDelay); + SetAllocationStepDelay(1, kMinimumStepDelay); + + SetIceRole(0, cricket::ICEROLE_CONTROLLING); + SetIceProtocol(0, cricket::ICEPROTO_HYBRID); + SetIceTiebreaker(0, kTiebreaker1); + SetIceRole(1, cricket::ICEROLE_CONTROLLED); + SetIceProtocol(1, proto); + SetIceTiebreaker(1, kTiebreaker2); + + CreateChannels(1); + // When channel is in hybrid and it's controlling agent, channel will + // receive ping request from the remote. Hence connection is readable. + // Since channel is in hybrid, it will not send any pings, so no writable + // connection. Since channel2 is in controlled state, it will not have + // any connections which are readable or writable, as it didn't received + // pings (or none) with USE-CANDIDATE attribute. + EXPECT_TRUE_WAIT(ep1_ch1()->readable(), 1000); + + // Set real protocol type. + ep1_ch1()->SetIceProtocolType(proto); + + // Channel should able to send ping requests and connections become writable + // in both directions. + EXPECT_TRUE_WAIT(ep1_ch1()->readable() && ep1_ch1()->writable() && + ep2_ch1()->readable() && ep2_ch1()->writable(), + 1000); + EXPECT_TRUE( + ep1_ch1()->best_connection() && ep2_ch1()->best_connection() && + LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) && + RemoteCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[1])); + + TestSendRecv(1); + DestroyChannels(); + } + + void OnChannelRequestSignaling(cricket::TransportChannelImpl* channel) { + channel->OnSignalingReady(); + } + // We pass the candidates directly to the other side. + void OnCandidate(cricket::TransportChannelImpl* ch, + const cricket::Candidate& c) { + if (force_relay_ && c.type() != cricket::RELAY_PORT_TYPE) + return; + + main_->PostDelayed(GetEndpoint(ch)->signaling_delay_, this, 0, + new CandidateData(ch, c)); + } + void OnMessage(rtc::Message* msg) { + rtc::scoped_ptr data( + static_cast(msg->pdata)); + cricket::P2PTransportChannel* rch = GetRemoteChannel(data->channel); + cricket::Candidate c = data->candidate; + if (clear_remote_candidates_ufrag_pwd_) { + c.set_username(""); + c.set_password(""); + } + LOG(LS_INFO) << "Candidate(" << data->channel->component() << "->" + << rch->component() << "): " << c.type() << ", " << c.protocol() + << ", " << c.address().ToString() << ", " << c.username() + << ", " << c.generation(); + rch->OnCandidate(c); + } + void OnReadPacket(cricket::TransportChannel* channel, const char* data, + size_t len, const rtc::PacketTime& packet_time, + int flags) { + std::list& packets = GetPacketList(channel); + packets.push_front(std::string(data, len)); + } + void OnRoleConflict(cricket::TransportChannelImpl* channel) { + GetEndpoint(channel)->OnRoleConflict(true); + cricket::IceRole new_role = + GetEndpoint(channel)->ice_role() == cricket::ICEROLE_CONTROLLING ? + cricket::ICEROLE_CONTROLLED : cricket::ICEROLE_CONTROLLING; + channel->SetIceRole(new_role); + } + int SendData(cricket::TransportChannel* channel, + const char* data, size_t len) { + rtc::PacketOptions options; + return channel->SendPacket(data, len, options, 0); + } + bool CheckDataOnChannel(cricket::TransportChannel* channel, + const char* data, int len) { + return GetChannelData(channel)->CheckData(data, len); + } + static const cricket::Candidate* LocalCandidate( + cricket::P2PTransportChannel* ch) { + return (ch && ch->best_connection()) ? + &ch->best_connection()->local_candidate() : NULL; + } + static const cricket::Candidate* RemoteCandidate( + cricket::P2PTransportChannel* ch) { + return (ch && ch->best_connection()) ? + &ch->best_connection()->remote_candidate() : NULL; + } + Endpoint* GetEndpoint(cricket::TransportChannel* ch) { + if (ep1_.HasChannel(ch)) { + return &ep1_; + } else if (ep2_.HasChannel(ch)) { + return &ep2_; + } else { + return NULL; + } + } + cricket::P2PTransportChannel* GetRemoteChannel( + cricket::TransportChannel* ch) { + if (ch == ep1_ch1()) + return ep2_ch1(); + else if (ch == ep1_ch2()) + return ep2_ch2(); + else if (ch == ep2_ch1()) + return ep1_ch1(); + else if (ch == ep2_ch2()) + return ep1_ch2(); + else + return NULL; + } + std::list& GetPacketList(cricket::TransportChannel* ch) { + return GetChannelData(ch)->ch_packets_; + } + + void set_clear_remote_candidates_ufrag_pwd(bool clear) { + clear_remote_candidates_ufrag_pwd_ = clear; + } + + void set_force_relay(bool relay) { + force_relay_ = relay; + } + + private: + rtc::Thread* main_; + rtc::scoped_ptr pss_; + rtc::scoped_ptr vss_; + rtc::scoped_ptr nss_; + rtc::scoped_ptr ss_; + rtc::SocketServerScope ss_scope_; + rtc::scoped_ptr stun_server_; + cricket::TestTurnServer turn_server_; + cricket::TestRelayServer relay_server_; + rtc::SocksProxyServer socks_server1_; + rtc::SocksProxyServer socks_server2_; + Endpoint ep1_; + Endpoint ep2_; + bool clear_remote_candidates_ufrag_pwd_; + bool force_relay_; +}; + +// The tests have only a few outcomes, which we predefine. +const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase:: + kLocalUdpToLocalUdp("local", "udp", "local", "udp", + "local", "udp", "local", "udp", 1000); +const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase:: + kLocalUdpToStunUdp("local", "udp", "stun", "udp", + "local", "udp", "stun", "udp", 1000); +const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase:: + kLocalUdpToPrflxUdp("local", "udp", "prflx", "udp", + "prflx", "udp", "local", "udp", 1000); +const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase:: + kPrflxUdpToLocalUdp("prflx", "udp", "local", "udp", + "local", "udp", "prflx", "udp", 1000); +const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase:: + kStunUdpToLocalUdp("stun", "udp", "local", "udp", + "local", "udp", "stun", "udp", 1000); +const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase:: + kStunUdpToStunUdp("stun", "udp", "stun", "udp", + "stun", "udp", "stun", "udp", 1000); +const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase:: + kPrflxUdpToStunUdp("prflx", "udp", "stun", "udp", + "local", "udp", "prflx", "udp", 1000); +const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase:: + kLocalUdpToRelayUdp("local", "udp", "relay", "udp", + "relay", "udp", "local", "udp", 2000); +const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase:: + kPrflxUdpToRelayUdp("prflx", "udp", "relay", "udp", + "relay", "udp", "prflx", "udp", 2000); +const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase:: + kLocalTcpToLocalTcp("local", "tcp", "local", "tcp", + "local", "tcp", "local", "tcp", 3000); +const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase:: + kLocalTcpToPrflxTcp("local", "tcp", "prflx", "tcp", + "prflx", "tcp", "local", "tcp", 3000); +const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase:: + kPrflxTcpToLocalTcp("prflx", "tcp", "local", "tcp", + "local", "tcp", "prflx", "tcp", 3000); + +// Test the matrix of all the connectivity types we expect to see in the wild. +// Just test every combination of the configs in the Config enum. +class P2PTransportChannelTest : public P2PTransportChannelTestBase { + protected: + static const Result* kMatrix[NUM_CONFIGS][NUM_CONFIGS]; + static const Result* kMatrixSharedUfrag[NUM_CONFIGS][NUM_CONFIGS]; + static const Result* kMatrixSharedSocketAsGice[NUM_CONFIGS][NUM_CONFIGS]; + static const Result* kMatrixSharedSocketAsIce[NUM_CONFIGS][NUM_CONFIGS]; + void ConfigureEndpoints(Config config1, Config config2, + int allocator_flags1, int allocator_flags2, + int delay1, int delay2, + cricket::IceProtocolType type) { + // Ideally we want to use TURN server for both GICE and ICE, but in case + // of GICE, TURN server usage is not producing results reliabally. + // TODO(mallinath): Remove Relay and use TURN server for all tests. + ServerAddresses stun_servers; + stun_servers.insert(kStunAddr); + GetEndpoint(0)->allocator_.reset( + new cricket::BasicPortAllocator(&(GetEndpoint(0)->network_manager_), + stun_servers, + rtc::SocketAddress(), rtc::SocketAddress(), + rtc::SocketAddress())); + GetEndpoint(1)->allocator_.reset( + new cricket::BasicPortAllocator(&(GetEndpoint(1)->network_manager_), + stun_servers, + rtc::SocketAddress(), rtc::SocketAddress(), + rtc::SocketAddress())); + + cricket::RelayServerConfig relay_server(cricket::RELAY_GTURN); + if (type == cricket::ICEPROTO_RFC5245) { + relay_server.type = cricket::RELAY_TURN; + relay_server.credentials = kRelayCredentials; + relay_server.ports.push_back(cricket::ProtocolAddress( + kTurnUdpIntAddr, cricket::PROTO_UDP, false)); + } else { + relay_server.ports.push_back(cricket::ProtocolAddress( + kRelayUdpIntAddr, cricket::PROTO_UDP, false)); + relay_server.ports.push_back(cricket::ProtocolAddress( + kRelayTcpIntAddr, cricket::PROTO_TCP, false)); + relay_server.ports.push_back(cricket::ProtocolAddress( + kRelaySslTcpIntAddr, cricket::PROTO_SSLTCP, false)); + } + GetEndpoint(0)->allocator_->AddRelay(relay_server); + GetEndpoint(1)->allocator_->AddRelay(relay_server); + + ConfigureEndpoint(0, config1); + SetIceProtocol(0, type); + SetAllocatorFlags(0, allocator_flags1); + SetAllocationStepDelay(0, delay1); + ConfigureEndpoint(1, config2); + SetIceProtocol(1, type); + SetAllocatorFlags(1, allocator_flags2); + SetAllocationStepDelay(1, delay2); + } + void ConfigureEndpoint(int endpoint, Config config) { + switch (config) { + case OPEN: + AddAddress(endpoint, kPublicAddrs[endpoint]); + break; + case NAT_FULL_CONE: + case NAT_ADDR_RESTRICTED: + case NAT_PORT_RESTRICTED: + case NAT_SYMMETRIC: + AddAddress(endpoint, kPrivateAddrs[endpoint]); + // Add a single NAT of the desired type + nat()->AddTranslator(kPublicAddrs[endpoint], kNatAddrs[endpoint], + static_cast(config - NAT_FULL_CONE))-> + AddClient(kPrivateAddrs[endpoint]); + break; + case NAT_DOUBLE_CONE: + case NAT_SYMMETRIC_THEN_CONE: + AddAddress(endpoint, kCascadedPrivateAddrs[endpoint]); + // Add a two cascaded NATs of the desired types + nat()->AddTranslator(kPublicAddrs[endpoint], kNatAddrs[endpoint], + (config == NAT_DOUBLE_CONE) ? + rtc::NAT_OPEN_CONE : rtc::NAT_SYMMETRIC)-> + AddTranslator(kPrivateAddrs[endpoint], kCascadedNatAddrs[endpoint], + rtc::NAT_OPEN_CONE)-> + AddClient(kCascadedPrivateAddrs[endpoint]); + break; + case BLOCK_UDP: + case BLOCK_UDP_AND_INCOMING_TCP: + case BLOCK_ALL_BUT_OUTGOING_HTTP: + case PROXY_HTTPS: + case PROXY_SOCKS: + AddAddress(endpoint, kPublicAddrs[endpoint]); + // Block all UDP + fw()->AddRule(false, rtc::FP_UDP, rtc::FD_ANY, + kPublicAddrs[endpoint]); + if (config == BLOCK_UDP_AND_INCOMING_TCP) { + // Block TCP inbound to the endpoint + fw()->AddRule(false, rtc::FP_TCP, SocketAddress(), + kPublicAddrs[endpoint]); + } else if (config == BLOCK_ALL_BUT_OUTGOING_HTTP) { + // Block all TCP to/from the endpoint except 80/443 out + fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint], + SocketAddress(rtc::IPAddress(INADDR_ANY), 80)); + fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint], + SocketAddress(rtc::IPAddress(INADDR_ANY), 443)); + fw()->AddRule(false, rtc::FP_TCP, rtc::FD_ANY, + kPublicAddrs[endpoint]); + } else if (config == PROXY_HTTPS) { + // Block all TCP to/from the endpoint except to the proxy server + fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint], + kHttpsProxyAddrs[endpoint]); + fw()->AddRule(false, rtc::FP_TCP, rtc::FD_ANY, + kPublicAddrs[endpoint]); + SetProxy(endpoint, rtc::PROXY_HTTPS); + } else if (config == PROXY_SOCKS) { + // Block all TCP to/from the endpoint except to the proxy server + fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint], + kSocksProxyAddrs[endpoint]); + fw()->AddRule(false, rtc::FP_TCP, rtc::FD_ANY, + kPublicAddrs[endpoint]); + SetProxy(endpoint, rtc::PROXY_SOCKS5); + } + break; + default: + break; + } + } +}; + +// Shorthands for use in the test matrix. +#define LULU &kLocalUdpToLocalUdp +#define LUSU &kLocalUdpToStunUdp +#define LUPU &kLocalUdpToPrflxUdp +#define PULU &kPrflxUdpToLocalUdp +#define SULU &kStunUdpToLocalUdp +#define SUSU &kStunUdpToStunUdp +#define PUSU &kPrflxUdpToStunUdp +#define LURU &kLocalUdpToRelayUdp +#define PURU &kPrflxUdpToRelayUdp +#define LTLT &kLocalTcpToLocalTcp +#define LTPT &kLocalTcpToPrflxTcp +#define PTLT &kPrflxTcpToLocalTcp +// TODO: Enable these once TestRelayServer can accept external TCP. +#define LTRT NULL +#define LSRS NULL + +// Test matrix. Originator behavior defined by rows, receiever by columns. + +// Currently the p2ptransportchannel.cc (specifically the +// P2PTransportChannel::OnUnknownAddress) operates in 2 modes depend on the +// remote candidates - ufrag per port or shared ufrag. +// For example, if the remote candidates have the shared ufrag, for the unknown +// address reaches the OnUnknownAddress, we will try to find the matched +// remote candidate based on the address and protocol, if not found, a new +// remote candidate will be created for this address. But if the remote +// candidates have different ufrags, we will try to find the matched remote +// candidate by comparing the ufrag. If not found, an error will be returned. +// Because currently the shared ufrag feature is under the experiment and will +// be rolled out gradually. We want to test the different combinations of peers +// with/without the shared ufrag enabled. And those different combinations have +// different expectation of the best connection. For example in the OpenToCONE +// case, an unknown address will be updated to a "host" remote candidate if the +// remote peer uses different ufrag per port. But in the shared ufrag case, +// a "stun" (should be peer-reflexive eventually) candidate will be created for +// that. So the expected best candidate will be LUSU instead of LULU. +// With all these, we have to keep 2 test matrixes for the tests: +// kMatrix - for the tests that the remote peer uses different ufrag per port. +// kMatrixSharedUfrag - for the tests that remote peer uses shared ufrag. +// The different between the two matrixes are on: +// OPToCONE, OPTo2CON, +// COToCONE, COToADDR, COToPORT, COToSYMM, COTo2CON, COToSCON, +// ADToCONE, ADToADDR, ADTo2CON, +// POToADDR, +// SYToADDR, +// 2CToCONE, 2CToADDR, 2CToPORT, 2CToSYMM, 2CTo2CON, 2CToSCON, +// SCToADDR, + +// TODO: Fix NULLs caused by lack of TCP support in NATSocket. +// TODO: Fix NULLs caused by no HTTP proxy support. +// TODO: Rearrange rows/columns from best to worst. +// TODO(ronghuawu): Keep only one test matrix once the shared ufrag is enabled. +const P2PTransportChannelTest::Result* + P2PTransportChannelTest::kMatrix[NUM_CONFIGS][NUM_CONFIGS] = { +// OPEN CONE ADDR PORT SYMM 2CON SCON !UDP !TCP HTTP PRXH PRXS +/*OP*/ {LULU, LULU, LULU, LULU, LULU, LULU, LULU, LTLT, LTLT, LSRS, NULL, LTLT}, +/*CO*/ {LULU, LULU, LULU, SULU, SULU, LULU, SULU, NULL, NULL, LSRS, NULL, LTRT}, +/*AD*/ {LULU, LULU, LULU, SUSU, SUSU, LULU, SUSU, NULL, NULL, LSRS, NULL, LTRT}, +/*PO*/ {LULU, LUSU, LUSU, SUSU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT}, +/*SY*/ {LULU, LUSU, LUSU, LURU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT}, +/*2C*/ {LULU, LULU, LULU, SULU, SULU, LULU, SULU, NULL, NULL, LSRS, NULL, LTRT}, +/*SC*/ {LULU, LUSU, LUSU, LURU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT}, +/*!U*/ {LTLT, NULL, NULL, NULL, NULL, NULL, NULL, LTLT, LTLT, LSRS, NULL, LTRT}, +/*!T*/ {LTRT, NULL, NULL, NULL, NULL, NULL, NULL, LTLT, LTRT, LSRS, NULL, LTRT}, +/*HT*/ {LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, NULL, LSRS}, +/*PR*/ {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, +/*PR*/ {LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LSRS, NULL, LTRT}, +}; +const P2PTransportChannelTest::Result* + P2PTransportChannelTest::kMatrixSharedUfrag[NUM_CONFIGS][NUM_CONFIGS] = { +// OPEN CONE ADDR PORT SYMM 2CON SCON !UDP !TCP HTTP PRXH PRXS +/*OP*/ {LULU, LUSU, LULU, LULU, LULU, LUSU, LULU, LTLT, LTLT, LSRS, NULL, LTLT}, +/*CO*/ {LULU, LUSU, LUSU, SUSU, SUSU, LUSU, SUSU, NULL, NULL, LSRS, NULL, LTRT}, +/*AD*/ {LULU, LUSU, LUSU, SUSU, SUSU, LUSU, SUSU, NULL, NULL, LSRS, NULL, LTRT}, +/*PO*/ {LULU, LUSU, LUSU, SUSU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT}, +/*SY*/ {LULU, LUSU, LUSU, LURU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT}, +/*2C*/ {LULU, LUSU, LUSU, SUSU, SUSU, LUSU, SUSU, NULL, NULL, LSRS, NULL, LTRT}, +/*SC*/ {LULU, LUSU, LUSU, LURU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT}, +/*!U*/ {LTLT, NULL, NULL, NULL, NULL, NULL, NULL, LTLT, LTLT, LSRS, NULL, LTRT}, +/*!T*/ {LTRT, NULL, NULL, NULL, NULL, NULL, NULL, LTLT, LTRT, LSRS, NULL, LTRT}, +/*HT*/ {LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, NULL, LSRS}, +/*PR*/ {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, +/*PR*/ {LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LSRS, NULL, LTRT}, +}; +const P2PTransportChannelTest::Result* + P2PTransportChannelTest::kMatrixSharedSocketAsGice + [NUM_CONFIGS][NUM_CONFIGS] = { +// OPEN CONE ADDR PORT SYMM 2CON SCON !UDP !TCP HTTP PRXH PRXS +/*OP*/ {LULU, LUSU, LUSU, LUSU, LUSU, LUSU, LUSU, LTLT, LTLT, LSRS, NULL, LTLT}, +/*CO*/ {LULU, LUSU, LUSU, LUSU, LUSU, LUSU, LUSU, NULL, NULL, LSRS, NULL, LTRT}, +/*AD*/ {LULU, LUSU, LUSU, LUSU, LUSU, LUSU, LUSU, NULL, NULL, LSRS, NULL, LTRT}, +/*PO*/ {LULU, LUSU, LUSU, LUSU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT}, +/*SY*/ {LULU, LUSU, LUSU, LURU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT}, +/*2C*/ {LULU, LUSU, LUSU, LUSU, LUSU, LUSU, LUSU, NULL, NULL, LSRS, NULL, LTRT}, +/*SC*/ {LULU, LUSU, LUSU, LURU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT}, +/*!U*/ {LTLT, NULL, NULL, NULL, NULL, NULL, NULL, LTLT, LTLT, LSRS, NULL, LTRT}, +/*!T*/ {LTRT, NULL, NULL, NULL, NULL, NULL, NULL, LTLT, LTRT, LSRS, NULL, LTRT}, +/*HT*/ {LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, NULL, LSRS}, +/*PR*/ {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, +/*PR*/ {LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LSRS, NULL, LTRT}, +}; +const P2PTransportChannelTest::Result* + P2PTransportChannelTest::kMatrixSharedSocketAsIce + [NUM_CONFIGS][NUM_CONFIGS] = { +// OPEN CONE ADDR PORT SYMM 2CON SCON !UDP !TCP HTTP PRXH PRXS +/*OP*/ {LULU, LUSU, LUSU, LUSU, LUPU, LUSU, LUPU, PTLT, LTPT, LSRS, NULL, PTLT}, +/*CO*/ {LULU, LUSU, LUSU, LUSU, LUPU, LUSU, LUPU, NULL, NULL, LSRS, NULL, LTRT}, +/*AD*/ {LULU, LUSU, LUSU, LUSU, LUPU, LUSU, LUPU, NULL, NULL, LSRS, NULL, LTRT}, +/*PO*/ {LULU, LUSU, LUSU, LUSU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT}, +/*SY*/ {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL, LSRS, NULL, LTRT}, +/*2C*/ {LULU, LUSU, LUSU, LUSU, LUPU, LUSU, LUPU, NULL, NULL, LSRS, NULL, LTRT}, +/*SC*/ {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL, LSRS, NULL, LTRT}, +/*!U*/ {PTLT, NULL, NULL, NULL, NULL, NULL, NULL, PTLT, LTPT, LSRS, NULL, LTRT}, +/*!T*/ {LTRT, NULL, NULL, NULL, NULL, NULL, NULL, PTLT, LTRT, LSRS, NULL, LTRT}, +/*HT*/ {LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, NULL, LSRS}, +/*PR*/ {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, +/*PR*/ {LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LSRS, NULL, LTRT}, +}; + +// The actual tests that exercise all the various configurations. +// Test names are of the form P2PTransportChannelTest_TestOPENToNAT_FULL_CONE +// Same test case is run in both GICE and ICE mode. +// kDefaultStepDelay - is used for all Gice cases. +// kMinimumStepDelay - is used when both end points have +// PORTALLOCATOR_ENABLE_SHARED_UFRAG flag enabled. +// Technically we should be able to use kMinimumStepDelay irrespective of +// protocol type. But which might need modifications to current result matrices +// for tests in this file. +#define P2P_TEST_DECLARATION(x, y, z) \ + TEST_F(P2PTransportChannelTest, z##Test##x##To##y##AsGiceNoneSharedUfrag) { \ + ConfigureEndpoints(x, y, kDefaultPortAllocatorFlags, \ + kDefaultPortAllocatorFlags, \ + kDefaultStepDelay, kDefaultStepDelay, \ + cricket::ICEPROTO_GOOGLE); \ + if (kMatrix[x][y] != NULL) \ + Test(*kMatrix[x][y]); \ + else \ + LOG(LS_WARNING) << "Not yet implemented"; \ + } \ + TEST_F(P2PTransportChannelTest, z##Test##x##To##y##AsGiceP0SharedUfrag) { \ + ConfigureEndpoints(x, y, PORTALLOCATOR_ENABLE_SHARED_UFRAG, \ + kDefaultPortAllocatorFlags, \ + kDefaultStepDelay, kDefaultStepDelay, \ + cricket::ICEPROTO_GOOGLE); \ + if (kMatrix[x][y] != NULL) \ + Test(*kMatrix[x][y]); \ + else \ + LOG(LS_WARNING) << "Not yet implemented"; \ + } \ + TEST_F(P2PTransportChannelTest, z##Test##x##To##y##AsGiceP1SharedUfrag) { \ + ConfigureEndpoints(x, y, kDefaultPortAllocatorFlags, \ + PORTALLOCATOR_ENABLE_SHARED_UFRAG, \ + kDefaultStepDelay, kDefaultStepDelay, \ + cricket::ICEPROTO_GOOGLE); \ + if (kMatrixSharedUfrag[x][y] != NULL) \ + Test(*kMatrixSharedUfrag[x][y]); \ + else \ + LOG(LS_WARNING) << "Not yet implemented"; \ + } \ + TEST_F(P2PTransportChannelTest, z##Test##x##To##y##AsGiceBothSharedUfrag) { \ + ConfigureEndpoints(x, y, PORTALLOCATOR_ENABLE_SHARED_UFRAG, \ + PORTALLOCATOR_ENABLE_SHARED_UFRAG, \ + kDefaultStepDelay, kDefaultStepDelay, \ + cricket::ICEPROTO_GOOGLE); \ + if (kMatrixSharedUfrag[x][y] != NULL) \ + Test(*kMatrixSharedUfrag[x][y]); \ + else \ + LOG(LS_WARNING) << "Not yet implemented"; \ + } \ + TEST_F(P2PTransportChannelTest, \ + z##Test##x##To##y##AsGiceBothSharedUfragWithMinimumStepDelay) { \ + ConfigureEndpoints(x, y, PORTALLOCATOR_ENABLE_SHARED_UFRAG, \ + PORTALLOCATOR_ENABLE_SHARED_UFRAG, \ + kMinimumStepDelay, kMinimumStepDelay, \ + cricket::ICEPROTO_GOOGLE); \ + if (kMatrixSharedUfrag[x][y] != NULL) \ + Test(*kMatrixSharedUfrag[x][y]); \ + else \ + LOG(LS_WARNING) << "Not yet implemented"; \ + } \ + TEST_F(P2PTransportChannelTest, \ + z##Test##x##To##y##AsGiceBothSharedUfragSocket) { \ + ConfigureEndpoints(x, y, PORTALLOCATOR_ENABLE_SHARED_UFRAG | \ + PORTALLOCATOR_ENABLE_SHARED_SOCKET, \ + PORTALLOCATOR_ENABLE_SHARED_UFRAG | \ + PORTALLOCATOR_ENABLE_SHARED_SOCKET, \ + kMinimumStepDelay, kMinimumStepDelay, \ + cricket::ICEPROTO_GOOGLE); \ + if (kMatrixSharedSocketAsGice[x][y] != NULL) \ + Test(*kMatrixSharedSocketAsGice[x][y]); \ + else \ + LOG(LS_WARNING) << "Not yet implemented"; \ + } \ + TEST_F(P2PTransportChannelTest, z##Test##x##To##y##AsIce) { \ + ConfigureEndpoints(x, y, PORTALLOCATOR_ENABLE_SHARED_UFRAG | \ + PORTALLOCATOR_ENABLE_SHARED_SOCKET, \ + PORTALLOCATOR_ENABLE_SHARED_UFRAG | \ + PORTALLOCATOR_ENABLE_SHARED_SOCKET, \ + kMinimumStepDelay, kMinimumStepDelay, \ + cricket::ICEPROTO_RFC5245); \ + if (kMatrixSharedSocketAsIce[x][y] != NULL) \ + Test(*kMatrixSharedSocketAsIce[x][y]); \ + else \ + LOG(LS_WARNING) << "Not yet implemented"; \ + } + +#define P2P_TEST(x, y) \ + P2P_TEST_DECLARATION(x, y,) + +#define FLAKY_P2P_TEST(x, y) \ + P2P_TEST_DECLARATION(x, y, DISABLED_) + +// TODO(holmer): Disabled due to randomly failing on webrtc buildbots. +// Issue: webrtc/2383 +#define P2P_TEST_SET(x) \ + P2P_TEST(x, OPEN) \ + FLAKY_P2P_TEST(x, NAT_FULL_CONE) \ + FLAKY_P2P_TEST(x, NAT_ADDR_RESTRICTED) \ + FLAKY_P2P_TEST(x, NAT_PORT_RESTRICTED) \ + P2P_TEST(x, NAT_SYMMETRIC) \ + FLAKY_P2P_TEST(x, NAT_DOUBLE_CONE) \ + P2P_TEST(x, NAT_SYMMETRIC_THEN_CONE) \ + P2P_TEST(x, BLOCK_UDP) \ + P2P_TEST(x, BLOCK_UDP_AND_INCOMING_TCP) \ + P2P_TEST(x, BLOCK_ALL_BUT_OUTGOING_HTTP) \ + P2P_TEST(x, PROXY_HTTPS) \ + P2P_TEST(x, PROXY_SOCKS) + +#define FLAKY_P2P_TEST_SET(x) \ + P2P_TEST(x, OPEN) \ + P2P_TEST(x, NAT_FULL_CONE) \ + P2P_TEST(x, NAT_ADDR_RESTRICTED) \ + P2P_TEST(x, NAT_PORT_RESTRICTED) \ + P2P_TEST(x, NAT_SYMMETRIC) \ + P2P_TEST(x, NAT_DOUBLE_CONE) \ + P2P_TEST(x, NAT_SYMMETRIC_THEN_CONE) \ + P2P_TEST(x, BLOCK_UDP) \ + P2P_TEST(x, BLOCK_UDP_AND_INCOMING_TCP) \ + P2P_TEST(x, BLOCK_ALL_BUT_OUTGOING_HTTP) \ + P2P_TEST(x, PROXY_HTTPS) \ + P2P_TEST(x, PROXY_SOCKS) + +P2P_TEST_SET(OPEN) +P2P_TEST_SET(NAT_FULL_CONE) +P2P_TEST_SET(NAT_ADDR_RESTRICTED) +P2P_TEST_SET(NAT_PORT_RESTRICTED) +P2P_TEST_SET(NAT_SYMMETRIC) +P2P_TEST_SET(NAT_DOUBLE_CONE) +P2P_TEST_SET(NAT_SYMMETRIC_THEN_CONE) +P2P_TEST_SET(BLOCK_UDP) +P2P_TEST_SET(BLOCK_UDP_AND_INCOMING_TCP) +P2P_TEST_SET(BLOCK_ALL_BUT_OUTGOING_HTTP) +P2P_TEST_SET(PROXY_HTTPS) +P2P_TEST_SET(PROXY_SOCKS) + +// Test that we restart candidate allocation when local ufrag&pwd changed. +// Standard Ice protocol is used. +TEST_F(P2PTransportChannelTest, HandleUfragPwdChangeAsIce) { + ConfigureEndpoints(OPEN, OPEN, + PORTALLOCATOR_ENABLE_SHARED_UFRAG, + PORTALLOCATOR_ENABLE_SHARED_UFRAG, + kMinimumStepDelay, kMinimumStepDelay, + cricket::ICEPROTO_RFC5245); + CreateChannels(1); + TestHandleIceUfragPasswordChanged(); + DestroyChannels(); +} + +// Test that we restart candidate allocation when local ufrag&pwd changed. +// Standard Ice protocol is used. +TEST_F(P2PTransportChannelTest, HandleUfragPwdChangeBundleAsIce) { + ConfigureEndpoints(OPEN, OPEN, + PORTALLOCATOR_ENABLE_SHARED_UFRAG, + PORTALLOCATOR_ENABLE_SHARED_UFRAG, + kMinimumStepDelay, kMinimumStepDelay, + cricket::ICEPROTO_RFC5245); + SetAllocatorFlags(0, cricket::PORTALLOCATOR_ENABLE_BUNDLE); + SetAllocatorFlags(1, cricket::PORTALLOCATOR_ENABLE_BUNDLE); + + CreateChannels(2); + TestHandleIceUfragPasswordChanged(); + DestroyChannels(); +} + +// Test that we restart candidate allocation when local ufrag&pwd changed. +// Google Ice protocol is used. +TEST_F(P2PTransportChannelTest, HandleUfragPwdChangeAsGice) { + ConfigureEndpoints(OPEN, OPEN, + PORTALLOCATOR_ENABLE_SHARED_UFRAG, + PORTALLOCATOR_ENABLE_SHARED_UFRAG, + kDefaultStepDelay, kDefaultStepDelay, + cricket::ICEPROTO_GOOGLE); + CreateChannels(1); + TestHandleIceUfragPasswordChanged(); + DestroyChannels(); +} + +// Test that ICE restart works when bundle is enabled. +// Google Ice protocol is used. +TEST_F(P2PTransportChannelTest, HandleUfragPwdChangeBundleAsGice) { + ConfigureEndpoints(OPEN, OPEN, + PORTALLOCATOR_ENABLE_SHARED_UFRAG, + PORTALLOCATOR_ENABLE_SHARED_UFRAG, + kDefaultStepDelay, kDefaultStepDelay, + cricket::ICEPROTO_GOOGLE); + SetAllocatorFlags(0, cricket::PORTALLOCATOR_ENABLE_BUNDLE); + SetAllocatorFlags(1, cricket::PORTALLOCATOR_ENABLE_BUNDLE); + + CreateChannels(2); + TestHandleIceUfragPasswordChanged(); + DestroyChannels(); +} + +// Test the operation of GetStats. +TEST_F(P2PTransportChannelTest, GetStats) { + ConfigureEndpoints(OPEN, OPEN, + kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags, + kDefaultStepDelay, kDefaultStepDelay, + cricket::ICEPROTO_GOOGLE); + CreateChannels(1); + EXPECT_TRUE_WAIT_MARGIN(ep1_ch1()->readable() && ep1_ch1()->writable() && + ep2_ch1()->readable() && ep2_ch1()->writable(), + 1000, 1000); + TestSendRecv(1); + cricket::ConnectionInfos infos; + ASSERT_TRUE(ep1_ch1()->GetStats(&infos)); + ASSERT_EQ(1U, infos.size()); + EXPECT_TRUE(infos[0].new_connection); + EXPECT_TRUE(infos[0].best_connection); + EXPECT_TRUE(infos[0].readable); + EXPECT_TRUE(infos[0].writable); + EXPECT_FALSE(infos[0].timeout); + EXPECT_EQ(10 * 36U, infos[0].sent_total_bytes); + EXPECT_EQ(10 * 36U, infos[0].recv_total_bytes); + EXPECT_GT(infos[0].rtt, 0U); + DestroyChannels(); +} + +// Test that we properly handle getting a STUN error due to slow signaling. +TEST_F(P2PTransportChannelTest, DISABLED_SlowSignaling) { + ConfigureEndpoints(OPEN, NAT_SYMMETRIC, + kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags, + kDefaultStepDelay, kDefaultStepDelay, + cricket::ICEPROTO_GOOGLE); + // Make signaling from the callee take 500ms, so that the initial STUN pings + // from the callee beat the signaling, and so the caller responds with a + // unknown username error. We should just eat that and carry on; mishandling + // this will instead cause all the callee's connections to be discarded. + SetSignalingDelay(1, 1000); + CreateChannels(1); + const cricket::Connection* best_connection = NULL; + // Wait until the callee's connections are created. + WAIT((best_connection = ep2_ch1()->best_connection()) != NULL, 1000); + // Wait to see if they get culled; they shouldn't. + WAIT(ep2_ch1()->best_connection() != best_connection, 1000); + EXPECT_TRUE(ep2_ch1()->best_connection() == best_connection); + DestroyChannels(); +} + +// Test that if remote candidates don't have ufrag and pwd, we still work. +TEST_F(P2PTransportChannelTest, RemoteCandidatesWithoutUfragPwd) { + set_clear_remote_candidates_ufrag_pwd(true); + ConfigureEndpoints(OPEN, OPEN, + PORTALLOCATOR_ENABLE_SHARED_UFRAG, + PORTALLOCATOR_ENABLE_SHARED_UFRAG, + kMinimumStepDelay, kMinimumStepDelay, + cricket::ICEPROTO_GOOGLE); + CreateChannels(1); + const cricket::Connection* best_connection = NULL; + // Wait until the callee's connections are created. + WAIT((best_connection = ep2_ch1()->best_connection()) != NULL, 1000); + // Wait to see if they get culled; they shouldn't. + WAIT(ep2_ch1()->best_connection() != best_connection, 1000); + EXPECT_TRUE(ep2_ch1()->best_connection() == best_connection); + DestroyChannels(); +} + +// Test that a host behind NAT cannot be reached when incoming_only +// is set to true. +TEST_F(P2PTransportChannelTest, IncomingOnlyBlocked) { + ConfigureEndpoints(NAT_FULL_CONE, OPEN, + kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags, + kDefaultStepDelay, kDefaultStepDelay, + cricket::ICEPROTO_GOOGLE); + + SetAllocatorFlags(0, kOnlyLocalPorts); + CreateChannels(1); + ep1_ch1()->set_incoming_only(true); + + // Pump for 1 second and verify that the channels are not connected. + rtc::Thread::Current()->ProcessMessages(1000); + + EXPECT_FALSE(ep1_ch1()->readable()); + EXPECT_FALSE(ep1_ch1()->writable()); + EXPECT_FALSE(ep2_ch1()->readable()); + EXPECT_FALSE(ep2_ch1()->writable()); + + DestroyChannels(); +} + +// Test that a peer behind NAT can connect to a peer that has +// incoming_only flag set. +TEST_F(P2PTransportChannelTest, IncomingOnlyOpen) { + ConfigureEndpoints(OPEN, NAT_FULL_CONE, + kDefaultPortAllocatorFlags, + kDefaultPortAllocatorFlags, + kDefaultStepDelay, kDefaultStepDelay, + cricket::ICEPROTO_GOOGLE); + + SetAllocatorFlags(0, kOnlyLocalPorts); + CreateChannels(1); + ep1_ch1()->set_incoming_only(true); + + EXPECT_TRUE_WAIT_MARGIN(ep1_ch1() != NULL && ep2_ch1() != NULL && + ep1_ch1()->readable() && ep1_ch1()->writable() && + ep2_ch1()->readable() && ep2_ch1()->writable(), + 1000, 1000); + + DestroyChannels(); +} + +TEST_F(P2PTransportChannelTest, TestTcpConnectionsFromActiveToPassive) { + AddAddress(0, kPublicAddrs[0]); + AddAddress(1, kPublicAddrs[1]); + + SetAllocationStepDelay(0, kMinimumStepDelay); + SetAllocationStepDelay(1, kMinimumStepDelay); + + int kOnlyLocalTcpPorts = cricket::PORTALLOCATOR_DISABLE_UDP | + cricket::PORTALLOCATOR_DISABLE_STUN | + cricket::PORTALLOCATOR_DISABLE_RELAY | + cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG; + // Disable all protocols except TCP. + SetAllocatorFlags(0, kOnlyLocalTcpPorts); + SetAllocatorFlags(1, kOnlyLocalTcpPorts); + + SetAllowTcpListen(0, true); // actpass. + SetAllowTcpListen(1, false); // active. + + CreateChannels(1); + + EXPECT_TRUE_WAIT(ep1_ch1()->readable() && ep1_ch1()->writable() && + ep2_ch1()->readable() && ep2_ch1()->writable(), + 1000); + EXPECT_TRUE( + ep1_ch1()->best_connection() && ep2_ch1()->best_connection() && + LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) && + RemoteCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[1])); + + std::string kTcpProtocol = "tcp"; + EXPECT_EQ(kTcpProtocol, RemoteCandidate(ep1_ch1())->protocol()); + EXPECT_EQ(kTcpProtocol, LocalCandidate(ep1_ch1())->protocol()); + EXPECT_EQ(kTcpProtocol, RemoteCandidate(ep2_ch1())->protocol()); + EXPECT_EQ(kTcpProtocol, LocalCandidate(ep2_ch1())->protocol()); + + TestSendRecv(1); + DestroyChannels(); +} + +TEST_F(P2PTransportChannelTest, TestBundleAllocatorToBundleAllocator) { + AddAddress(0, kPublicAddrs[0]); + AddAddress(1, kPublicAddrs[1]); + SetAllocatorFlags(0, cricket::PORTALLOCATOR_ENABLE_BUNDLE); + SetAllocatorFlags(1, cricket::PORTALLOCATOR_ENABLE_BUNDLE); + + CreateChannels(2); + + EXPECT_TRUE_WAIT(ep1_ch1()->readable() && + ep1_ch1()->writable() && + ep2_ch1()->readable() && + ep2_ch1()->writable(), + 1000); + EXPECT_TRUE(ep1_ch1()->best_connection() && + ep2_ch1()->best_connection()); + + EXPECT_FALSE(ep1_ch2()->readable()); + EXPECT_FALSE(ep1_ch2()->writable()); + EXPECT_FALSE(ep2_ch2()->readable()); + EXPECT_FALSE(ep2_ch2()->writable()); + + TestSendRecv(1); // Only 1 channel is writable per Endpoint. + DestroyChannels(); +} + +TEST_F(P2PTransportChannelTest, TestBundleAllocatorToNonBundleAllocator) { + AddAddress(0, kPublicAddrs[0]); + AddAddress(1, kPublicAddrs[1]); + // Enable BUNDLE flag at one side. + SetAllocatorFlags(0, cricket::PORTALLOCATOR_ENABLE_BUNDLE); + + CreateChannels(2); + + EXPECT_TRUE_WAIT(ep1_ch1()->readable() && + ep1_ch1()->writable() && + ep2_ch1()->readable() && + ep2_ch1()->writable(), + 1000); + EXPECT_TRUE_WAIT(ep1_ch2()->readable() && + ep1_ch2()->writable() && + ep2_ch2()->readable() && + ep2_ch2()->writable(), + 1000); + + EXPECT_TRUE(ep1_ch1()->best_connection() && + ep2_ch1()->best_connection()); + EXPECT_TRUE(ep1_ch2()->best_connection() && + ep2_ch2()->best_connection()); + + TestSendRecv(2); + DestroyChannels(); +} + +TEST_F(P2PTransportChannelTest, TestIceRoleConflictWithoutBundle) { + AddAddress(0, kPublicAddrs[0]); + AddAddress(1, kPublicAddrs[1]); + TestSignalRoleConflict(); +} + +TEST_F(P2PTransportChannelTest, TestIceRoleConflictWithBundle) { + AddAddress(0, kPublicAddrs[0]); + AddAddress(1, kPublicAddrs[1]); + SetAllocatorFlags(0, cricket::PORTALLOCATOR_ENABLE_BUNDLE); + SetAllocatorFlags(1, cricket::PORTALLOCATOR_ENABLE_BUNDLE); + TestSignalRoleConflict(); +} + +// Tests that the ice configs (protocol, tiebreaker and role) can be passed +// down to ports. +TEST_F(P2PTransportChannelTest, TestIceConfigWillPassDownToPort) { + AddAddress(0, kPublicAddrs[0]); + AddAddress(1, kPublicAddrs[1]); + + SetIceRole(0, cricket::ICEROLE_CONTROLLING); + SetIceProtocol(0, cricket::ICEPROTO_GOOGLE); + SetIceTiebreaker(0, kTiebreaker1); + SetIceRole(1, cricket::ICEROLE_CONTROLLING); + SetIceProtocol(1, cricket::ICEPROTO_RFC5245); + SetIceTiebreaker(1, kTiebreaker2); + + CreateChannels(1); + + EXPECT_EQ_WAIT(2u, ep1_ch1()->ports().size(), 1000); + + const std::vector ports_before = ep1_ch1()->ports(); + for (size_t i = 0; i < ports_before.size(); ++i) { + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, ports_before[i]->GetIceRole()); + EXPECT_EQ(cricket::ICEPROTO_GOOGLE, ports_before[i]->IceProtocol()); + EXPECT_EQ(kTiebreaker1, ports_before[i]->IceTiebreaker()); + } + + ep1_ch1()->SetIceRole(cricket::ICEROLE_CONTROLLED); + ep1_ch1()->SetIceProtocolType(cricket::ICEPROTO_RFC5245); + ep1_ch1()->SetIceTiebreaker(kTiebreaker2); + + const std::vector ports_after = ep1_ch1()->ports(); + for (size_t i = 0; i < ports_after.size(); ++i) { + EXPECT_EQ(cricket::ICEROLE_CONTROLLED, ports_before[i]->GetIceRole()); + EXPECT_EQ(cricket::ICEPROTO_RFC5245, ports_before[i]->IceProtocol()); + // SetIceTiebreaker after Connect() has been called will fail. So expect the + // original value. + EXPECT_EQ(kTiebreaker1, ports_before[i]->IceTiebreaker()); + } + + EXPECT_TRUE_WAIT(ep1_ch1()->readable() && + ep1_ch1()->writable() && + ep2_ch1()->readable() && + ep2_ch1()->writable(), + 1000); + + EXPECT_TRUE(ep1_ch1()->best_connection() && + ep2_ch1()->best_connection()); + + TestSendRecv(1); + DestroyChannels(); +} + +// This test verifies channel can handle ice messages when channel is in +// hybrid mode. +TEST_F(P2PTransportChannelTest, TestConnectivityBetweenHybridandIce) { + TestHybridConnectivity(cricket::ICEPROTO_RFC5245); +} + +// This test verifies channel can handle Gice messages when channel is in +// hybrid mode. +TEST_F(P2PTransportChannelTest, TestConnectivityBetweenHybridandGice) { + TestHybridConnectivity(cricket::ICEPROTO_GOOGLE); +} + +// Verify that we can set DSCP value and retrieve properly from P2PTC. +TEST_F(P2PTransportChannelTest, TestDefaultDscpValue) { + AddAddress(0, kPublicAddrs[0]); + AddAddress(1, kPublicAddrs[1]); + + CreateChannels(1); + EXPECT_EQ(rtc::DSCP_NO_CHANGE, + GetEndpoint(0)->cd1_.ch_->DefaultDscpValue()); + EXPECT_EQ(rtc::DSCP_NO_CHANGE, + GetEndpoint(1)->cd1_.ch_->DefaultDscpValue()); + GetEndpoint(0)->cd1_.ch_->SetOption( + rtc::Socket::OPT_DSCP, rtc::DSCP_CS6); + GetEndpoint(1)->cd1_.ch_->SetOption( + rtc::Socket::OPT_DSCP, rtc::DSCP_CS6); + EXPECT_EQ(rtc::DSCP_CS6, + GetEndpoint(0)->cd1_.ch_->DefaultDscpValue()); + EXPECT_EQ(rtc::DSCP_CS6, + GetEndpoint(1)->cd1_.ch_->DefaultDscpValue()); + GetEndpoint(0)->cd1_.ch_->SetOption( + rtc::Socket::OPT_DSCP, rtc::DSCP_AF41); + GetEndpoint(1)->cd1_.ch_->SetOption( + rtc::Socket::OPT_DSCP, rtc::DSCP_AF41); + EXPECT_EQ(rtc::DSCP_AF41, + GetEndpoint(0)->cd1_.ch_->DefaultDscpValue()); + EXPECT_EQ(rtc::DSCP_AF41, + GetEndpoint(1)->cd1_.ch_->DefaultDscpValue()); +} + +// Verify IPv6 connection is preferred over IPv4. +// Flaky: https://code.google.com/p/webrtc/issues/detail?id=3317 +TEST_F(P2PTransportChannelTest, DISABLED_TestIPv6Connections) { + AddAddress(0, kIPv6PublicAddrs[0]); + AddAddress(0, kPublicAddrs[0]); + AddAddress(1, kIPv6PublicAddrs[1]); + AddAddress(1, kPublicAddrs[1]); + + SetAllocationStepDelay(0, kMinimumStepDelay); + SetAllocationStepDelay(1, kMinimumStepDelay); + + // Enable IPv6 + SetAllocatorFlags(0, cricket::PORTALLOCATOR_ENABLE_IPV6); + SetAllocatorFlags(1, cricket::PORTALLOCATOR_ENABLE_IPV6); + + CreateChannels(1); + + EXPECT_TRUE_WAIT(ep1_ch1()->readable() && ep1_ch1()->writable() && + ep2_ch1()->readable() && ep2_ch1()->writable(), + 1000); + EXPECT_TRUE( + ep1_ch1()->best_connection() && ep2_ch1()->best_connection() && + LocalCandidate(ep1_ch1())->address().EqualIPs(kIPv6PublicAddrs[0]) && + RemoteCandidate(ep1_ch1())->address().EqualIPs(kIPv6PublicAddrs[1])); + + TestSendRecv(1); + DestroyChannels(); +} + +// Testing forceful TURN connections. +TEST_F(P2PTransportChannelTest, TestForceTurn) { + ConfigureEndpoints(NAT_PORT_RESTRICTED, NAT_SYMMETRIC, + kDefaultPortAllocatorFlags | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET | + cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG, + kDefaultPortAllocatorFlags | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET | + cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG, + kDefaultStepDelay, kDefaultStepDelay, + cricket::ICEPROTO_RFC5245); + set_force_relay(true); + + SetAllocationStepDelay(0, kMinimumStepDelay); + SetAllocationStepDelay(1, kMinimumStepDelay); + + CreateChannels(1); + + EXPECT_TRUE_WAIT(ep1_ch1()->readable() && + ep1_ch1()->writable() && + ep2_ch1()->readable() && + ep2_ch1()->writable(), + 1000); + + EXPECT_TRUE(ep1_ch1()->best_connection() && + ep2_ch1()->best_connection()); + + EXPECT_EQ("relay", RemoteCandidate(ep1_ch1())->type()); + EXPECT_EQ("relay", LocalCandidate(ep1_ch1())->type()); + EXPECT_EQ("relay", RemoteCandidate(ep2_ch1())->type()); + EXPECT_EQ("relay", LocalCandidate(ep2_ch1())->type()); + + TestSendRecv(1); + DestroyChannels(); +} + +// Test what happens when we have 2 users behind the same NAT. This can lead +// to interesting behavior because the STUN server will only give out the +// address of the outermost NAT. +class P2PTransportChannelSameNatTest : public P2PTransportChannelTestBase { + protected: + void ConfigureEndpoints(Config nat_type, Config config1, Config config2) { + ASSERT(nat_type >= NAT_FULL_CONE && nat_type <= NAT_SYMMETRIC); + rtc::NATSocketServer::Translator* outer_nat = + nat()->AddTranslator(kPublicAddrs[0], kNatAddrs[0], + static_cast(nat_type - NAT_FULL_CONE)); + ConfigureEndpoint(outer_nat, 0, config1); + ConfigureEndpoint(outer_nat, 1, config2); + } + void ConfigureEndpoint(rtc::NATSocketServer::Translator* nat, + int endpoint, Config config) { + ASSERT(config <= NAT_SYMMETRIC); + if (config == OPEN) { + AddAddress(endpoint, kPrivateAddrs[endpoint]); + nat->AddClient(kPrivateAddrs[endpoint]); + } else { + AddAddress(endpoint, kCascadedPrivateAddrs[endpoint]); + nat->AddTranslator(kPrivateAddrs[endpoint], kCascadedNatAddrs[endpoint], + static_cast(config - NAT_FULL_CONE))->AddClient( + kCascadedPrivateAddrs[endpoint]); + } + } +}; + +TEST_F(P2PTransportChannelSameNatTest, TestConesBehindSameCone) { + ConfigureEndpoints(NAT_FULL_CONE, NAT_FULL_CONE, NAT_FULL_CONE); + Test(kLocalUdpToStunUdp); +} + +// Test what happens when we have multiple available pathways. +// In the future we will try different RTTs and configs for the different +// interfaces, so that we can simulate a user with Ethernet and VPN networks. +class P2PTransportChannelMultihomedTest : public P2PTransportChannelTestBase { +}; + +// Test that we can establish connectivity when both peers are multihomed. +TEST_F(P2PTransportChannelMultihomedTest, DISABLED_TestBasic) { + AddAddress(0, kPublicAddrs[0]); + AddAddress(0, kAlternateAddrs[0]); + AddAddress(1, kPublicAddrs[1]); + AddAddress(1, kAlternateAddrs[1]); + Test(kLocalUdpToLocalUdp); +} + +// Test that we can quickly switch links if an interface goes down. +TEST_F(P2PTransportChannelMultihomedTest, TestFailover) { + AddAddress(0, kPublicAddrs[0]); + // Adding alternate address will make sure |kPublicAddrs| has the higher + // priority than others. This is due to FakeNetwork::AddInterface method. + AddAddress(1, kAlternateAddrs[1]); + AddAddress(1, kPublicAddrs[1]); + + // Use only local ports for simplicity. + SetAllocatorFlags(0, kOnlyLocalPorts); + SetAllocatorFlags(1, kOnlyLocalPorts); + + // Create channels and let them go writable, as usual. + CreateChannels(1); + EXPECT_TRUE_WAIT(ep1_ch1()->readable() && ep1_ch1()->writable() && + ep2_ch1()->readable() && ep2_ch1()->writable(), + 1000); + EXPECT_TRUE( + ep1_ch1()->best_connection() && ep2_ch1()->best_connection() && + LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) && + RemoteCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[1])); + + // Blackhole any traffic to or from the public addrs. + LOG(LS_INFO) << "Failing over..."; + fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, + kPublicAddrs[1]); + + // We should detect loss of connectivity within 5 seconds or so. + EXPECT_TRUE_WAIT(!ep1_ch1()->writable(), 7000); + + // We should switch over to use the alternate addr immediately + // when we lose writability. + EXPECT_TRUE_WAIT( + ep1_ch1()->best_connection() && ep2_ch1()->best_connection() && + LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) && + RemoteCandidate(ep1_ch1())->address().EqualIPs(kAlternateAddrs[1]), + 3000); + + DestroyChannels(); +} + +// Test that we can switch links in a coordinated fashion. +TEST_F(P2PTransportChannelMultihomedTest, TestDrain) { + AddAddress(0, kPublicAddrs[0]); + AddAddress(1, kPublicAddrs[1]); + // Use only local ports for simplicity. + SetAllocatorFlags(0, kOnlyLocalPorts); + SetAllocatorFlags(1, kOnlyLocalPorts); + + // Create channels and let them go writable, as usual. + CreateChannels(1); + EXPECT_TRUE_WAIT(ep1_ch1()->readable() && ep1_ch1()->writable() && + ep2_ch1()->readable() && ep2_ch1()->writable(), + 1000); + EXPECT_TRUE( + ep1_ch1()->best_connection() && ep2_ch1()->best_connection() && + LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) && + RemoteCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[1])); + + // Remove the public interface, add the alternate interface, and allocate + // a new generation of candidates for the new interface (via Connect()). + LOG(LS_INFO) << "Draining..."; + AddAddress(1, kAlternateAddrs[1]); + RemoveAddress(1, kPublicAddrs[1]); + ep2_ch1()->Connect(); + + // We should switch over to use the alternate address after + // an exchange of pings. + EXPECT_TRUE_WAIT( + ep1_ch1()->best_connection() && ep2_ch1()->best_connection() && + LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) && + RemoteCandidate(ep1_ch1())->address().EqualIPs(kAlternateAddrs[1]), + 3000); + + DestroyChannels(); +} diff --git a/webrtc/p2p/base/packetsocketfactory.h b/webrtc/p2p/base/packetsocketfactory.h new file mode 100644 index 000000000..1f45feca1 --- /dev/null +++ b/webrtc/p2p/base/packetsocketfactory.h @@ -0,0 +1,52 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_PACKETSOCKETFACTORY_H_ +#define WEBRTC_P2P_BASE_PACKETSOCKETFACTORY_H_ + +#include "webrtc/base/proxyinfo.h" + +namespace rtc { + +class AsyncPacketSocket; +class AsyncResolverInterface; + +class PacketSocketFactory { + public: + enum Options { + OPT_SSLTCP = 0x01, // Pseudo-TLS. + OPT_TLS = 0x02, + OPT_STUN = 0x04, + }; + + PacketSocketFactory() { } + virtual ~PacketSocketFactory() { } + + virtual AsyncPacketSocket* CreateUdpSocket( + const SocketAddress& address, int min_port, int max_port) = 0; + virtual AsyncPacketSocket* CreateServerTcpSocket( + const SocketAddress& local_address, int min_port, int max_port, + int opts) = 0; + + // TODO: |proxy_info| and |user_agent| should be set + // per-factory and not when socket is created. + virtual AsyncPacketSocket* CreateClientTcpSocket( + const SocketAddress& local_address, const SocketAddress& remote_address, + const ProxyInfo& proxy_info, const std::string& user_agent, int opts) = 0; + + virtual AsyncResolverInterface* CreateAsyncResolver() = 0; + + private: + DISALLOW_EVIL_CONSTRUCTORS(PacketSocketFactory); +}; + +} // namespace rtc + +#endif // WEBRTC_P2P_BASE_PACKETSOCKETFACTORY_H_ diff --git a/webrtc/p2p/base/parsing.cc b/webrtc/p2p/base/parsing.cc new file mode 100644 index 000000000..04d7e79ee --- /dev/null +++ b/webrtc/p2p/base/parsing.cc @@ -0,0 +1,141 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/parsing.h" + +#include +#include +#include "webrtc/base/stringutils.h" + +namespace { +static const char kTrue[] = "true"; +static const char kOne[] = "1"; +} + +namespace cricket { + +bool BadParse(const std::string& text, ParseError* err) { + if (err != NULL) { + err->text = text; + } + return false; +} + +bool BadWrite(const std::string& text, WriteError* err) { + if (err != NULL) { + err->text = text; + } + return false; +} + +std::string GetXmlAttr(const buzz::XmlElement* elem, + const buzz::QName& name, + const std::string& def) { + std::string val = elem->Attr(name); + return val.empty() ? def : val; +} + +std::string GetXmlAttr(const buzz::XmlElement* elem, + const buzz::QName& name, + const char* def) { + return GetXmlAttr(elem, name, std::string(def)); +} + +bool GetXmlAttr(const buzz::XmlElement* elem, + const buzz::QName& name, bool def) { + std::string val = elem->Attr(name); + std::transform(val.begin(), val.end(), val.begin(), tolower); + + return val.empty() ? def : (val == kTrue || val == kOne); +} + +int GetXmlAttr(const buzz::XmlElement* elem, + const buzz::QName& name, int def) { + std::string val = elem->Attr(name); + return val.empty() ? def : atoi(val.c_str()); +} + +const buzz::XmlElement* GetXmlChild(const buzz::XmlElement* parent, + const std::string& name) { + for (const buzz::XmlElement* child = parent->FirstElement(); + child != NULL; + child = child->NextElement()) { + if (child->Name().LocalPart() == name) { + return child; + } + } + return NULL; +} + +bool RequireXmlChild(const buzz::XmlElement* parent, + const std::string& name, + const buzz::XmlElement** child, + ParseError* error) { + *child = GetXmlChild(parent, name); + if (*child == NULL) { + return BadParse("element '" + parent->Name().Merged() + + "' missing required child '" + name, + error); + } else { + return true; + } +} + +bool RequireXmlAttr(const buzz::XmlElement* elem, + const buzz::QName& name, + std::string* value, + ParseError* error) { + if (!elem->HasAttr(name)) { + return BadParse("element '" + elem->Name().Merged() + + "' missing required attribute '" + + name.Merged() + "'", + error); + } else { + *value = elem->Attr(name); + return true; + } +} + +void AddXmlAttrIfNonEmpty(buzz::XmlElement* elem, + const buzz::QName name, + const std::string& value) { + if (!value.empty()) { + elem->AddAttr(name, value); + } +} + +void AddXmlChildren(buzz::XmlElement* parent, + const std::vector& children) { + for (std::vector::const_iterator iter = children.begin(); + iter != children.end(); + iter++) { + parent->AddElement(*iter); + } +} + +void CopyXmlChildren(const buzz::XmlElement* source, buzz::XmlElement* dest) { + for (const buzz::XmlElement* child = source->FirstElement(); + child != NULL; + child = child->NextElement()) { + dest->AddElement(new buzz::XmlElement(*child)); + } +} + +std::vector CopyOfXmlChildren(const buzz::XmlElement* elem) { + std::vector children; + for (const buzz::XmlElement* child = elem->FirstElement(); + child != NULL; + child = child->NextElement()) { + children.push_back(new buzz::XmlElement(*child)); + } + return children; +} + +} // namespace cricket diff --git a/webrtc/p2p/base/parsing.h b/webrtc/p2p/base/parsing.h new file mode 100644 index 000000000..6b32b5dbd --- /dev/null +++ b/webrtc/p2p/base/parsing.h @@ -0,0 +1,140 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_PARSING_H_ +#define WEBRTC_P2P_BASE_PARSING_H_ + +#include +#include +#include "webrtc/libjingle/xmllite/xmlelement.h" // Needed to delete ParseError.extra. +#include "webrtc/base/basictypes.h" +#include "webrtc/base/stringencode.h" + +namespace cricket { + +typedef std::vector XmlElements; + +// We decided "bool Parse(in, out*, error*)" is generally the best +// parse signature. "out Parse(in)" doesn't allow for errors. +// "error* Parse(in, out*)" doesn't allow flexible memory management. + +// The error type for parsing. +struct ParseError { + public: + // explains the error + std::string text; + // provide details about what wasn't parsable + const buzz::XmlElement* extra; + + ParseError() : extra(NULL) {} + + ~ParseError() { + delete extra; + } + + void SetText(const std::string& text) { + this->text = text; + } +}; + +// The error type for writing. +struct WriteError { + std::string text; + + void SetText(const std::string& text) { + this->text = text; + } +}; + +// Convenience method for returning a message when parsing fails. +bool BadParse(const std::string& text, ParseError* err); + +// Convenience method for returning a message when writing fails. +bool BadWrite(const std::string& text, WriteError* error); + +// helper XML functions +std::string GetXmlAttr(const buzz::XmlElement* elem, + const buzz::QName& name, + const std::string& def); +std::string GetXmlAttr(const buzz::XmlElement* elem, + const buzz::QName& name, + const char* def); +// Return true if the value is "true" or "1". +bool GetXmlAttr(const buzz::XmlElement* elem, + const buzz::QName& name, bool def); +int GetXmlAttr(const buzz::XmlElement* elem, + const buzz::QName& name, int def); + +template +bool GetXmlAttr(const buzz::XmlElement* elem, + const buzz::QName& name, + T* val_out) { + if (!elem->HasAttr(name)) { + return false; + } + std::string unparsed = elem->Attr(name); + return rtc::FromString(unparsed, val_out); +} + +template +bool GetXmlAttr(const buzz::XmlElement* elem, + const buzz::QName& name, + const T& def, + T* val_out) { + if (!elem->HasAttr(name)) { + *val_out = def; + return true; + } + return GetXmlAttr(elem, name, val_out); +} + +template +bool AddXmlAttr(buzz::XmlElement* elem, + const buzz::QName& name, const T& val) { + std::string buf; + if (!rtc::ToString(val, &buf)) { + return false; + } + elem->AddAttr(name, buf); + return true; +} + +template +bool SetXmlBody(buzz::XmlElement* elem, const T& val) { + std::string buf; + if (!rtc::ToString(val, &buf)) { + return false; + } + elem->SetBodyText(buf); + return true; +} + +const buzz::XmlElement* GetXmlChild(const buzz::XmlElement* parent, + const std::string& name); + +bool RequireXmlChild(const buzz::XmlElement* parent, + const std::string& name, + const buzz::XmlElement** child, + ParseError* error); +bool RequireXmlAttr(const buzz::XmlElement* elem, + const buzz::QName& name, + std::string* value, + ParseError* error); +void AddXmlAttrIfNonEmpty(buzz::XmlElement* elem, + const buzz::QName name, + const std::string& value); +void AddXmlChildren(buzz::XmlElement* parent, + const std::vector& children); +void CopyXmlChildren(const buzz::XmlElement* source, buzz::XmlElement* dest); +std::vector CopyOfXmlChildren(const buzz::XmlElement* elem); + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_PARSING_H_ diff --git a/webrtc/p2p/base/port.cc b/webrtc/p2p/base/port.cc new file mode 100644 index 000000000..f569d9f50 --- /dev/null +++ b/webrtc/p2p/base/port.cc @@ -0,0 +1,1430 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/port.h" + +#include +#include + +#include "webrtc/p2p/base/common.h" +#include "webrtc/p2p/base/portallocator.h" +#include "webrtc/base/base64.h" +#include "webrtc/base/crc32.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/messagedigest.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/stringutils.h" + +namespace { + +// 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; +} + +// GICE(ICEPROTO_GOOGLE) requires different username for RTP and RTCP. +// This function generates a different username by +1 on the last character of +// the given username (|rtp_ufrag|). +std::string GetRtcpUfragFromRtpUfrag(const std::string& rtp_ufrag) { + ASSERT(!rtp_ufrag.empty()); + if (rtp_ufrag.empty()) { + return rtp_ufrag; + } + // Change the last character to the one next to it in the base64 table. + char new_last_char; + if (!rtc::Base64::GetNextBase64Char(rtp_ufrag[rtp_ufrag.size() - 1], + &new_last_char)) { + // Should not be here. + ASSERT(false); + } + std::string rtcp_ufrag = rtp_ufrag; + rtcp_ufrag[rtcp_ufrag.size() - 1] = new_last_char; + ASSERT(rtcp_ufrag != rtp_ufrag); + return rtcp_ufrag; +} + +// 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. +inline uint32 ConservativeRTTEstimate(uint32 rtt) { + return rtc::_max(MINIMUM_RTT, rtc::_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 + +// Used by the Connection. +const uint32 MSG_DELETE = 1; +} + +namespace cricket { + +// TODO(ronghuawu): Use "host", "srflx", "prflx" and "relay". But this requires +// the signaling part be updated correspondingly as well. +const char LOCAL_PORT_TYPE[] = "local"; +const char STUN_PORT_TYPE[] = "stun"; +const char PRFLX_PORT_TYPE[] = "prflx"; +const char RELAY_PORT_TYPE[] = "relay"; + +const char UDP_PROTOCOL_NAME[] = "udp"; +const char TCP_PROTOCOL_NAME[] = "tcp"; +const char SSLTCP_PROTOCOL_NAME[] = "ssltcp"; + +static const char* const PROTO_NAMES[] = { UDP_PROTOCOL_NAME, + TCP_PROTOCOL_NAME, + SSLTCP_PROTOCOL_NAME }; + +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 (_stricmp(PROTO_NAMES[i], value) == 0) { + *proto = static_cast(i); + return true; + } + } + return false; +} + +// RFC 6544, TCP candidate encoding rules. +const int DISCARD_PORT = 9; +const char TCPTYPE_ACTIVE_STR[] = "active"; +const char TCPTYPE_PASSIVE_STR[] = "passive"; +const char TCPTYPE_SIMOPEN_STR[] = "so"; + +// Foundation: An arbitrary string that is the same for two candidates +// that have the same type, base IP address, protocol (UDP, TCP, +// etc.), and STUN or TURN server. If any of these are different, +// then the foundation will be different. Two candidate pairs with +// the same foundation pairs are likely to have similar network +// characteristics. Foundations are used in the frozen algorithm. +static std::string ComputeFoundation( + const std::string& type, + const std::string& protocol, + const rtc::SocketAddress& base_address) { + std::ostringstream ost; + ost << type << base_address.ipaddr().ToString() << protocol; + return rtc::ToString(rtc::ComputeCrc32(ost.str())); +} + +Port::Port(rtc::Thread* thread, rtc::PacketSocketFactory* factory, + rtc::Network* network, const rtc::IPAddress& ip, + const std::string& username_fragment, const std::string& password) + : thread_(thread), + factory_(factory), + send_retransmit_count_attribute_(false), + network_(network), + ip_(ip), + min_port_(0), + max_port_(0), + component_(ICE_CANDIDATE_COMPONENT_DEFAULT), + generation_(0), + ice_username_fragment_(username_fragment), + password_(password), + timeout_delay_(kPortTimeoutDelay), + enable_port_packets_(false), + ice_protocol_(ICEPROTO_HYBRID), + ice_role_(ICEROLE_UNKNOWN), + tiebreaker_(0), + shared_socket_(true), + candidate_filter_(CF_ALL) { + Construct(); +} + +Port::Port(rtc::Thread* thread, const std::string& type, + rtc::PacketSocketFactory* factory, + rtc::Network* network, const rtc::IPAddress& ip, + int min_port, int max_port, const std::string& username_fragment, + const std::string& password) + : thread_(thread), + factory_(factory), + type_(type), + send_retransmit_count_attribute_(false), + network_(network), + ip_(ip), + min_port_(min_port), + max_port_(max_port), + component_(ICE_CANDIDATE_COMPONENT_DEFAULT), + generation_(0), + ice_username_fragment_(username_fragment), + password_(password), + timeout_delay_(kPortTimeoutDelay), + enable_port_packets_(false), + ice_protocol_(ICEPROTO_HYBRID), + ice_role_(ICEROLE_UNKNOWN), + tiebreaker_(0), + shared_socket_(false), + candidate_filter_(CF_ALL) { + ASSERT(factory_ != NULL); + Construct(); +} + +void Port::Construct() { + // If the username_fragment and password are empty, we should just create one. + if (ice_username_fragment_.empty()) { + ASSERT(password_.empty()); + ice_username_fragment_ = rtc::CreateRandomString(ICE_UFRAG_LENGTH); + password_ = rtc::CreateRandomString(ICE_PWD_LENGTH); + } + LOG_J(LS_INFO, this) << "Port created"; +} + +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 rtc::SocketAddress& remote_addr) { + AddressMap::const_iterator iter = connections_.find(remote_addr); + if (iter != connections_.end()) + return iter->second; + else + return NULL; +} + +void Port::AddAddress(const rtc::SocketAddress& address, + const rtc::SocketAddress& base_address, + const rtc::SocketAddress& related_address, + const std::string& protocol, + const std::string& tcptype, + const std::string& type, + uint32 type_preference, + uint32 relay_preference, + bool final) { + if (protocol == TCP_PROTOCOL_NAME && type == LOCAL_PORT_TYPE) { + ASSERT(!tcptype.empty()); + } + + Candidate c; + c.set_id(rtc::CreateRandomString(8)); + c.set_component(component_); + c.set_type(type); + c.set_protocol(protocol); + c.set_tcptype(tcptype); + c.set_address(address); + c.set_priority(c.GetPriority(type_preference, network_->preference(), + relay_preference)); + c.set_username(username_fragment()); + c.set_password(password_); + c.set_network_name(network_->name()); + c.set_generation(generation_); + c.set_related_address(related_address); + c.set_foundation(ComputeFoundation(type, protocol, base_address)); + candidates_.push_back(c); + SignalCandidateReady(this, c); + + if (final) { + SignalPortComplete(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 rtc::SocketAddress& addr, + ProtocolType proto) { + // If the user has enabled port packets, just hand this over. + if (enable_port_packets_) { + SignalReadPacket(this, data, size, addr); + return; + } + + // If this is an authenticated STUN request, then signal unknown address and + // send back a proper binding response. + rtc::scoped_ptr msg; + std::string remote_username; + if (!GetStunMessage(data, size, addr, msg.accept(), &remote_username)) { + LOG_J(LS_ERROR, this) << "Received non-STUN packet from unknown address (" + << addr.ToSensitiveString() << ")"; + } else if (!msg) { + // STUN message handled already + } else if (msg->type() == STUN_BINDING_REQUEST) { + // Check for role conflicts. + if (IsStandardIce() && + !MaybeIceRoleConflict(addr, msg.get(), remote_username)) { + LOG(LS_INFO) << "Received conflicting role from the peer."; + return; + } + + SignalUnknownAddress(this, addr, proto, msg.get(), remote_username, false); + } else { + // NOTE(tschmelcher): STUN_BINDING_RESPONSE is benign. It occurs if we + // pruned a connection for this port while it had STUN requests in flight, + // because we then get back responses for them, which this code correctly + // does not handle. + if (msg->type() != STUN_BINDING_RESPONSE) { + LOG_J(LS_ERROR, this) << "Received unexpected STUN message type (" + << msg->type() << ") from unknown address (" + << addr.ToSensitiveString() << ")"; + } + } +} + +void Port::OnReadyToSend() { + AddressMap::iterator iter = connections_.begin(); + for (; iter != connections_.end(); ++iter) { + iter->second->OnReadyToSend(); + } +} + +size_t Port::AddPrflxCandidate(const Candidate& local) { + candidates_.push_back(local); + return (candidates_.size() - 1); +} + +bool Port::IsStandardIce() const { + return (ice_protocol_ == ICEPROTO_RFC5245); +} + +bool Port::IsGoogleIce() const { + return (ice_protocol_ == ICEPROTO_GOOGLE); +} + +bool Port::IsHybridIce() const { + return (ice_protocol_ == ICEPROTO_HYBRID); +} + +bool Port::GetStunMessage(const char* data, size_t size, + const rtc::SocketAddress& addr, + IceMessage** out_msg, std::string* out_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. + ASSERT(out_msg != NULL); + ASSERT(out_username != NULL); + *out_msg = NULL; + out_username->clear(); + + // Don't bother parsing the packet if we can tell it's not STUN. + // In ICE mode, all STUN packets will have a valid fingerprint. + if (IsStandardIce() && !StunMessage::ValidateFingerprint(data, size)) { + return false; + } + + // Parse the request message. If the packet is not a complete and correct + // STUN message, then ignore it. + rtc::scoped_ptr stun_msg(new IceMessage()); + rtc::ByteBuffer buf(data, size); + if (!stun_msg->Read(&buf) || (buf.Length() > 0)) { + return false; + } + + if (stun_msg->type() == STUN_BINDING_REQUEST) { + // Check for the presence of USERNAME and MESSAGE-INTEGRITY (if ICE) first. + // If not present, fail with a 400 Bad Request. + if (!stun_msg->GetByteString(STUN_ATTR_USERNAME) || + (IsStandardIce() && + !stun_msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY))) { + LOG_J(LS_ERROR, this) << "Received STUN request without username/M-I " + << "from " << addr.ToSensitiveString(); + SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_BAD_REQUEST, + STUN_ERROR_REASON_BAD_REQUEST); + return true; + } + + // If the username is bad or unknown, fail with a 401 Unauthorized. + std::string local_ufrag; + std::string remote_ufrag; + IceProtocolType remote_protocol_type; + if (!ParseStunUsername(stun_msg.get(), &local_ufrag, &remote_ufrag, + &remote_protocol_type) || + local_ufrag != username_fragment()) { + LOG_J(LS_ERROR, this) << "Received STUN request with bad local username " + << local_ufrag << " from " + << addr.ToSensitiveString(); + SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_UNAUTHORIZED, + STUN_ERROR_REASON_UNAUTHORIZED); + return true; + } + + // Port is initialized to GOOGLE-ICE protocol type. If pings from remote + // are received before the signal message, protocol type may be different. + // Based on the STUN username, we can determine what's the remote protocol. + // This also enables us to send the response back using the same protocol + // as the request. + if (IsHybridIce()) { + SetIceProtocolType(remote_protocol_type); + } + + // If ICE, and the MESSAGE-INTEGRITY is bad, fail with a 401 Unauthorized + if (IsStandardIce() && + !stun_msg->ValidateMessageIntegrity(data, size, password_)) { + LOG_J(LS_ERROR, this) << "Received STUN request with bad M-I " + << "from " << addr.ToSensitiveString(); + SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_UNAUTHORIZED, + STUN_ERROR_REASON_UNAUTHORIZED); + return true; + } + out_username->assign(remote_ufrag); + } else if ((stun_msg->type() == STUN_BINDING_RESPONSE) || + (stun_msg->type() == STUN_BINDING_ERROR_RESPONSE)) { + if (stun_msg->type() == STUN_BINDING_ERROR_RESPONSE) { + if (const StunErrorCodeAttribute* error_code = stun_msg->GetErrorCode()) { + LOG_J(LS_ERROR, this) << "Received STUN binding error:" + << " class=" << error_code->eclass() + << " number=" << error_code->number() + << " reason='" << error_code->reason() << "'" + << " from " << addr.ToSensitiveString(); + // Return message to allow error-specific processing + } else { + LOG_J(LS_ERROR, this) << "Received STUN binding error without a error " + << "code from " << addr.ToSensitiveString(); + return true; + } + } + // NOTE: Username should not be used in verifying response messages. + out_username->clear(); + } else if (stun_msg->type() == STUN_BINDING_INDICATION) { + LOG_J(LS_VERBOSE, this) << "Received STUN binding indication:" + << " from " << addr.ToSensitiveString(); + out_username->clear(); + // No stun attributes will be verified, if it's stun indication message. + // Returning from end of the this method. + } else { + LOG_J(LS_ERROR, this) << "Received STUN packet with invalid type (" + << stun_msg->type() << ") from " + << addr.ToSensitiveString(); + return true; + } + + // Return the STUN message found. + *out_msg = stun_msg.release(); + return true; +} + +bool Port::IsCompatibleAddress(const rtc::SocketAddress& addr) { + int family = ip().family(); + // We use single-stack sockets, so families must match. + if (addr.family() != family) { + return false; + } + // Link-local IPv6 ports can only connect to other link-local IPv6 ports. + if (family == AF_INET6 && (IPIsPrivate(ip()) != IPIsPrivate(addr.ipaddr()))) { + return false; + } + return true; +} + +bool Port::ParseStunUsername(const StunMessage* stun_msg, + std::string* local_ufrag, + std::string* remote_ufrag, + IceProtocolType* remote_protocol_type) const { + // 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. + local_ufrag->clear(); + remote_ufrag->clear(); + const StunByteStringAttribute* username_attr = + stun_msg->GetByteString(STUN_ATTR_USERNAME); + if (username_attr == NULL) + return false; + + const std::string username_attr_str = username_attr->GetString(); + size_t colon_pos = username_attr_str.find(":"); + // If we are in hybrid mode set the appropriate ice protocol type based on + // the username argument style. + if (IsHybridIce()) { + *remote_protocol_type = (colon_pos != std::string::npos) ? + ICEPROTO_RFC5245 : ICEPROTO_GOOGLE; + } else { + *remote_protocol_type = ice_protocol_; + } + if (*remote_protocol_type == ICEPROTO_RFC5245) { + if (colon_pos != std::string::npos) { // RFRAG:LFRAG + *local_ufrag = username_attr_str.substr(0, colon_pos); + *remote_ufrag = username_attr_str.substr( + colon_pos + 1, username_attr_str.size()); + } else { + return false; + } + } else if (*remote_protocol_type == ICEPROTO_GOOGLE) { + int remote_frag_len = static_cast(username_attr_str.size()); + remote_frag_len -= static_cast(username_fragment().size()); + if (remote_frag_len < 0) + return false; + + *local_ufrag = username_attr_str.substr(0, username_fragment().size()); + *remote_ufrag = username_attr_str.substr( + username_fragment().size(), username_attr_str.size()); + } + return true; +} + +bool Port::MaybeIceRoleConflict( + const rtc::SocketAddress& addr, IceMessage* stun_msg, + const std::string& remote_ufrag) { + // Validate ICE_CONTROLLING or ICE_CONTROLLED attributes. + bool ret = true; + IceRole remote_ice_role = ICEROLE_UNKNOWN; + uint64 remote_tiebreaker = 0; + const StunUInt64Attribute* stun_attr = + stun_msg->GetUInt64(STUN_ATTR_ICE_CONTROLLING); + if (stun_attr) { + remote_ice_role = ICEROLE_CONTROLLING; + remote_tiebreaker = stun_attr->value(); + } + + // If |remote_ufrag| is same as port local username fragment and + // tie breaker value received in the ping message matches port + // tiebreaker value this must be a loopback call. + // We will treat this as valid scenario. + if (remote_ice_role == ICEROLE_CONTROLLING && + username_fragment() == remote_ufrag && + remote_tiebreaker == IceTiebreaker()) { + return true; + } + + stun_attr = stun_msg->GetUInt64(STUN_ATTR_ICE_CONTROLLED); + if (stun_attr) { + remote_ice_role = ICEROLE_CONTROLLED; + remote_tiebreaker = stun_attr->value(); + } + + switch (ice_role_) { + case ICEROLE_CONTROLLING: + if (ICEROLE_CONTROLLING == remote_ice_role) { + if (remote_tiebreaker >= tiebreaker_) { + SignalRoleConflict(this); + } else { + // Send Role Conflict (487) error response. + SendBindingErrorResponse(stun_msg, addr, + STUN_ERROR_ROLE_CONFLICT, STUN_ERROR_REASON_ROLE_CONFLICT); + ret = false; + } + } + break; + case ICEROLE_CONTROLLED: + if (ICEROLE_CONTROLLED == remote_ice_role) { + if (remote_tiebreaker < tiebreaker_) { + SignalRoleConflict(this); + } else { + // Send Role Conflict (487) error response. + SendBindingErrorResponse(stun_msg, addr, + STUN_ERROR_ROLE_CONFLICT, STUN_ERROR_REASON_ROLE_CONFLICT); + ret = false; + } + } + break; + default: + ASSERT(false); + } + return ret; +} + +void Port::CreateStunUsername(const std::string& remote_username, + std::string* stun_username_attr_str) const { + stun_username_attr_str->clear(); + *stun_username_attr_str = remote_username; + if (IsStandardIce()) { + // Connectivity checks from L->R will have username RFRAG:LFRAG. + stun_username_attr_str->append(":"); + } + stun_username_attr_str->append(username_fragment()); +} + +void Port::SendBindingResponse(StunMessage* request, + const rtc::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 != NULL); + if (username_attr == NULL) { + // No valid username, skip the response. + return; + } + + // Fill in the response message. + StunMessage response; + response.SetType(STUN_BINDING_RESPONSE); + response.SetTransactionID(request->transaction_id()); + const StunUInt32Attribute* retransmit_attr = + request->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT); + if (retransmit_attr) { + // Inherit the incoming retransmit value in the response so the other side + // can see our view of lost pings. + response.AddAttribute(new StunUInt32Attribute( + STUN_ATTR_RETRANSMIT_COUNT, retransmit_attr->value())); + + if (retransmit_attr->value() > CONNECTION_WRITE_CONNECT_FAILURES) { + LOG_J(LS_INFO, this) + << "Received a remote ping with high retransmit count: " + << retransmit_attr->value(); + } + } + + // Only GICE messages have USERNAME and MAPPED-ADDRESS in the response. + // ICE messages use XOR-MAPPED-ADDRESS, and add MESSAGE-INTEGRITY. + if (IsStandardIce()) { + response.AddAttribute( + new StunXorAddressAttribute(STUN_ATTR_XOR_MAPPED_ADDRESS, addr)); + response.AddMessageIntegrity(password_); + response.AddFingerprint(); + } else if (IsGoogleIce()) { + response.AddAttribute( + new StunAddressAttribute(STUN_ATTR_MAPPED_ADDRESS, addr)); + response.AddAttribute(new StunByteStringAttribute( + STUN_ATTR_USERNAME, username_attr->GetString())); + } + + // Send the response message. + rtc::ByteBuffer buf; + response.Write(&buf); + rtc::PacketOptions options(DefaultDscpValue()); + if (SendTo(buf.Data(), buf.Length(), addr, options, false) < 0) { + LOG_J(LS_ERROR, this) << "Failed to send STUN ping response to " + << addr.ToSensitiveString(); + } + + // 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 != NULL); + if (conn) + conn->ReceivedPing(); +} + +void Port::SendBindingErrorResponse(StunMessage* request, + const rtc::SocketAddress& addr, + int error_code, const std::string& reason) { + ASSERT(request->type() == STUN_BINDING_REQUEST); + + // Fill in the response message. + StunMessage response; + response.SetType(STUN_BINDING_ERROR_RESPONSE); + response.SetTransactionID(request->transaction_id()); + + // When doing GICE, we need to write out the error code incorrectly to + // maintain backwards compatiblility. + StunErrorCodeAttribute* error_attr = StunAttribute::CreateErrorCode(); + if (IsStandardIce()) { + error_attr->SetCode(error_code); + } else if (IsGoogleIce()) { + error_attr->SetClass(error_code / 256); + error_attr->SetNumber(error_code % 256); + } + error_attr->SetReason(reason); + response.AddAttribute(error_attr); + + if (IsStandardIce()) { + // Per Section 10.1.2, certain error cases don't get a MESSAGE-INTEGRITY, + // because we don't have enough information to determine the shared secret. + if (error_code != STUN_ERROR_BAD_REQUEST && + error_code != STUN_ERROR_UNAUTHORIZED) + response.AddMessageIntegrity(password_); + response.AddFingerprint(); + } else if (IsGoogleIce()) { + // GICE responses include a username, if one exists. + const StunByteStringAttribute* username_attr = + request->GetByteString(STUN_ATTR_USERNAME); + if (username_attr) + response.AddAttribute(new StunByteStringAttribute( + STUN_ATTR_USERNAME, username_attr->GetString())); + } + + // Send the response message. + rtc::ByteBuffer buf; + response.Write(&buf); + rtc::PacketOptions options(DefaultDscpValue()); + SendTo(buf.Data(), buf.Length(), addr, options, false); + LOG_J(LS_INFO, this) << "Sending STUN binding error: reason=" << reason + << " to " << addr.ToSensitiveString(); +} + +void Port::OnMessage(rtc::Message *pmsg) { + ASSERT(pmsg->message_id == MSG_CHECKTIMEOUT); + CheckTimeout(); +} + +std::string Port::ToString() const { + std::stringstream ss; + ss << "Port[" << content_name_ << ":" << component_ + << ":" << generation_ << ":" << type_ + << ":" << network_->ToString() << "]"; + return ss.str(); +} + +void Port::EnablePortPackets() { + enable_port_packets_ = true; +} + +void Port::OnConnectionDestroyed(Connection* conn) { + AddressMap::iterator iter = + connections_.find(conn->remote_candidate().address()); + ASSERT(iter != connections_.end()); + connections_.erase(iter); + + // On the controlled side, ports time out, but only after all connections + // fail. Note: If a new connection is added after this message is posted, + // but it fails and is removed before kPortTimeoutDelay, then this message + // will still cause the Port to be destroyed. + if (ice_role_ == ICEROLE_CONTROLLED) + thread_->PostDelayed(timeout_delay_, this, MSG_CHECKTIMEOUT); +} + +void Port::Destroy() { + ASSERT(connections_.empty()); + LOG_J(LS_INFO, this) << "Port deleted"; + SignalDestroyed(this); + delete this; +} + +void Port::CheckTimeout() { + ASSERT(ice_role_ == ICEROLE_CONTROLLED); + // 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 (connections_.empty()) + Destroy(); +} + +const std::string Port::username_fragment() const { + if (!IsStandardIce() && + component_ == ICE_CANDIDATE_COMPONENT_RTCP) { + // In GICE mode, we should adjust username fragment for rtcp component. + return GetRtcpUfragFromRtpUfrag(ice_username_fragment_); + } else { + return ice_username_fragment_; + } +} + +// A ConnectionRequest is a simple STUN ping used to determine writability. +class ConnectionRequest : public StunRequest { + public: + explicit ConnectionRequest(Connection* connection) + : StunRequest(new IceMessage()), + connection_(connection) { + } + + virtual ~ConnectionRequest() { + } + + virtual void Prepare(StunMessage* request) { + request->SetType(STUN_BINDING_REQUEST); + std::string username; + connection_->port()->CreateStunUsername( + connection_->remote_candidate().username(), &username); + request->AddAttribute( + new StunByteStringAttribute(STUN_ATTR_USERNAME, username)); + + // connection_ already holds this ping, so subtract one from count. + if (connection_->port()->send_retransmit_count_attribute()) { + request->AddAttribute(new StunUInt32Attribute( + STUN_ATTR_RETRANSMIT_COUNT, + static_cast( + connection_->pings_since_last_response_.size() - 1))); + } + + // Adding ICE-specific attributes to the STUN request message. + if (connection_->port()->IsStandardIce()) { + // Adding ICE_CONTROLLED or ICE_CONTROLLING attribute based on the role. + if (connection_->port()->GetIceRole() == ICEROLE_CONTROLLING) { + request->AddAttribute(new StunUInt64Attribute( + STUN_ATTR_ICE_CONTROLLING, connection_->port()->IceTiebreaker())); + // Since we are trying aggressive nomination, sending USE-CANDIDATE + // attribute in every ping. + // If we are dealing with a ice-lite end point, nomination flag + // in Connection will be set to false by default. Once the connection + // becomes "best connection", nomination flag will be turned on. + if (connection_->use_candidate_attr()) { + request->AddAttribute(new StunByteStringAttribute( + STUN_ATTR_USE_CANDIDATE)); + } + } else if (connection_->port()->GetIceRole() == ICEROLE_CONTROLLED) { + request->AddAttribute(new StunUInt64Attribute( + STUN_ATTR_ICE_CONTROLLED, connection_->port()->IceTiebreaker())); + } else { + ASSERT(false); + } + + // Adding PRIORITY Attribute. + // Changing the type preference to Peer Reflexive and local preference + // and component id information is unchanged from the original priority. + // priority = (2^24)*(type preference) + + // (2^8)*(local preference) + + // (2^0)*(256 - component ID) + uint32 prflx_priority = ICE_TYPE_PREFERENCE_PRFLX << 24 | + (connection_->local_candidate().priority() & 0x00FFFFFF); + request->AddAttribute( + new StunUInt32Attribute(STUN_ATTR_PRIORITY, prflx_priority)); + + // Adding Message Integrity attribute. + request->AddMessageIntegrity(connection_->remote_candidate().password()); + // Adding Fingerprint. + request->AddFingerprint(); + } + } + + virtual void OnResponse(StunMessage* response) { + connection_->OnConnectionRequestResponse(this, response); + } + + virtual void OnErrorResponse(StunMessage* response) { + connection_->OnConnectionRequestErrorResponse(this, response); + } + + virtual void OnTimeout() { + connection_->OnConnectionRequestTimeout(this); + } + + 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) + : port_(port), local_candidate_index_(index), + remote_candidate_(remote_candidate), read_state_(STATE_READ_INIT), + write_state_(STATE_WRITE_INIT), connected_(true), pruned_(false), + use_candidate_attr_(false), remote_ice_mode_(ICEMODE_FULL), + requests_(port->thread()), rtt_(DEFAULT_RTT), last_ping_sent_(0), + last_ping_received_(0), last_data_received_(0), + last_ping_response_received_(0), reported_(false), state_(STATE_WAITING) { + // All of our connections start in WAITING state. + // TODO(mallinath) - Start connections from STATE_FROZEN. + // Wire up to send stun packets + requests_.SignalSendPacket.connect(this, &Connection::OnSendStunPacket); + LOG_J(LS_INFO, this) << "Connection created"; +} + +Connection::~Connection() { +} + +const Candidate& Connection::local_candidate() const { + ASSERT(local_candidate_index_ < port_->Candidates().size()); + return port_->Candidates()[local_candidate_index_]; +} + +uint64 Connection::priority() const { + uint64 priority = 0; + // RFC 5245 - 5.7.2. Computing Pair Priority and Ordering Pairs + // Let G be the priority for the candidate provided by the controlling + // agent. Let D be the priority for the candidate provided by the + // controlled agent. + // pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0) + IceRole role = port_->GetIceRole(); + if (role != ICEROLE_UNKNOWN) { + uint32 g = 0; + uint32 d = 0; + if (role == ICEROLE_CONTROLLING) { + g = local_candidate().priority(); + d = remote_candidate_.priority(); + } else { + g = remote_candidate_.priority(); + d = local_candidate().priority(); + } + priority = rtc::_min(g, d); + priority = priority << 32; + priority += 2 * rtc::_max(g, d) + (g > d ? 1 : 0); + } + return priority; +} + +void Connection::set_read_state(ReadState value) { + ReadState old_value = read_state_; + read_state_ = value; + if (value != old_value) { + LOG_J(LS_VERBOSE, this) << "set_read_state"; + SignalStateChange(this); + CheckTimeout(); + } +} + +void Connection::set_write_state(WriteState value) { + WriteState old_value = write_state_; + write_state_ = value; + if (value != old_value) { + LOG_J(LS_VERBOSE, this) << "set_write_state"; + SignalStateChange(this); + CheckTimeout(); + } +} + +void Connection::set_state(State state) { + State old_state = state_; + state_ = state; + if (state != old_state) { + LOG_J(LS_VERBOSE, this) << "set_state"; + } +} + +void Connection::set_connected(bool value) { + bool old_value = connected_; + connected_ = value; + if (value != old_value) { + LOG_J(LS_VERBOSE, this) << "set_connected"; + } +} + +void Connection::set_use_candidate_attr(bool enable) { + use_candidate_attr_ = enable; +} + +void Connection::OnSendStunPacket(const void* data, size_t size, + StunRequest* req) { + rtc::PacketOptions options(port_->DefaultDscpValue()); + if (port_->SendTo(data, size, remote_candidate_.address(), + options, false) < 0) { + LOG_J(LS_WARNING, this) << "Failed to send STUN ping " << req->id(); + } +} + +void Connection::OnReadPacket( + const char* data, size_t size, const rtc::PacketTime& packet_time) { + rtc::scoped_ptr msg; + std::string remote_ufrag; + const rtc::SocketAddress& addr(remote_candidate_.address()); + if (!port_->GetStunMessage(data, size, addr, msg.accept(), &remote_ufrag)) { + // 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! + + last_data_received_ = rtc::Time(); + recv_rate_tracker_.Update(size); + SignalReadPacket(this, data, size, packet_time); + + // If timed out sending writability checks, start up again + if (!pruned_ && (write_state_ == STATE_WRITE_TIMEOUT)) { + LOG(LS_WARNING) << "Received a data packet on a timed-out Connection. " + << "Resetting state to STATE_WRITE_INIT."; + set_write_state(STATE_WRITE_INIT); + } + } else { + // Not readable means the remote address hasn't sent a valid + // binding request yet. + + LOG_J(LS_WARNING, this) + << "Received non-STUN packet from an unreadable connection."; + } + } else if (!msg) { + // The packet was STUN, but failed a check and was handled internally. + } else { + // The packet is STUN and passed the Port checks. + // Perform our own checks to ensure this packet is valid. + // 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: + if (remote_ufrag == remote_candidate_.username()) { + // Check for role conflicts. + if (port_->IsStandardIce() && + !port_->MaybeIceRoleConflict(addr, msg.get(), remote_ufrag)) { + // Received conflicting role from the peer. + LOG(LS_INFO) << "Received conflicting role from the peer."; + return; + } + + // Incoming, validated stun request from remote peer. + // This call will also set the connection readable. + port_->SendBindingResponse(msg.get(), addr); + + // If timed out sending writability checks, start up again + if (!pruned_ && (write_state_ == STATE_WRITE_TIMEOUT)) + set_write_state(STATE_WRITE_INIT); + + if ((port_->IsStandardIce()) && + (port_->GetIceRole() == ICEROLE_CONTROLLED)) { + const StunByteStringAttribute* use_candidate_attr = + msg->GetByteString(STUN_ATTR_USE_CANDIDATE); + if (use_candidate_attr) + SignalUseCandidate(this); + } + } else { + // The packet had the right local username, but the remote username + // was not the right one for the remote address. + LOG_J(LS_ERROR, this) + << "Received STUN request with bad remote username " + << remote_ufrag; + port_->SendBindingErrorResponse(msg.get(), addr, + STUN_ERROR_UNAUTHORIZED, + STUN_ERROR_REASON_UNAUTHORIZED); + + } + break; + + // Response from remote peer. Does it match request sent? + // This doesn't just check, it makes callbacks if transaction + // id's match. + case STUN_BINDING_RESPONSE: + case STUN_BINDING_ERROR_RESPONSE: + if (port_->IsGoogleIce() || + msg->ValidateMessageIntegrity( + data, size, remote_candidate().password())) { + requests_.CheckResponse(msg.get()); + } + // Otherwise silently discard the response message. + break; + + // Remote end point sent an STUN indication instead of regular + // binding request. In this case |last_ping_received_| will be updated. + // Otherwise we can mark connection to read timeout. No response will be + // sent in this scenario. + case STUN_BINDING_INDICATION: + if (port_->IsStandardIce() && read_state_ == STATE_READABLE) { + ReceivedPing(); + } else { + LOG_J(LS_WARNING, this) << "Received STUN binding indication " + << "from an unreadable connection."; + } + break; + + default: + ASSERT(false); + break; + } + } +} + +void Connection::OnReadyToSend() { + if (write_state_ == STATE_WRITABLE) { + SignalReadyToSend(this); + } +} + +void Connection::Prune() { + if (!pruned_) { + LOG_J(LS_VERBOSE, this) << "Connection pruned"; + pruned_ = true; + requests_.Clear(); + set_write_state(STATE_WRITE_TIMEOUT); + } +} + +void Connection::Destroy() { + LOG_J(LS_VERBOSE, this) << "Connection destroyed"; + set_read_state(STATE_READ_TIMEOUT); + set_write_state(STATE_WRITE_TIMEOUT); +} + +void Connection::UpdateState(uint32 now) { + uint32 rtt = ConservativeRTTEstimate(rtt_); + + std::string pings; + for (size_t i = 0; i < pings_since_last_response_.size(); ++i) { + char buf[32]; + rtc::sprintfn(buf, sizeof(buf), "%u", + pings_since_last_response_[i]); + pings.append(buf).append(" "); + } + LOG_J(LS_VERBOSE, this) << "UpdateState(): pings_since_last_response_=" << + pings << ", rtt=" << rtt << ", now=" << 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 other side has not sent ping after connection has become readable, use + // |last_data_received_| as the indication. + // If remote endpoint is doing RFC 5245, it's not required to send ping + // after connection is established. If this connection is serving a data + // channel, it may not be in a position to send media continuously. Do not + // mark connection timeout if it's in RFC5245 mode. + // Below check will be performed with end point if it's doing google-ice. + if (port_->IsGoogleIce() && (read_state_ == STATE_READABLE) && + (last_ping_received_ + CONNECTION_READ_TIMEOUT <= now) && + (last_data_received_ + CONNECTION_READ_TIMEOUT <= now)) { + LOG_J(LS_INFO, this) << "Unreadable after " + << now - last_ping_received_ + << " ms without a ping," + << " ms since last received response=" + << now - last_ping_response_received_ + << " ms since last received data=" + << now - last_data_received_ + << " rtt=" << rtt; + 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. + + 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)) { + uint32 max_pings = CONNECTION_WRITE_CONNECT_FAILURES; + LOG_J(LS_INFO, this) << "Unwritable after " << max_pings + << " ping failures and " + << now - pings_since_last_response_[0] + << " ms without a response," + << " ms since last received ping=" + << now - last_ping_received_ + << " ms since last received data=" + << now - last_data_received_ + << " rtt=" << rtt; + set_write_state(STATE_WRITE_UNRELIABLE); + } + + if ((write_state_ == STATE_WRITE_UNRELIABLE || + write_state_ == STATE_WRITE_INIT) && + TooLongWithoutResponse(pings_since_last_response_, + CONNECTION_WRITE_TIMEOUT, + now)) { + LOG_J(LS_INFO, this) << "Timed out after " + << now - pings_since_last_response_[0] + << " ms without a response, rtt=" << rtt; + set_write_state(STATE_WRITE_TIMEOUT); + } +} + +void Connection::Ping(uint32 now) { + ASSERT(connected_); + last_ping_sent_ = now; + pings_since_last_response_.push_back(now); + ConnectionRequest *req = new ConnectionRequest(this); + LOG_J(LS_VERBOSE, this) << "Sending STUN ping " << req->id() << " at " << now; + requests_.Send(req); + state_ = STATE_INPROGRESS; +} + +void Connection::ReceivedPing() { + last_ping_received_ = rtc::Time(); + set_read_state(STATE_READABLE); +} + +std::string Connection::ToString() const { + const char CONNECT_STATE_ABBREV[2] = { + '-', // not connected (false) + 'C', // connected (true) + }; + const char READ_STATE_ABBREV[3] = { + '-', // STATE_READ_INIT + 'R', // STATE_READABLE + 'x', // STATE_READ_TIMEOUT + }; + const char WRITE_STATE_ABBREV[4] = { + 'W', // STATE_WRITABLE + 'w', // STATE_WRITE_UNRELIABLE + '-', // STATE_WRITE_INIT + 'x', // STATE_WRITE_TIMEOUT + }; + const std::string ICESTATE[4] = { + "W", // STATE_WAITING + "I", // STATE_INPROGRESS + "S", // STATE_SUCCEEDED + "F" // STATE_FAILED + }; + const Candidate& local = local_candidate(); + const Candidate& remote = remote_candidate(); + std::stringstream ss; + ss << "Conn[" << port_->content_name() + << ":" << local.id() << ":" << local.component() + << ":" << local.generation() + << ":" << local.type() << ":" << local.protocol() + << ":" << local.address().ToSensitiveString() + << "->" << remote.id() << ":" << remote.component() + << ":" << remote.priority() + << ":" << remote.type() << ":" + << remote.protocol() << ":" << remote.address().ToSensitiveString() << "|" + << CONNECT_STATE_ABBREV[connected()] + << READ_STATE_ABBREV[read_state()] + << WRITE_STATE_ABBREV[write_state()] + << ICESTATE[state()] << "|" + << priority() << "|"; + if (rtt_ < DEFAULT_RTT) { + ss << rtt_ << "]"; + } else { + ss << "-]"; + } + return ss.str(); +} + +std::string Connection::ToSensitiveString() const { + return ToString(); +} + +void Connection::OnConnectionRequestResponse(ConnectionRequest* request, + StunMessage* response) { + // We've already validated that this is a STUN binding response with + // the correct local and remote username for this connection. + // So 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. + uint32 rtt = request->Elapsed(); + set_write_state(STATE_WRITABLE); + set_state(STATE_SUCCEEDED); + + if (remote_ice_mode_ == ICEMODE_LITE) { + // A ice-lite end point never initiates ping requests. This will allow + // us to move to STATE_READABLE. + ReceivedPing(); + } + + std::string pings; + for (size_t i = 0; i < pings_since_last_response_.size(); ++i) { + char buf[32]; + rtc::sprintfn(buf, sizeof(buf), "%u", + pings_since_last_response_[i]); + pings.append(buf).append(" "); + } + + rtc::LoggingSeverity level = + (pings_since_last_response_.size() > CONNECTION_WRITE_CONNECT_FAILURES) ? + rtc::LS_INFO : rtc::LS_VERBOSE; + + LOG_JV(level, this) << "Received STUN ping response " << request->id() + << ", pings_since_last_response_=" << pings + << ", rtt=" << rtt; + + pings_since_last_response_.clear(); + last_ping_response_received_ = rtc::Time(); + rtt_ = (RTT_RATIO * rtt_ + rtt) / (RTT_RATIO + 1); + + // Peer reflexive candidate is only for RFC 5245 ICE. + if (port_->IsStandardIce()) { + MaybeAddPrflxCandidate(request, response); + } +} + +void Connection::OnConnectionRequestErrorResponse(ConnectionRequest* request, + StunMessage* response) { + const StunErrorCodeAttribute* error_attr = response->GetErrorCode(); + int error_code = STUN_ERROR_GLOBAL_FAILURE; + if (error_attr) { + if (port_->IsGoogleIce()) { + // When doing GICE, the error code is written out incorrectly, so we need + // to unmunge it here. + error_code = error_attr->eclass() * 256 + error_attr->number(); + } else { + error_code = error_attr->code(); + } + } + + 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 if (error_code == STUN_ERROR_ROLE_CONFLICT) { + HandleRoleConflictFromPeer(); + } else { + // This is not a valid connection. + LOG_J(LS_ERROR, this) << "Received STUN error response, code=" + << error_code << "; killing connection"; + set_state(STATE_FAILED); + set_write_state(STATE_WRITE_TIMEOUT); + } +} + +void Connection::OnConnectionRequestTimeout(ConnectionRequest* request) { + // Log at LS_INFO if we miss a ping on a writable connection. + rtc::LoggingSeverity sev = (write_state_ == STATE_WRITABLE) ? + rtc::LS_INFO : rtc::LS_VERBOSE; + LOG_JV(sev, this) << "Timing-out STUN ping " << request->id() + << " after " << request->Elapsed() << " ms"; +} + +void Connection::CheckTimeout() { + // If both read and write have timed out or read has never initialized, 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 + // connection. + if ((read_state_ == STATE_READ_TIMEOUT || + read_state_ == STATE_READ_INIT) && + write_state_ == STATE_WRITE_TIMEOUT) { + port_->thread()->Post(this, MSG_DELETE); + } +} + +void Connection::HandleRoleConflictFromPeer() { + port_->SignalRoleConflict(port_); +} + +void Connection::OnMessage(rtc::Message *pmsg) { + ASSERT(pmsg->message_id == MSG_DELETE); + + LOG_J(LS_INFO, this) << "Connection deleted"; + SignalDestroyed(this); + delete this; +} + +size_t Connection::recv_bytes_second() { + return recv_rate_tracker_.units_second(); +} + +size_t Connection::recv_total_bytes() { + return recv_rate_tracker_.total_units(); +} + +size_t Connection::sent_bytes_second() { + return send_rate_tracker_.units_second(); +} + +size_t Connection::sent_total_bytes() { + return send_rate_tracker_.total_units(); +} + +void Connection::MaybeAddPrflxCandidate(ConnectionRequest* request, + StunMessage* response) { + // RFC 5245 + // The agent checks the mapped address from the STUN response. If the + // transport address does not match any of the local candidates that the + // agent knows about, the mapped address represents a new candidate -- a + // peer reflexive candidate. + const StunAddressAttribute* addr = + response->GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS); + if (!addr) { + LOG(LS_WARNING) << "Connection::OnConnectionRequestResponse - " + << "No MAPPED-ADDRESS or XOR-MAPPED-ADDRESS found in the " + << "stun response message"; + return; + } + + bool known_addr = false; + for (size_t i = 0; i < port_->Candidates().size(); ++i) { + if (port_->Candidates()[i].address() == addr->GetAddress()) { + known_addr = true; + break; + } + } + if (known_addr) { + return; + } + + // RFC 5245 + // Its priority is set equal to the value of the PRIORITY attribute + // in the Binding request. + const StunUInt32Attribute* priority_attr = + request->msg()->GetUInt32(STUN_ATTR_PRIORITY); + if (!priority_attr) { + LOG(LS_WARNING) << "Connection::OnConnectionRequestResponse - " + << "No STUN_ATTR_PRIORITY found in the " + << "stun response message"; + return; + } + const uint32 priority = priority_attr->value(); + std::string id = rtc::CreateRandomString(8); + + Candidate new_local_candidate; + new_local_candidate.set_id(id); + new_local_candidate.set_component(local_candidate().component()); + new_local_candidate.set_type(PRFLX_PORT_TYPE); + new_local_candidate.set_protocol(local_candidate().protocol()); + new_local_candidate.set_address(addr->GetAddress()); + new_local_candidate.set_priority(priority); + new_local_candidate.set_username(local_candidate().username()); + new_local_candidate.set_password(local_candidate().password()); + new_local_candidate.set_network_name(local_candidate().network_name()); + new_local_candidate.set_related_address(local_candidate().address()); + new_local_candidate.set_foundation( + ComputeFoundation(PRFLX_PORT_TYPE, local_candidate().protocol(), + local_candidate().address())); + + // Change the local candidate of this Connection to the new prflx candidate. + local_candidate_index_ = port_->AddPrflxCandidate(new_local_candidate); + + // SignalStateChange to force a re-sort in P2PTransportChannel as this + // Connection's local candidate has changed. + SignalStateChange(this); +} + +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, + const rtc::PacketOptions& options) { + if (write_state_ == STATE_WRITE_INIT || write_state_ == STATE_WRITE_TIMEOUT) { + error_ = EWOULDBLOCK; + return SOCKET_ERROR; + } + int sent = port_->SendTo(data, size, remote_candidate_.address(), + options, true); + if (sent <= 0) { + ASSERT(sent < 0); + error_ = port_->GetError(); + } else { + send_rate_tracker_.Update(sent); + } + return sent; +} + +} // namespace cricket diff --git a/webrtc/p2p/base/port.h b/webrtc/p2p/base/port.h new file mode 100644 index 000000000..48b85302b --- /dev/null +++ b/webrtc/p2p/base/port.h @@ -0,0 +1,602 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_PORT_H_ +#define WEBRTC_P2P_BASE_PORT_H_ + +#include +#include +#include +#include + +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/packetsocketfactory.h" +#include "webrtc/p2p/base/portinterface.h" +#include "webrtc/p2p/base/stun.h" +#include "webrtc/p2p/base/stunrequest.h" +#include "webrtc/p2p/base/transport.h" +#include "webrtc/base/asyncpacketsocket.h" +#include "webrtc/base/network.h" +#include "webrtc/base/proxyinfo.h" +#include "webrtc/base/ratetracker.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/thread.h" + +namespace cricket { + +class Connection; +class ConnectionRequest; + +extern const char LOCAL_PORT_TYPE[]; +extern const char STUN_PORT_TYPE[]; +extern const char PRFLX_PORT_TYPE[]; +extern const char RELAY_PORT_TYPE[]; + +extern const char UDP_PROTOCOL_NAME[]; +extern const char TCP_PROTOCOL_NAME[]; +extern const char SSLTCP_PROTOCOL_NAME[]; + +// RFC 6544, TCP candidate encoding rules. +extern const int DISCARD_PORT; +extern const char TCPTYPE_ACTIVE_STR[]; +extern const char TCPTYPE_PASSIVE_STR[]; +extern const char TCPTYPE_SIMOPEN_STR[]; + +// 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 + +enum RelayType { + RELAY_GTURN, // Legacy google relay service. + RELAY_TURN // Standard (TURN) relay service. +}; + +enum IcePriorityValue { + // The reason we are choosing Relay preference 2 is because, we can run + // Relay from client to server on UDP/TCP/TLS. To distinguish the transport + // protocol, we prefer UDP over TCP over TLS. + // For UDP ICE_TYPE_PREFERENCE_RELAY will be 2. + // For TCP ICE_TYPE_PREFERENCE_RELAY will be 1. + // For TLS ICE_TYPE_PREFERENCE_RELAY will be 0. + // Check turnport.cc for setting these values. + ICE_TYPE_PREFERENCE_RELAY = 2, + ICE_TYPE_PREFERENCE_HOST_TCP = 90, + ICE_TYPE_PREFERENCE_SRFLX = 100, + ICE_TYPE_PREFERENCE_PRFLX = 110, + ICE_TYPE_PREFERENCE_HOST = 126 +}; + +const char* ProtoToString(ProtocolType proto); +bool StringToProto(const char* value, ProtocolType* proto); + +struct ProtocolAddress { + rtc::SocketAddress address; + ProtocolType proto; + bool secure; + + ProtocolAddress(const rtc::SocketAddress& a, ProtocolType p) + : address(a), proto(p), secure(false) { } + ProtocolAddress(const rtc::SocketAddress& a, ProtocolType p, bool sec) + : address(a), proto(p), secure(sec) { } +}; + +typedef std::set ServerAddresses; + +// 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 PortInterface, public rtc::MessageHandler, + public sigslot::has_slots<> { + public: + Port(rtc::Thread* thread, rtc::PacketSocketFactory* factory, + rtc::Network* network, const rtc::IPAddress& ip, + const std::string& username_fragment, const std::string& password); + Port(rtc::Thread* thread, const std::string& type, + rtc::PacketSocketFactory* factory, + rtc::Network* network, const rtc::IPAddress& ip, + int min_port, int max_port, const std::string& username_fragment, + const std::string& password); + virtual ~Port(); + + virtual const std::string& Type() const { return type_; } + virtual rtc::Network* Network() const { return network_; } + + // This method will set the flag which enables standard ICE/STUN procedures + // in STUN connectivity checks. Currently this method does + // 1. Add / Verify MI attribute in STUN binding requests. + // 2. Username attribute in STUN binding request will be RFRAF:LFRAG, + // as opposed to RFRAGLFRAG. + virtual void SetIceProtocolType(IceProtocolType protocol) { + ice_protocol_ = protocol; + } + virtual IceProtocolType IceProtocol() const { return ice_protocol_; } + + // Methods to set/get ICE role and tiebreaker values. + IceRole GetIceRole() const { return ice_role_; } + void SetIceRole(IceRole role) { ice_role_ = role; } + + void SetIceTiebreaker(uint64 tiebreaker) { tiebreaker_ = tiebreaker; } + uint64 IceTiebreaker() const { return tiebreaker_; } + + virtual bool SharedSocket() const { return shared_socket_; } + void ResetSharedSocket() { shared_socket_ = false; } + + // The thread on which this port performs its I/O. + rtc::Thread* thread() { return thread_; } + + // The factory used to create the sockets of this port. + rtc::PacketSocketFactory* socket_factory() const { return factory_; } + void set_socket_factory(rtc::PacketSocketFactory* factory) { + factory_ = factory; + } + + // For debugging purposes. + const std::string& content_name() const { return content_name_; } + void set_content_name(const std::string& content_name) { + content_name_ = content_name; + } + + int component() const { return component_; } + void set_component(int component) { component_ = component; } + + bool send_retransmit_count_attribute() const { + return send_retransmit_count_attribute_; + } + void set_send_retransmit_count_attribute(bool enable) { + send_retransmit_count_attribute_ = enable; + } + + // Identifies the generation that this port was created in. + uint32 generation() { return generation_; } + void set_generation(uint32 generation) { generation_ = generation; } + + // ICE requires a single username/password per content/media line. So the + // |ice_username_fragment_| of the ports that belongs to the same content will + // be the same. However this causes a small complication with our relay + // server, which expects different username for RTP and RTCP. + // + // To resolve this problem, we implemented the username_fragment(), + // which returns a different username (calculated from + // |ice_username_fragment_|) for RTCP in the case of ICEPROTO_GOOGLE. And the + // username_fragment() simply returns |ice_username_fragment_| when running + // in ICEPROTO_RFC5245. + // + // As a result the ICEPROTO_GOOGLE will use different usernames for RTP and + // RTCP. And the ICEPROTO_RFC5245 will use same username for both RTP and + // RTCP. + const std::string username_fragment() const; + const std::string& password() const { return password_; } + + // Fired when candidates are discovered by the port. When all candidates + // are discovered that belong to port SignalAddressReady is fired. + sigslot::signal2 SignalCandidateReady; + + // Provides all of the above information in one handy object. + virtual const std::vector& Candidates() const { + return candidates_; + } + + // SignalPortComplete is sent when port completes the task of candidates + // allocation. + sigslot::signal1 SignalPortComplete; + // This signal sent when port fails to allocate candidates and this port + // can't be used in establishing the connections. When port is in shared mode + // and port fails to allocate one of the candidates, port shouldn't send + // this signal as other candidates might be usefull in establishing the + // connection. + sigslot::signal1 SignalPortError; + + // 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. + virtual Connection* GetConnection( + const rtc::SocketAddress& remote_addr); + + // Called each time a connection is created. + sigslot::signal2 SignalConnectionCreated; + + // In a shared socket mode each port which shares the socket will decide + // to accept the packet based on the |remote_addr|. Currently only UDP + // port implemented this method. + // TODO(mallinath) - Make it pure virtual. + virtual bool HandleIncomingPacket( + rtc::AsyncPacketSocket* socket, const char* data, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time) { + ASSERT(false); + return false; + } + + // 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. + virtual void SendBindingResponse(StunMessage* request, + const rtc::SocketAddress& addr); + virtual void SendBindingErrorResponse( + StunMessage* request, const rtc::SocketAddress& addr, + int error_code, const std::string& reason); + + void set_proxy(const std::string& user_agent, + const rtc::ProxyInfo& proxy) { + user_agent_ = user_agent; + proxy_ = proxy; + } + const std::string& user_agent() { return user_agent_; } + const rtc::ProxyInfo& proxy() { return proxy_; } + + virtual void EnablePortPackets(); + + // Called if the port has no connections and is no longer useful. + void Destroy(); + + virtual void OnMessage(rtc::Message *pmsg); + + // Debugging description of this port + virtual std::string ToString() const; + rtc::IPAddress& ip() { return ip_; } + int min_port() { return min_port_; } + int max_port() { return max_port_; } + + // Timeout shortening function to speed up unit tests. + void set_timeout_delay(int delay) { timeout_delay_ = delay; } + + // This method will return local and remote username fragements from the + // stun username attribute if present. + bool ParseStunUsername(const StunMessage* stun_msg, + std::string* local_username, + std::string* remote_username, + IceProtocolType* remote_protocol_type) const; + void CreateStunUsername(const std::string& remote_username, + std::string* stun_username_attr_str) const; + + bool MaybeIceRoleConflict(const rtc::SocketAddress& addr, + IceMessage* stun_msg, + const std::string& remote_ufrag); + + // Called when the socket is currently able to send. + void OnReadyToSend(); + + // Called when the Connection discovers a local peer reflexive candidate. + // Returns the index of the new local candidate. + size_t AddPrflxCandidate(const Candidate& local); + + // Returns if RFC 5245 ICE protocol is used. + bool IsStandardIce() const; + + // Returns if Google ICE protocol is used. + bool IsGoogleIce() const; + + // Returns if Hybrid ICE protocol is used. + bool IsHybridIce() const; + + void set_candidate_filter(uint32 candidate_filter) { + candidate_filter_ = candidate_filter; + } + + protected: + enum { + MSG_CHECKTIMEOUT = 0, + MSG_FIRST_AVAILABLE + }; + + void set_type(const std::string& type) { type_ = type; } + + void AddAddress(const rtc::SocketAddress& address, + const rtc::SocketAddress& base_address, + const rtc::SocketAddress& related_address, + const std::string& protocol, const std::string& tcptype, + const std::string& type, uint32 type_preference, + uint32 relay_preference, bool final); + + // 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 rtc::SocketAddress& addr, + ProtocolType proto); + + // 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 rtc::SocketAddress& addr, + IceMessage** out_msg, std::string* out_username); + + // Checks if the address in addr is compatible with the port's ip. + bool IsCompatibleAddress(const rtc::SocketAddress& addr); + + // Returns default DSCP value. + rtc::DiffServCodePoint DefaultDscpValue() const { + // No change from what MediaChannel set. + return rtc::DSCP_NO_CHANGE; + } + + uint32 candidate_filter() { return candidate_filter_; } + + private: + void Construct(); + // 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(); + + rtc::Thread* thread_; + rtc::PacketSocketFactory* factory_; + std::string type_; + bool send_retransmit_count_attribute_; + rtc::Network* network_; + rtc::IPAddress ip_; + int min_port_; + int max_port_; + std::string content_name_; + int component_; + uint32 generation_; + // 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_fragment and password. + // PortAllocatorSession will provide these username_fragment and password. + // + // Note: we should always use username_fragment() instead of using + // |ice_username_fragment_| directly. For the details see the comment on + // username_fragment(). + std::string ice_username_fragment_; + std::string password_; + std::vector candidates_; + AddressMap connections_; + int timeout_delay_; + bool enable_port_packets_; + IceProtocolType ice_protocol_; + IceRole ice_role_; + uint64 tiebreaker_; + bool shared_socket_; + // Information to use when going through a proxy. + std::string user_agent_; + rtc::ProxyInfo proxy_; + + // Candidate filter is pushed down to Port such that each Port could + // make its own decision on how to create candidates. For example, + // when IceTransportsType is set to relay, both RelayPort and + // TurnPort will hide raddr to avoid local address leakage. + uint32 candidate_filter_; + + friend class Connection; +}; + +// Represents a communication link between a port on the local client and a +// port on the remote client. +class Connection : public rtc::MessageHandler, + public sigslot::has_slots<> { + public: + // States are from RFC 5245. http://tools.ietf.org/html/rfc5245#section-5.7.4 + enum State { + STATE_WAITING = 0, // Check has not been performed, Waiting pair on CL. + STATE_INPROGRESS, // Check has been sent, transaction is in progress. + STATE_SUCCEEDED, // Check already done, produced a successful result. + STATE_FAILED // Check for this connection failed. + }; + + 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_; } + + // Returns the pair priority. + uint64 priority() const; + + enum ReadState { + STATE_READ_INIT = 0, // we have yet to receive a ping + STATE_READABLE = 1, // we have received pings recently + STATE_READ_TIMEOUT = 2, // we haven't received pings in a while + }; + + ReadState read_state() const { return read_state_; } + bool readable() const { return read_state_ == STATE_READABLE; } + + enum WriteState { + STATE_WRITABLE = 0, // we have received ping responses recently + STATE_WRITE_UNRELIABLE = 1, // we have had a few ping failures + STATE_WRITE_INIT = 2, // we have yet to receive a ping response + STATE_WRITE_TIMEOUT = 3, // we have had a large number of ping failures + }; + + WriteState write_state() const { return write_state_; } + bool writable() const { return write_state_ == STATE_WRITABLE; } + + // 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, + const rtc::PacketOptions& options) = 0; + + // Error if Send() returns < 0 + virtual int GetError() = 0; + + sigslot::signal4 SignalReadPacket; + + sigslot::signal1 SignalReadyToSend; + + // Called when a packet is received on this connection. + void OnReadPacket(const char* data, size_t size, + const rtc::PacketTime& packet_time); + + // Called when the socket is currently able to send. + void OnReadyToSend(); + + // 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() const { return pruned_; } + void Prune(); + + bool use_candidate_attr() const { return use_candidate_attr_; } + void set_use_candidate_attr(bool enable); + + void set_remote_ice_mode(IceMode mode) { + remote_ice_mode_ = mode; + } + + // 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() const { 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. + uint32 last_ping_received() const { return last_ping_received_; } + void ReceivedPing(); + + // Debugging description of this connection + std::string ToString() const; + std::string ToSensitiveString() const; + + bool reported() const { return reported_; } + void set_reported(bool reported) { reported_ = reported;} + + // This flag will be set if this connection is the chosen one for media + // transmission. This connection will send STUN ping with USE-CANDIDATE + // attribute. + sigslot::signal1 SignalUseCandidate; + // Invoked when Connection receives STUN error response with 487 code. + void HandleRoleConflictFromPeer(); + + State state() const { return state_; } + + IceMode remote_ice_mode() const { return remote_ice_mode_; } + + protected: + // Constructs a new connection to the given remote port. + Connection(Port* port, size_t index, const Candidate& candidate); + + // Called back when StunRequestManager has a stun packet to send + void OnSendStunPacket(const void* data, size_t size, StunRequest* req); + + // Callbacks from ConnectionRequest + void OnConnectionRequestResponse(ConnectionRequest* req, + StunMessage* response); + void OnConnectionRequestErrorResponse(ConnectionRequest* req, + StunMessage* response); + void OnConnectionRequestTimeout(ConnectionRequest* req); + + // Changes the state and signals if necessary. + void set_read_state(ReadState value); + void set_write_state(WriteState value); + void set_state(State state); + void set_connected(bool value); + + // Checks if this connection is useless, and hence, should be destroyed. + void CheckTimeout(); + + void OnMessage(rtc::Message *pmsg); + + Port* port_; + size_t local_candidate_index_; + Candidate remote_candidate_; + ReadState read_state_; + WriteState write_state_; + bool connected_; + bool pruned_; + // By default |use_candidate_attr_| flag will be true, + // as we will be using agrressive nomination. + // But when peer is ice-lite, this flag "must" be initialized to false and + // turn on when connection becomes "best connection". + bool use_candidate_attr_; + IceMode remote_ice_mode_; + StunRequestManager requests_; + uint32 rtt_; + 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 + uint32 last_data_received_; + uint32 last_ping_response_received_; + std::vector pings_since_last_response_; + + rtc::RateTracker recv_rate_tracker_; + rtc::RateTracker send_rate_tracker_; + + private: + void MaybeAddPrflxCandidate(ConnectionRequest* request, + StunMessage* response); + + bool reported_; + State state_; + + 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, + const rtc::PacketOptions& options); + virtual int GetError() { return error_; } + + private: + int error_; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_PORT_H_ diff --git a/webrtc/p2p/base/port_unittest.cc b/webrtc/p2p/base/port_unittest.cc new file mode 100644 index 000000000..8805709a6 --- /dev/null +++ b/webrtc/p2p/base/port_unittest.cc @@ -0,0 +1,2494 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/portproxy.h" +#include "webrtc/p2p/base/relayport.h" +#include "webrtc/p2p/base/stunport.h" +#include "webrtc/p2p/base/tcpport.h" +#include "webrtc/p2p/base/testrelayserver.h" +#include "webrtc/p2p/base/teststunserver.h" +#include "webrtc/p2p/base/testturnserver.h" +#include "webrtc/p2p/base/transport.h" +#include "webrtc/p2p/base/turnport.h" +#include "webrtc/base/crc32.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/natserver.h" +#include "webrtc/base/natsocketfactory.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/ssladapter.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/virtualsocketserver.h" + +using rtc::AsyncPacketSocket; +using rtc::ByteBuffer; +using rtc::NATType; +using rtc::NAT_OPEN_CONE; +using rtc::NAT_ADDR_RESTRICTED; +using rtc::NAT_PORT_RESTRICTED; +using rtc::NAT_SYMMETRIC; +using rtc::PacketSocketFactory; +using rtc::scoped_ptr; +using rtc::Socket; +using rtc::SocketAddress; +using namespace cricket; + +static const int kTimeout = 1000; +static const SocketAddress kLocalAddr1("192.168.1.2", 0); +static const SocketAddress kLocalAddr2("192.168.1.3", 0); +static const SocketAddress kNatAddr1("77.77.77.77", rtc::NAT_SERVER_PORT); +static const SocketAddress kNatAddr2("88.88.88.88", rtc::NAT_SERVER_PORT); +static const SocketAddress kStunAddr("99.99.99.1", STUN_SERVER_PORT); +static const SocketAddress kRelayUdpIntAddr("99.99.99.2", 5000); +static const SocketAddress kRelayUdpExtAddr("99.99.99.3", 5001); +static const SocketAddress kRelayTcpIntAddr("99.99.99.2", 5002); +static const SocketAddress kRelayTcpExtAddr("99.99.99.3", 5003); +static const SocketAddress kRelaySslTcpIntAddr("99.99.99.2", 5004); +static const SocketAddress kRelaySslTcpExtAddr("99.99.99.3", 5005); +static const SocketAddress kTurnUdpIntAddr("99.99.99.4", STUN_SERVER_PORT); +static const SocketAddress kTurnUdpExtAddr("99.99.99.5", 0); +static const RelayCredentials kRelayCredentials("test", "test"); + +// TODO: Update these when RFC5245 is completely supported. +// Magic value of 30 is from RFC3484, for IPv4 addresses. +static const uint32 kDefaultPrflxPriority = ICE_TYPE_PREFERENCE_PRFLX << 24 | + 30 << 8 | (256 - ICE_CANDIDATE_COMPONENT_DEFAULT); +static const int STUN_ERROR_BAD_REQUEST_AS_GICE = + STUN_ERROR_BAD_REQUEST / 256 * 100 + STUN_ERROR_BAD_REQUEST % 256; +static const int STUN_ERROR_UNAUTHORIZED_AS_GICE = + STUN_ERROR_UNAUTHORIZED / 256 * 100 + STUN_ERROR_UNAUTHORIZED % 256; +static const int STUN_ERROR_SERVER_ERROR_AS_GICE = + STUN_ERROR_SERVER_ERROR / 256 * 100 + STUN_ERROR_SERVER_ERROR % 256; + +static const int kTiebreaker1 = 11111; +static const int kTiebreaker2 = 22222; + +static Candidate GetCandidate(Port* port) { + assert(port->Candidates().size() == 1); + return port->Candidates()[0]; +} + +static SocketAddress GetAddress(Port* port) { + return GetCandidate(port).address(); +} + +static IceMessage* CopyStunMessage(const IceMessage* src) { + IceMessage* dst = new IceMessage(); + ByteBuffer buf; + src->Write(&buf); + dst->Read(&buf); + return dst; +} + +static bool WriteStunMessage(const StunMessage* msg, ByteBuffer* buf) { + buf->Resize(0); // clear out any existing buffer contents + return msg->Write(buf); +} + +// Stub port class for testing STUN generation and processing. +class TestPort : public Port { + public: + TestPort(rtc::Thread* thread, const std::string& type, + rtc::PacketSocketFactory* factory, rtc::Network* network, + const rtc::IPAddress& ip, int min_port, int max_port, + const std::string& username_fragment, const std::string& password) + : Port(thread, type, factory, network, ip, + min_port, max_port, username_fragment, password) { + } + ~TestPort() {} + + // Expose GetStunMessage so that we can test it. + using cricket::Port::GetStunMessage; + + // The last StunMessage that was sent on this Port. + // TODO: Make these const; requires changes to SendXXXXResponse. + ByteBuffer* last_stun_buf() { return last_stun_buf_.get(); } + IceMessage* last_stun_msg() { return last_stun_msg_.get(); } + int last_stun_error_code() { + int code = 0; + if (last_stun_msg_) { + const StunErrorCodeAttribute* error_attr = last_stun_msg_->GetErrorCode(); + if (error_attr) { + code = error_attr->code(); + } + } + return code; + } + + virtual void PrepareAddress() { + rtc::SocketAddress addr(ip(), min_port()); + AddAddress(addr, addr, rtc::SocketAddress(), "udp", "", Type(), + ICE_TYPE_PREFERENCE_HOST, 0, true); + } + + // Exposed for testing candidate building. + void AddCandidateAddress(const rtc::SocketAddress& addr) { + AddAddress(addr, addr, rtc::SocketAddress(), "udp", "", Type(), + type_preference_, 0, false); + } + void AddCandidateAddress(const rtc::SocketAddress& addr, + const rtc::SocketAddress& base_address, + const std::string& type, + int type_preference, + bool final) { + AddAddress(addr, base_address, rtc::SocketAddress(), "udp", "", type, + type_preference, 0, final); + } + + virtual Connection* CreateConnection(const Candidate& remote_candidate, + CandidateOrigin origin) { + Connection* conn = new ProxyConnection(this, 0, remote_candidate); + AddConnection(conn); + // Set use-candidate attribute flag as this will add USE-CANDIDATE attribute + // in STUN binding requests. + conn->set_use_candidate_attr(true); + return conn; + } + virtual int SendTo( + const void* data, size_t size, const rtc::SocketAddress& addr, + const rtc::PacketOptions& options, bool payload) { + if (!payload) { + IceMessage* msg = new IceMessage; + ByteBuffer* buf = new ByteBuffer(static_cast(data), size); + ByteBuffer::ReadPosition pos(buf->GetReadPosition()); + if (!msg->Read(buf)) { + delete msg; + delete buf; + return -1; + } + buf->SetReadPosition(pos); + last_stun_buf_.reset(buf); + last_stun_msg_.reset(msg); + } + return static_cast(size); + } + virtual int SetOption(rtc::Socket::Option opt, int value) { + return 0; + } + virtual int GetOption(rtc::Socket::Option opt, int* value) { + return -1; + } + virtual int GetError() { + return 0; + } + void Reset() { + last_stun_buf_.reset(); + last_stun_msg_.reset(); + } + void set_type_preference(int type_preference) { + type_preference_ = type_preference; + } + + private: + rtc::scoped_ptr last_stun_buf_; + rtc::scoped_ptr last_stun_msg_; + int type_preference_; +}; + +class TestChannel : public sigslot::has_slots<> { + public: + // Takes ownership of |p1| (but not |p2|). + TestChannel(Port* p1, Port* p2) + : ice_mode_(ICEMODE_FULL), src_(p1), dst_(p2), complete_count_(0), + conn_(NULL), remote_request_(), nominated_(false) { + src_->SignalPortComplete.connect( + this, &TestChannel::OnPortComplete); + src_->SignalUnknownAddress.connect(this, &TestChannel::OnUnknownAddress); + src_->SignalDestroyed.connect(this, &TestChannel::OnSrcPortDestroyed); + } + + int complete_count() { return complete_count_; } + Connection* conn() { return conn_; } + const SocketAddress& remote_address() { return remote_address_; } + const std::string remote_fragment() { return remote_frag_; } + + void Start() { + src_->PrepareAddress(); + } + void CreateConnection() { + conn_ = src_->CreateConnection(GetCandidate(dst_), Port::ORIGIN_MESSAGE); + IceMode remote_ice_mode = + (ice_mode_ == ICEMODE_FULL) ? ICEMODE_LITE : ICEMODE_FULL; + conn_->set_remote_ice_mode(remote_ice_mode); + conn_->set_use_candidate_attr(remote_ice_mode == ICEMODE_FULL); + conn_->SignalStateChange.connect( + this, &TestChannel::OnConnectionStateChange); + } + void OnConnectionStateChange(Connection* conn) { + if (conn->write_state() == Connection::STATE_WRITABLE) { + conn->set_use_candidate_attr(true); + nominated_ = true; + } + } + void AcceptConnection() { + ASSERT_TRUE(remote_request_.get() != NULL); + Candidate c = GetCandidate(dst_); + c.set_address(remote_address_); + conn_ = src_->CreateConnection(c, Port::ORIGIN_MESSAGE); + src_->SendBindingResponse(remote_request_.get(), remote_address_); + remote_request_.reset(); + } + void Ping() { + Ping(0); + } + void Ping(uint32 now) { + conn_->Ping(now); + } + void Stop() { + conn_->SignalDestroyed.connect(this, &TestChannel::OnDestroyed); + conn_->Destroy(); + } + + void OnPortComplete(Port* port) { + complete_count_++; + } + void SetIceMode(IceMode ice_mode) { + ice_mode_ = ice_mode; + } + + void OnUnknownAddress(PortInterface* port, const SocketAddress& addr, + ProtocolType proto, + IceMessage* msg, const std::string& rf, + bool /*port_muxed*/) { + ASSERT_EQ(src_.get(), port); + if (!remote_address_.IsNil()) { + ASSERT_EQ(remote_address_, addr); + } + // MI and PRIORITY attribute should be present in ping requests when port + // is in ICEPROTO_RFC5245 mode. + const cricket::StunUInt32Attribute* priority_attr = + msg->GetUInt32(STUN_ATTR_PRIORITY); + const cricket::StunByteStringAttribute* mi_attr = + msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY); + const cricket::StunUInt32Attribute* fingerprint_attr = + msg->GetUInt32(STUN_ATTR_FINGERPRINT); + if (src_->IceProtocol() == cricket::ICEPROTO_RFC5245) { + EXPECT_TRUE(priority_attr != NULL); + EXPECT_TRUE(mi_attr != NULL); + EXPECT_TRUE(fingerprint_attr != NULL); + } else { + EXPECT_TRUE(priority_attr == NULL); + EXPECT_TRUE(mi_attr == NULL); + EXPECT_TRUE(fingerprint_attr == NULL); + } + remote_address_ = addr; + remote_request_.reset(CopyStunMessage(msg)); + remote_frag_ = rf; + } + + void OnDestroyed(Connection* conn) { + ASSERT_EQ(conn_, conn); + conn_ = NULL; + } + + void OnSrcPortDestroyed(PortInterface* port) { + Port* destroyed_src = src_.release(); + ASSERT_EQ(destroyed_src, port); + } + + bool nominated() const { return nominated_; } + + private: + IceMode ice_mode_; + rtc::scoped_ptr src_; + Port* dst_; + + int complete_count_; + Connection* conn_; + SocketAddress remote_address_; + rtc::scoped_ptr remote_request_; + std::string remote_frag_; + bool nominated_; +}; + +class PortTest : public testing::Test, public sigslot::has_slots<> { + public: + PortTest() + : main_(rtc::Thread::Current()), + pss_(new rtc::PhysicalSocketServer), + ss_(new rtc::VirtualSocketServer(pss_.get())), + ss_scope_(ss_.get()), + network_("unittest", "unittest", rtc::IPAddress(INADDR_ANY), 32), + socket_factory_(rtc::Thread::Current()), + nat_factory1_(ss_.get(), kNatAddr1), + nat_factory2_(ss_.get(), kNatAddr2), + nat_socket_factory1_(&nat_factory1_), + nat_socket_factory2_(&nat_factory2_), + stun_server_(TestStunServer::Create(main_, kStunAddr)), + turn_server_(main_, kTurnUdpIntAddr, kTurnUdpExtAddr), + relay_server_(main_, kRelayUdpIntAddr, kRelayUdpExtAddr, + kRelayTcpIntAddr, kRelayTcpExtAddr, + kRelaySslTcpIntAddr, kRelaySslTcpExtAddr), + username_(rtc::CreateRandomString(ICE_UFRAG_LENGTH)), + password_(rtc::CreateRandomString(ICE_PWD_LENGTH)), + ice_protocol_(cricket::ICEPROTO_GOOGLE), + role_conflict_(false), + destroyed_(false) { + network_.AddIP(rtc::IPAddress(INADDR_ANY)); + } + + protected: + void TestLocalToLocal() { + Port* port1 = CreateUdpPort(kLocalAddr1); + Port* port2 = CreateUdpPort(kLocalAddr2); + TestConnectivity("udp", port1, "udp", port2, true, true, true, true); + } + void TestLocalToStun(NATType ntype) { + Port* port1 = CreateUdpPort(kLocalAddr1); + nat_server2_.reset(CreateNatServer(kNatAddr2, ntype)); + Port* port2 = CreateStunPort(kLocalAddr2, &nat_socket_factory2_); + TestConnectivity("udp", port1, StunName(ntype), port2, + ntype == NAT_OPEN_CONE, true, + ntype != NAT_SYMMETRIC, true); + } + void TestLocalToRelay(RelayType rtype, ProtocolType proto) { + Port* port1 = CreateUdpPort(kLocalAddr1); + Port* port2 = CreateRelayPort(kLocalAddr2, rtype, proto, PROTO_UDP); + TestConnectivity("udp", port1, RelayName(rtype, proto), port2, + rtype == RELAY_GTURN, true, true, true); + } + void TestStunToLocal(NATType ntype) { + nat_server1_.reset(CreateNatServer(kNatAddr1, ntype)); + Port* port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_); + Port* port2 = CreateUdpPort(kLocalAddr2); + TestConnectivity(StunName(ntype), port1, "udp", port2, + true, ntype != NAT_SYMMETRIC, true, true); + } + void TestStunToStun(NATType ntype1, NATType ntype2) { + nat_server1_.reset(CreateNatServer(kNatAddr1, ntype1)); + Port* port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_); + nat_server2_.reset(CreateNatServer(kNatAddr2, ntype2)); + Port* port2 = CreateStunPort(kLocalAddr2, &nat_socket_factory2_); + TestConnectivity(StunName(ntype1), port1, StunName(ntype2), port2, + ntype2 == NAT_OPEN_CONE, + ntype1 != NAT_SYMMETRIC, ntype2 != NAT_SYMMETRIC, + ntype1 + ntype2 < (NAT_PORT_RESTRICTED + NAT_SYMMETRIC)); + } + void TestStunToRelay(NATType ntype, RelayType rtype, ProtocolType proto) { + nat_server1_.reset(CreateNatServer(kNatAddr1, ntype)); + Port* port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_); + Port* port2 = CreateRelayPort(kLocalAddr2, rtype, proto, PROTO_UDP); + TestConnectivity(StunName(ntype), port1, RelayName(rtype, proto), port2, + rtype == RELAY_GTURN, ntype != NAT_SYMMETRIC, true, true); + } + void TestTcpToTcp() { + Port* port1 = CreateTcpPort(kLocalAddr1); + Port* port2 = CreateTcpPort(kLocalAddr2); + TestConnectivity("tcp", port1, "tcp", port2, true, false, true, true); + } + void TestTcpToRelay(RelayType rtype, ProtocolType proto) { + Port* port1 = CreateTcpPort(kLocalAddr1); + Port* port2 = CreateRelayPort(kLocalAddr2, rtype, proto, PROTO_TCP); + TestConnectivity("tcp", port1, RelayName(rtype, proto), port2, + rtype == RELAY_GTURN, false, true, true); + } + void TestSslTcpToRelay(RelayType rtype, ProtocolType proto) { + Port* port1 = CreateTcpPort(kLocalAddr1); + Port* port2 = CreateRelayPort(kLocalAddr2, rtype, proto, PROTO_SSLTCP); + TestConnectivity("ssltcp", port1, RelayName(rtype, proto), port2, + rtype == RELAY_GTURN, false, true, true); + } + + // helpers for above functions + UDPPort* CreateUdpPort(const SocketAddress& addr) { + return CreateUdpPort(addr, &socket_factory_); + } + UDPPort* CreateUdpPort(const SocketAddress& addr, + PacketSocketFactory* socket_factory) { + UDPPort* port = UDPPort::Create(main_, socket_factory, &network_, + addr.ipaddr(), 0, 0, username_, password_); + port->SetIceProtocolType(ice_protocol_); + return port; + } + TCPPort* CreateTcpPort(const SocketAddress& addr) { + TCPPort* port = CreateTcpPort(addr, &socket_factory_); + port->SetIceProtocolType(ice_protocol_); + return port; + } + TCPPort* CreateTcpPort(const SocketAddress& addr, + PacketSocketFactory* socket_factory) { + TCPPort* port = TCPPort::Create(main_, socket_factory, &network_, + addr.ipaddr(), 0, 0, username_, password_, + true); + port->SetIceProtocolType(ice_protocol_); + return port; + } + StunPort* CreateStunPort(const SocketAddress& addr, + rtc::PacketSocketFactory* factory) { + ServerAddresses stun_servers; + stun_servers.insert(kStunAddr); + StunPort* port = StunPort::Create(main_, factory, &network_, + addr.ipaddr(), 0, 0, + username_, password_, stun_servers); + port->SetIceProtocolType(ice_protocol_); + return port; + } + Port* CreateRelayPort(const SocketAddress& addr, RelayType rtype, + ProtocolType int_proto, ProtocolType ext_proto) { + if (rtype == RELAY_TURN) { + return CreateTurnPort(addr, &socket_factory_, int_proto, ext_proto); + } else { + return CreateGturnPort(addr, int_proto, ext_proto); + } + } + TurnPort* CreateTurnPort(const SocketAddress& addr, + PacketSocketFactory* socket_factory, + ProtocolType int_proto, ProtocolType ext_proto) { + return CreateTurnPort(addr, socket_factory, + int_proto, ext_proto, kTurnUdpIntAddr); + } + TurnPort* CreateTurnPort(const SocketAddress& addr, + PacketSocketFactory* socket_factory, + ProtocolType int_proto, ProtocolType ext_proto, + const rtc::SocketAddress& server_addr) { + TurnPort* port = TurnPort::Create(main_, socket_factory, &network_, + addr.ipaddr(), 0, 0, + username_, password_, ProtocolAddress( + server_addr, PROTO_UDP), + kRelayCredentials, 0); + port->SetIceProtocolType(ice_protocol_); + return port; + } + RelayPort* CreateGturnPort(const SocketAddress& addr, + ProtocolType int_proto, ProtocolType ext_proto) { + RelayPort* port = CreateGturnPort(addr); + SocketAddress addrs[] = + { kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr }; + port->AddServerAddress(ProtocolAddress(addrs[int_proto], int_proto)); + return port; + } + RelayPort* CreateGturnPort(const SocketAddress& addr) { + RelayPort* port = RelayPort::Create(main_, &socket_factory_, &network_, + addr.ipaddr(), 0, 0, + username_, password_); + // TODO: Add an external address for ext_proto, so that the + // other side can connect to this port using a non-UDP protocol. + port->SetIceProtocolType(ice_protocol_); + return port; + } + rtc::NATServer* CreateNatServer(const SocketAddress& addr, + rtc::NATType type) { + return new rtc::NATServer(type, ss_.get(), addr, ss_.get(), addr); + } + static const char* StunName(NATType type) { + switch (type) { + case NAT_OPEN_CONE: return "stun(open cone)"; + case NAT_ADDR_RESTRICTED: return "stun(addr restricted)"; + case NAT_PORT_RESTRICTED: return "stun(port restricted)"; + case NAT_SYMMETRIC: return "stun(symmetric)"; + default: return "stun(?)"; + } + } + static const char* RelayName(RelayType type, ProtocolType proto) { + if (type == RELAY_TURN) { + switch (proto) { + case PROTO_UDP: return "turn(udp)"; + case PROTO_TCP: return "turn(tcp)"; + case PROTO_SSLTCP: return "turn(ssltcp)"; + default: return "turn(?)"; + } + } else { + switch (proto) { + case PROTO_UDP: return "gturn(udp)"; + case PROTO_TCP: return "gturn(tcp)"; + case PROTO_SSLTCP: return "gturn(ssltcp)"; + default: return "gturn(?)"; + } + } + } + + void TestCrossFamilyPorts(int type); + + // This does all the work and then deletes |port1| and |port2|. + void TestConnectivity(const char* name1, Port* port1, + const char* name2, Port* port2, + bool accept, bool same_addr1, + bool same_addr2, bool possible); + + // This connects and disconnects the provided channels in the same sequence as + // TestConnectivity with all options set to |true|. It does not delete either + // channel. + void ConnectAndDisconnectChannels(TestChannel* ch1, TestChannel* ch2); + + void SetIceProtocolType(cricket::IceProtocolType protocol) { + ice_protocol_ = protocol; + } + + IceMessage* CreateStunMessage(int type) { + IceMessage* msg = new IceMessage(); + msg->SetType(type); + msg->SetTransactionID("TESTTESTTEST"); + return msg; + } + IceMessage* CreateStunMessageWithUsername(int type, + const std::string& username) { + IceMessage* msg = CreateStunMessage(type); + msg->AddAttribute( + new StunByteStringAttribute(STUN_ATTR_USERNAME, username)); + return msg; + } + TestPort* CreateTestPort(const rtc::SocketAddress& addr, + const std::string& username, + const std::string& password) { + TestPort* port = new TestPort(main_, "test", &socket_factory_, &network_, + addr.ipaddr(), 0, 0, username, password); + port->SignalRoleConflict.connect(this, &PortTest::OnRoleConflict); + return port; + } + TestPort* CreateTestPort(const rtc::SocketAddress& addr, + const std::string& username, + const std::string& password, + cricket::IceProtocolType type, + cricket::IceRole role, + int tiebreaker) { + TestPort* port = CreateTestPort(addr, username, password); + port->SetIceProtocolType(type); + port->SetIceRole(role); + port->SetIceTiebreaker(tiebreaker); + return port; + } + + void OnRoleConflict(PortInterface* port) { + role_conflict_ = true; + } + bool role_conflict() const { return role_conflict_; } + + void ConnectToSignalDestroyed(PortInterface* port) { + port->SignalDestroyed.connect(this, &PortTest::OnDestroyed); + } + + void OnDestroyed(PortInterface* port) { + destroyed_ = true; + } + bool destroyed() const { return destroyed_; } + + rtc::BasicPacketSocketFactory* nat_socket_factory1() { + return &nat_socket_factory1_; + } + + private: + rtc::Thread* main_; + rtc::scoped_ptr pss_; + rtc::scoped_ptr ss_; + rtc::SocketServerScope ss_scope_; + rtc::Network network_; + rtc::BasicPacketSocketFactory socket_factory_; + rtc::scoped_ptr nat_server1_; + rtc::scoped_ptr nat_server2_; + rtc::NATSocketFactory nat_factory1_; + rtc::NATSocketFactory nat_factory2_; + rtc::BasicPacketSocketFactory nat_socket_factory1_; + rtc::BasicPacketSocketFactory nat_socket_factory2_; + scoped_ptr stun_server_; + TestTurnServer turn_server_; + TestRelayServer relay_server_; + std::string username_; + std::string password_; + cricket::IceProtocolType ice_protocol_; + bool role_conflict_; + bool destroyed_; +}; + +void PortTest::TestConnectivity(const char* name1, Port* port1, + const char* name2, Port* port2, + bool accept, bool same_addr1, + bool same_addr2, bool possible) { + LOG(LS_INFO) << "Test: " << name1 << " to " << name2 << ": "; + port1->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT); + port2->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT); + + // Set up channels and ensure both ports will be deleted. + TestChannel ch1(port1, port2); + TestChannel ch2(port2, port1); + EXPECT_EQ(0, ch1.complete_count()); + EXPECT_EQ(0, ch2.complete_count()); + + // Acquire addresses. + ch1.Start(); + ch2.Start(); + ASSERT_EQ_WAIT(1, ch1.complete_count(), kTimeout); + ASSERT_EQ_WAIT(1, ch2.complete_count(), kTimeout); + + // Send a ping from src to dst. This may or may not make it. + ch1.CreateConnection(); + ASSERT_TRUE(ch1.conn() != NULL); + EXPECT_TRUE_WAIT(ch1.conn()->connected(), kTimeout); // for TCP connect + ch1.Ping(); + WAIT(!ch2.remote_address().IsNil(), kTimeout); + + if (accept) { + // We are able to send a ping from src to dst. This is the case when + // sending to UDP ports and cone NATs. + EXPECT_TRUE(ch1.remote_address().IsNil()); + EXPECT_EQ(ch2.remote_fragment(), port1->username_fragment()); + + // Ensure the ping came from the same address used for src. + // This is the case unless the source NAT was symmetric. + if (same_addr1) EXPECT_EQ(ch2.remote_address(), GetAddress(port1)); + EXPECT_TRUE(same_addr2); + + // Send a ping from dst to src. + ch2.AcceptConnection(); + ASSERT_TRUE(ch2.conn() != NULL); + ch2.Ping(); + EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch2.conn()->write_state(), + kTimeout); + } else { + // We can't send a ping from src to dst, so flip it around. This will happen + // when the destination NAT is addr/port restricted or symmetric. + EXPECT_TRUE(ch1.remote_address().IsNil()); + EXPECT_TRUE(ch2.remote_address().IsNil()); + + // Send a ping from dst to src. Again, this may or may not make it. + ch2.CreateConnection(); + ASSERT_TRUE(ch2.conn() != NULL); + ch2.Ping(); + WAIT(ch2.conn()->write_state() == Connection::STATE_WRITABLE, kTimeout); + + if (same_addr1 && same_addr2) { + // The new ping got back to the source. + EXPECT_EQ(Connection::STATE_READABLE, ch1.conn()->read_state()); + EXPECT_EQ(Connection::STATE_WRITABLE, ch2.conn()->write_state()); + + // First connection may not be writable if the first ping did not get + // through. So we will have to do another. + if (ch1.conn()->write_state() == Connection::STATE_WRITE_INIT) { + ch1.Ping(); + EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(), + kTimeout); + } + } else if (!same_addr1 && possible) { + // The new ping went to the candidate address, but that address was bad. + // This will happen when the source NAT is symmetric. + EXPECT_TRUE(ch1.remote_address().IsNil()); + EXPECT_TRUE(ch2.remote_address().IsNil()); + + // However, since we have now sent a ping to the source IP, we should be + // able to get a ping from it. This gives us the real source address. + ch1.Ping(); + EXPECT_TRUE_WAIT(!ch2.remote_address().IsNil(), kTimeout); + EXPECT_EQ(Connection::STATE_READ_INIT, ch2.conn()->read_state()); + EXPECT_TRUE(ch1.remote_address().IsNil()); + + // Pick up the actual address and establish the connection. + ch2.AcceptConnection(); + ASSERT_TRUE(ch2.conn() != NULL); + ch2.Ping(); + EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch2.conn()->write_state(), + kTimeout); + } else if (!same_addr2 && possible) { + // The new ping came in, but from an unexpected address. This will happen + // when the destination NAT is symmetric. + EXPECT_FALSE(ch1.remote_address().IsNil()); + EXPECT_EQ(Connection::STATE_READ_INIT, ch1.conn()->read_state()); + + // Update our address and complete the connection. + ch1.AcceptConnection(); + ch1.Ping(); + EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(), + kTimeout); + } else { // (!possible) + // There should be s no way for the pings to reach each other. Check it. + EXPECT_TRUE(ch1.remote_address().IsNil()); + EXPECT_TRUE(ch2.remote_address().IsNil()); + ch1.Ping(); + WAIT(!ch2.remote_address().IsNil(), kTimeout); + EXPECT_TRUE(ch1.remote_address().IsNil()); + EXPECT_TRUE(ch2.remote_address().IsNil()); + } + } + + // Everything should be good, unless we know the situation is impossible. + ASSERT_TRUE(ch1.conn() != NULL); + ASSERT_TRUE(ch2.conn() != NULL); + if (possible) { + EXPECT_EQ(Connection::STATE_READABLE, ch1.conn()->read_state()); + EXPECT_EQ(Connection::STATE_WRITABLE, ch1.conn()->write_state()); + EXPECT_EQ(Connection::STATE_READABLE, ch2.conn()->read_state()); + EXPECT_EQ(Connection::STATE_WRITABLE, ch2.conn()->write_state()); + } else { + EXPECT_NE(Connection::STATE_READABLE, ch1.conn()->read_state()); + EXPECT_NE(Connection::STATE_WRITABLE, ch1.conn()->write_state()); + EXPECT_NE(Connection::STATE_READABLE, ch2.conn()->read_state()); + EXPECT_NE(Connection::STATE_WRITABLE, ch2.conn()->write_state()); + } + + // Tear down and ensure that goes smoothly. + ch1.Stop(); + ch2.Stop(); + EXPECT_TRUE_WAIT(ch1.conn() == NULL, kTimeout); + EXPECT_TRUE_WAIT(ch2.conn() == NULL, kTimeout); +} + +void PortTest::ConnectAndDisconnectChannels(TestChannel* ch1, + TestChannel* ch2) { + // Acquire addresses. + ch1->Start(); + ch2->Start(); + + // Send a ping from src to dst. + ch1->CreateConnection(); + EXPECT_TRUE_WAIT(ch1->conn()->connected(), kTimeout); // for TCP connect + ch1->Ping(); + WAIT(!ch2->remote_address().IsNil(), kTimeout); + + // Send a ping from dst to src. + ch2->AcceptConnection(); + ch2->Ping(); + EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch2->conn()->write_state(), + kTimeout); + + // Destroy the connections. + ch1->Stop(); + ch2->Stop(); +} + +class FakePacketSocketFactory : public rtc::PacketSocketFactory { + public: + FakePacketSocketFactory() + : next_udp_socket_(NULL), + next_server_tcp_socket_(NULL), + next_client_tcp_socket_(NULL) { + } + virtual ~FakePacketSocketFactory() { } + + virtual AsyncPacketSocket* CreateUdpSocket( + const SocketAddress& address, int min_port, int max_port) { + EXPECT_TRUE(next_udp_socket_ != NULL); + AsyncPacketSocket* result = next_udp_socket_; + next_udp_socket_ = NULL; + return result; + } + + virtual AsyncPacketSocket* CreateServerTcpSocket( + const SocketAddress& local_address, int min_port, int max_port, + int opts) { + EXPECT_TRUE(next_server_tcp_socket_ != NULL); + AsyncPacketSocket* result = next_server_tcp_socket_; + next_server_tcp_socket_ = NULL; + return result; + } + + // TODO: |proxy_info| and |user_agent| should be set + // per-factory and not when socket is created. + virtual AsyncPacketSocket* CreateClientTcpSocket( + const SocketAddress& local_address, const SocketAddress& remote_address, + const rtc::ProxyInfo& proxy_info, + const std::string& user_agent, int opts) { + EXPECT_TRUE(next_client_tcp_socket_ != NULL); + AsyncPacketSocket* result = next_client_tcp_socket_; + next_client_tcp_socket_ = NULL; + return result; + } + + void set_next_udp_socket(AsyncPacketSocket* next_udp_socket) { + next_udp_socket_ = next_udp_socket; + } + void set_next_server_tcp_socket(AsyncPacketSocket* next_server_tcp_socket) { + next_server_tcp_socket_ = next_server_tcp_socket; + } + void set_next_client_tcp_socket(AsyncPacketSocket* next_client_tcp_socket) { + next_client_tcp_socket_ = next_client_tcp_socket; + } + rtc::AsyncResolverInterface* CreateAsyncResolver() { + return NULL; + } + + private: + AsyncPacketSocket* next_udp_socket_; + AsyncPacketSocket* next_server_tcp_socket_; + AsyncPacketSocket* next_client_tcp_socket_; +}; + +class FakeAsyncPacketSocket : public AsyncPacketSocket { + public: + // Returns current local address. Address may be set to NULL if the + // socket is not bound yet (GetState() returns STATE_BINDING). + virtual SocketAddress GetLocalAddress() const { + return SocketAddress(); + } + + // Returns remote address. Returns zeroes if this is not a client TCP socket. + virtual SocketAddress GetRemoteAddress() const { + return SocketAddress(); + } + + // Send a packet. + virtual int Send(const void *pv, size_t cb, + const rtc::PacketOptions& options) { + return static_cast(cb); + } + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr, + const rtc::PacketOptions& options) { + return static_cast(cb); + } + virtual int Close() { + return 0; + } + + virtual State GetState() const { return state_; } + virtual int GetOption(Socket::Option opt, int* value) { return 0; } + virtual int SetOption(Socket::Option opt, int value) { return 0; } + virtual int GetError() const { return 0; } + virtual void SetError(int error) { } + + void set_state(State state) { state_ = state; } + + private: + State state_; +}; + +// Local -> XXXX +TEST_F(PortTest, TestLocalToLocal) { + TestLocalToLocal(); +} + +TEST_F(PortTest, TestLocalToConeNat) { + TestLocalToStun(NAT_OPEN_CONE); +} + +TEST_F(PortTest, TestLocalToARNat) { + TestLocalToStun(NAT_ADDR_RESTRICTED); +} + +TEST_F(PortTest, TestLocalToPRNat) { + TestLocalToStun(NAT_PORT_RESTRICTED); +} + +TEST_F(PortTest, TestLocalToSymNat) { + TestLocalToStun(NAT_SYMMETRIC); +} + +// Flaky: https://code.google.com/p/webrtc/issues/detail?id=3316. +TEST_F(PortTest, DISABLED_TestLocalToTurn) { + TestLocalToRelay(RELAY_TURN, PROTO_UDP); +} + +TEST_F(PortTest, TestLocalToGturn) { + TestLocalToRelay(RELAY_GTURN, PROTO_UDP); +} + +TEST_F(PortTest, TestLocalToTcpGturn) { + TestLocalToRelay(RELAY_GTURN, PROTO_TCP); +} + +TEST_F(PortTest, TestLocalToSslTcpGturn) { + TestLocalToRelay(RELAY_GTURN, PROTO_SSLTCP); +} + +// Cone NAT -> XXXX +TEST_F(PortTest, TestConeNatToLocal) { + TestStunToLocal(NAT_OPEN_CONE); +} + +TEST_F(PortTest, TestConeNatToConeNat) { + TestStunToStun(NAT_OPEN_CONE, NAT_OPEN_CONE); +} + +TEST_F(PortTest, TestConeNatToARNat) { + TestStunToStun(NAT_OPEN_CONE, NAT_ADDR_RESTRICTED); +} + +TEST_F(PortTest, TestConeNatToPRNat) { + TestStunToStun(NAT_OPEN_CONE, NAT_PORT_RESTRICTED); +} + +TEST_F(PortTest, TestConeNatToSymNat) { + TestStunToStun(NAT_OPEN_CONE, NAT_SYMMETRIC); +} + +TEST_F(PortTest, TestConeNatToTurn) { + TestStunToRelay(NAT_OPEN_CONE, RELAY_TURN, PROTO_UDP); +} + +TEST_F(PortTest, TestConeNatToGturn) { + TestStunToRelay(NAT_OPEN_CONE, RELAY_GTURN, PROTO_UDP); +} + +TEST_F(PortTest, TestConeNatToTcpGturn) { + TestStunToRelay(NAT_OPEN_CONE, RELAY_GTURN, PROTO_TCP); +} + +// Address-restricted NAT -> XXXX +TEST_F(PortTest, TestARNatToLocal) { + TestStunToLocal(NAT_ADDR_RESTRICTED); +} + +TEST_F(PortTest, TestARNatToConeNat) { + TestStunToStun(NAT_ADDR_RESTRICTED, NAT_OPEN_CONE); +} + +TEST_F(PortTest, TestARNatToARNat) { + TestStunToStun(NAT_ADDR_RESTRICTED, NAT_ADDR_RESTRICTED); +} + +TEST_F(PortTest, TestARNatToPRNat) { + TestStunToStun(NAT_ADDR_RESTRICTED, NAT_PORT_RESTRICTED); +} + +TEST_F(PortTest, TestARNatToSymNat) { + TestStunToStun(NAT_ADDR_RESTRICTED, NAT_SYMMETRIC); +} + +TEST_F(PortTest, TestARNatToTurn) { + TestStunToRelay(NAT_ADDR_RESTRICTED, RELAY_TURN, PROTO_UDP); +} + +TEST_F(PortTest, TestARNatToGturn) { + TestStunToRelay(NAT_ADDR_RESTRICTED, RELAY_GTURN, PROTO_UDP); +} + +TEST_F(PortTest, TestARNATNatToTcpGturn) { + TestStunToRelay(NAT_ADDR_RESTRICTED, RELAY_GTURN, PROTO_TCP); +} + +// Port-restricted NAT -> XXXX +TEST_F(PortTest, TestPRNatToLocal) { + TestStunToLocal(NAT_PORT_RESTRICTED); +} + +TEST_F(PortTest, TestPRNatToConeNat) { + TestStunToStun(NAT_PORT_RESTRICTED, NAT_OPEN_CONE); +} + +TEST_F(PortTest, TestPRNatToARNat) { + TestStunToStun(NAT_PORT_RESTRICTED, NAT_ADDR_RESTRICTED); +} + +TEST_F(PortTest, TestPRNatToPRNat) { + TestStunToStun(NAT_PORT_RESTRICTED, NAT_PORT_RESTRICTED); +} + +TEST_F(PortTest, TestPRNatToSymNat) { + // Will "fail" + TestStunToStun(NAT_PORT_RESTRICTED, NAT_SYMMETRIC); +} + +TEST_F(PortTest, TestPRNatToTurn) { + TestStunToRelay(NAT_PORT_RESTRICTED, RELAY_TURN, PROTO_UDP); +} + +TEST_F(PortTest, TestPRNatToGturn) { + TestStunToRelay(NAT_PORT_RESTRICTED, RELAY_GTURN, PROTO_UDP); +} + +TEST_F(PortTest, TestPRNatToTcpGturn) { + TestStunToRelay(NAT_PORT_RESTRICTED, RELAY_GTURN, PROTO_TCP); +} + +// Symmetric NAT -> XXXX +TEST_F(PortTest, TestSymNatToLocal) { + TestStunToLocal(NAT_SYMMETRIC); +} + +TEST_F(PortTest, TestSymNatToConeNat) { + TestStunToStun(NAT_SYMMETRIC, NAT_OPEN_CONE); +} + +TEST_F(PortTest, TestSymNatToARNat) { + TestStunToStun(NAT_SYMMETRIC, NAT_ADDR_RESTRICTED); +} + +TEST_F(PortTest, TestSymNatToPRNat) { + // Will "fail" + TestStunToStun(NAT_SYMMETRIC, NAT_PORT_RESTRICTED); +} + +TEST_F(PortTest, TestSymNatToSymNat) { + // Will "fail" + TestStunToStun(NAT_SYMMETRIC, NAT_SYMMETRIC); +} + +TEST_F(PortTest, TestSymNatToTurn) { + TestStunToRelay(NAT_SYMMETRIC, RELAY_TURN, PROTO_UDP); +} + +TEST_F(PortTest, TestSymNatToGturn) { + TestStunToRelay(NAT_SYMMETRIC, RELAY_GTURN, PROTO_UDP); +} + +TEST_F(PortTest, TestSymNatToTcpGturn) { + TestStunToRelay(NAT_SYMMETRIC, RELAY_GTURN, PROTO_TCP); +} + +// Outbound TCP -> XXXX +TEST_F(PortTest, TestTcpToTcp) { + TestTcpToTcp(); +} + +/* TODO: Enable these once testrelayserver can accept external TCP. +TEST_F(PortTest, TestTcpToTcpRelay) { + TestTcpToRelay(PROTO_TCP); +} + +TEST_F(PortTest, TestTcpToSslTcpRelay) { + TestTcpToRelay(PROTO_SSLTCP); +} +*/ + +// Outbound SSLTCP -> XXXX +/* TODO: Enable these once testrelayserver can accept external SSL. +TEST_F(PortTest, TestSslTcpToTcpRelay) { + TestSslTcpToRelay(PROTO_TCP); +} + +TEST_F(PortTest, TestSslTcpToSslTcpRelay) { + TestSslTcpToRelay(PROTO_SSLTCP); +} +*/ + +// This test case verifies standard ICE features in STUN messages. Currently it +// verifies Message Integrity attribute in STUN messages and username in STUN +// binding request will have colon (":") between remote and local username. +TEST_F(PortTest, TestLocalToLocalAsIce) { + SetIceProtocolType(cricket::ICEPROTO_RFC5245); + UDPPort* port1 = CreateUdpPort(kLocalAddr1); + port1->SetIceRole(cricket::ICEROLE_CONTROLLING); + port1->SetIceTiebreaker(kTiebreaker1); + ASSERT_EQ(cricket::ICEPROTO_RFC5245, port1->IceProtocol()); + UDPPort* port2 = CreateUdpPort(kLocalAddr2); + port2->SetIceRole(cricket::ICEROLE_CONTROLLED); + port2->SetIceTiebreaker(kTiebreaker2); + ASSERT_EQ(cricket::ICEPROTO_RFC5245, port2->IceProtocol()); + // Same parameters as TestLocalToLocal above. + TestConnectivity("udp", port1, "udp", port2, true, true, true, true); +} + +// This test is trying to validate a successful and failure scenario in a +// loopback test when protocol is RFC5245. For success IceTiebreaker, username +// should remain equal to the request generated by the port and role of port +// must be in controlling. +TEST_F(PortTest, TestLoopbackCallAsIce) { + rtc::scoped_ptr lport( + CreateTestPort(kLocalAddr1, "lfrag", "lpass")); + lport->SetIceProtocolType(ICEPROTO_RFC5245); + lport->SetIceRole(cricket::ICEROLE_CONTROLLING); + lport->SetIceTiebreaker(kTiebreaker1); + lport->PrepareAddress(); + ASSERT_FALSE(lport->Candidates().empty()); + Connection* conn = lport->CreateConnection(lport->Candidates()[0], + Port::ORIGIN_MESSAGE); + conn->Ping(0); + + ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000); + IceMessage* msg = lport->last_stun_msg(); + EXPECT_EQ(STUN_BINDING_REQUEST, msg->type()); + conn->OnReadPacket(lport->last_stun_buf()->Data(), + lport->last_stun_buf()->Length(), + rtc::PacketTime()); + ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000); + msg = lport->last_stun_msg(); + EXPECT_EQ(STUN_BINDING_RESPONSE, msg->type()); + + // If the tiebreaker value is different from port, we expect a error + // response. + lport->Reset(); + lport->AddCandidateAddress(kLocalAddr2); + // Creating a different connection as |conn| is in STATE_READABLE. + Connection* conn1 = lport->CreateConnection(lport->Candidates()[1], + Port::ORIGIN_MESSAGE); + conn1->Ping(0); + + ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000); + msg = lport->last_stun_msg(); + EXPECT_EQ(STUN_BINDING_REQUEST, msg->type()); + rtc::scoped_ptr modified_req( + CreateStunMessage(STUN_BINDING_REQUEST)); + const StunByteStringAttribute* username_attr = msg->GetByteString( + STUN_ATTR_USERNAME); + modified_req->AddAttribute(new StunByteStringAttribute( + STUN_ATTR_USERNAME, username_attr->GetString())); + // To make sure we receive error response, adding tiebreaker less than + // what's present in request. + modified_req->AddAttribute(new StunUInt64Attribute( + STUN_ATTR_ICE_CONTROLLING, kTiebreaker1 - 1)); + modified_req->AddMessageIntegrity("lpass"); + modified_req->AddFingerprint(); + + lport->Reset(); + rtc::scoped_ptr buf(new ByteBuffer()); + WriteStunMessage(modified_req.get(), buf.get()); + conn1->OnReadPacket(buf->Data(), buf->Length(), rtc::PacketTime()); + ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000); + msg = lport->last_stun_msg(); + EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, msg->type()); +} + +// This test verifies role conflict signal is received when there is +// conflict in the role. In this case both ports are in controlling and +// |rport| has higher tiebreaker value than |lport|. Since |lport| has lower +// value of tiebreaker, when it receives ping request from |rport| it will +// send role conflict signal. +TEST_F(PortTest, TestIceRoleConflict) { + rtc::scoped_ptr lport( + CreateTestPort(kLocalAddr1, "lfrag", "lpass")); + lport->SetIceProtocolType(ICEPROTO_RFC5245); + lport->SetIceRole(cricket::ICEROLE_CONTROLLING); + lport->SetIceTiebreaker(kTiebreaker1); + rtc::scoped_ptr rport( + CreateTestPort(kLocalAddr2, "rfrag", "rpass")); + rport->SetIceProtocolType(ICEPROTO_RFC5245); + rport->SetIceRole(cricket::ICEROLE_CONTROLLING); + rport->SetIceTiebreaker(kTiebreaker2); + + lport->PrepareAddress(); + rport->PrepareAddress(); + ASSERT_FALSE(lport->Candidates().empty()); + ASSERT_FALSE(rport->Candidates().empty()); + Connection* lconn = lport->CreateConnection(rport->Candidates()[0], + Port::ORIGIN_MESSAGE); + Connection* rconn = rport->CreateConnection(lport->Candidates()[0], + Port::ORIGIN_MESSAGE); + rconn->Ping(0); + + ASSERT_TRUE_WAIT(rport->last_stun_msg() != NULL, 1000); + IceMessage* msg = rport->last_stun_msg(); + EXPECT_EQ(STUN_BINDING_REQUEST, msg->type()); + // Send rport binding request to lport. + lconn->OnReadPacket(rport->last_stun_buf()->Data(), + rport->last_stun_buf()->Length(), + rtc::PacketTime()); + + ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000); + EXPECT_EQ(STUN_BINDING_RESPONSE, lport->last_stun_msg()->type()); + EXPECT_TRUE(role_conflict()); +} + +TEST_F(PortTest, TestTcpNoDelay) { + TCPPort* port1 = CreateTcpPort(kLocalAddr1); + int option_value = -1; + int success = port1->GetOption(rtc::Socket::OPT_NODELAY, + &option_value); + ASSERT_EQ(0, success); // GetOption() should complete successfully w/ 0 + ASSERT_EQ(1, option_value); + delete port1; +} + +TEST_F(PortTest, TestDelayedBindingUdp) { + FakeAsyncPacketSocket *socket = new FakeAsyncPacketSocket(); + FakePacketSocketFactory socket_factory; + + socket_factory.set_next_udp_socket(socket); + scoped_ptr port( + CreateUdpPort(kLocalAddr1, &socket_factory)); + + socket->set_state(AsyncPacketSocket::STATE_BINDING); + port->PrepareAddress(); + + EXPECT_EQ(0U, port->Candidates().size()); + socket->SignalAddressReady(socket, kLocalAddr2); + + EXPECT_EQ(1U, port->Candidates().size()); +} + +TEST_F(PortTest, TestDelayedBindingTcp) { + FakeAsyncPacketSocket *socket = new FakeAsyncPacketSocket(); + FakePacketSocketFactory socket_factory; + + socket_factory.set_next_server_tcp_socket(socket); + scoped_ptr port( + CreateTcpPort(kLocalAddr1, &socket_factory)); + + socket->set_state(AsyncPacketSocket::STATE_BINDING); + port->PrepareAddress(); + + EXPECT_EQ(0U, port->Candidates().size()); + socket->SignalAddressReady(socket, kLocalAddr2); + + EXPECT_EQ(1U, port->Candidates().size()); +} + +void PortTest::TestCrossFamilyPorts(int type) { + FakePacketSocketFactory factory; + scoped_ptr ports[4]; + SocketAddress addresses[4] = {SocketAddress("192.168.1.3", 0), + SocketAddress("192.168.1.4", 0), + SocketAddress("2001:db8::1", 0), + SocketAddress("2001:db8::2", 0)}; + for (int i = 0; i < 4; i++) { + FakeAsyncPacketSocket *socket = new FakeAsyncPacketSocket(); + if (type == SOCK_DGRAM) { + factory.set_next_udp_socket(socket); + ports[i].reset(CreateUdpPort(addresses[i], &factory)); + } else if (type == SOCK_STREAM) { + factory.set_next_server_tcp_socket(socket); + ports[i].reset(CreateTcpPort(addresses[i], &factory)); + } + socket->set_state(AsyncPacketSocket::STATE_BINDING); + socket->SignalAddressReady(socket, addresses[i]); + ports[i]->PrepareAddress(); + } + + // IPv4 Port, connects to IPv6 candidate and then to IPv4 candidate. + if (type == SOCK_STREAM) { + FakeAsyncPacketSocket* clientsocket = new FakeAsyncPacketSocket(); + factory.set_next_client_tcp_socket(clientsocket); + } + Connection* c = ports[0]->CreateConnection(GetCandidate(ports[2].get()), + Port::ORIGIN_MESSAGE); + EXPECT_TRUE(NULL == c); + EXPECT_EQ(0U, ports[0]->connections().size()); + c = ports[0]->CreateConnection(GetCandidate(ports[1].get()), + Port::ORIGIN_MESSAGE); + EXPECT_FALSE(NULL == c); + EXPECT_EQ(1U, ports[0]->connections().size()); + + // IPv6 Port, connects to IPv4 candidate and to IPv6 candidate. + if (type == SOCK_STREAM) { + FakeAsyncPacketSocket* clientsocket = new FakeAsyncPacketSocket(); + factory.set_next_client_tcp_socket(clientsocket); + } + c = ports[2]->CreateConnection(GetCandidate(ports[0].get()), + Port::ORIGIN_MESSAGE); + EXPECT_TRUE(NULL == c); + EXPECT_EQ(0U, ports[2]->connections().size()); + c = ports[2]->CreateConnection(GetCandidate(ports[3].get()), + Port::ORIGIN_MESSAGE); + EXPECT_FALSE(NULL == c); + EXPECT_EQ(1U, ports[2]->connections().size()); +} + +TEST_F(PortTest, TestSkipCrossFamilyTcp) { + TestCrossFamilyPorts(SOCK_STREAM); +} + +TEST_F(PortTest, TestSkipCrossFamilyUdp) { + TestCrossFamilyPorts(SOCK_DGRAM); +} + +// This test verifies DSCP value set through SetOption interface can be +// get through DefaultDscpValue. +TEST_F(PortTest, TestDefaultDscpValue) { + int dscp; + rtc::scoped_ptr udpport(CreateUdpPort(kLocalAddr1)); + EXPECT_EQ(0, udpport->SetOption(rtc::Socket::OPT_DSCP, + rtc::DSCP_CS6)); + EXPECT_EQ(0, udpport->GetOption(rtc::Socket::OPT_DSCP, &dscp)); + rtc::scoped_ptr tcpport(CreateTcpPort(kLocalAddr1)); + EXPECT_EQ(0, tcpport->SetOption(rtc::Socket::OPT_DSCP, + rtc::DSCP_AF31)); + EXPECT_EQ(0, tcpport->GetOption(rtc::Socket::OPT_DSCP, &dscp)); + EXPECT_EQ(rtc::DSCP_AF31, dscp); + rtc::scoped_ptr stunport( + CreateStunPort(kLocalAddr1, nat_socket_factory1())); + EXPECT_EQ(0, stunport->SetOption(rtc::Socket::OPT_DSCP, + rtc::DSCP_AF41)); + EXPECT_EQ(0, stunport->GetOption(rtc::Socket::OPT_DSCP, &dscp)); + EXPECT_EQ(rtc::DSCP_AF41, dscp); + rtc::scoped_ptr turnport1(CreateTurnPort( + kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP)); + // Socket is created in PrepareAddress. + turnport1->PrepareAddress(); + EXPECT_EQ(0, turnport1->SetOption(rtc::Socket::OPT_DSCP, + rtc::DSCP_CS7)); + EXPECT_EQ(0, turnport1->GetOption(rtc::Socket::OPT_DSCP, &dscp)); + EXPECT_EQ(rtc::DSCP_CS7, dscp); + // This will verify correct value returned without the socket. + rtc::scoped_ptr turnport2(CreateTurnPort( + kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP)); + EXPECT_EQ(0, turnport2->SetOption(rtc::Socket::OPT_DSCP, + rtc::DSCP_CS6)); + EXPECT_EQ(0, turnport2->GetOption(rtc::Socket::OPT_DSCP, &dscp)); + EXPECT_EQ(rtc::DSCP_CS6, dscp); +} + +// Test sending STUN messages in GICE format. +TEST_F(PortTest, TestSendStunMessageAsGice) { + rtc::scoped_ptr lport( + CreateTestPort(kLocalAddr1, "lfrag", "lpass")); + rtc::scoped_ptr rport( + CreateTestPort(kLocalAddr2, "rfrag", "rpass")); + lport->SetIceProtocolType(ICEPROTO_GOOGLE); + rport->SetIceProtocolType(ICEPROTO_GOOGLE); + + // Send a fake ping from lport to rport. + lport->PrepareAddress(); + rport->PrepareAddress(); + ASSERT_FALSE(rport->Candidates().empty()); + Connection* conn = lport->CreateConnection(rport->Candidates()[0], + Port::ORIGIN_MESSAGE); + rport->CreateConnection(lport->Candidates()[0], Port::ORIGIN_MESSAGE); + conn->Ping(0); + + // Check that it's a proper BINDING-REQUEST. + ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000); + IceMessage* msg = lport->last_stun_msg(); + EXPECT_EQ(STUN_BINDING_REQUEST, msg->type()); + EXPECT_FALSE(msg->IsLegacy()); + const StunByteStringAttribute* username_attr = msg->GetByteString( + STUN_ATTR_USERNAME); + ASSERT_TRUE(username_attr != NULL); + EXPECT_EQ("rfraglfrag", username_attr->GetString()); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) == NULL); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_PRIORITY) == NULL); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_FINGERPRINT) == NULL); + + // Save a copy of the BINDING-REQUEST for use below. + rtc::scoped_ptr request(CopyStunMessage(msg)); + + // Respond with a BINDING-RESPONSE. + rport->SendBindingResponse(request.get(), lport->Candidates()[0].address()); + msg = rport->last_stun_msg(); + ASSERT_TRUE(msg != NULL); + EXPECT_EQ(STUN_BINDING_RESPONSE, msg->type()); + EXPECT_FALSE(msg->IsLegacy()); + username_attr = msg->GetByteString(STUN_ATTR_USERNAME); + ASSERT_TRUE(username_attr != NULL); // GICE has a username in the response. + EXPECT_EQ("rfraglfrag", username_attr->GetString()); + const StunAddressAttribute* addr_attr = msg->GetAddress( + STUN_ATTR_MAPPED_ADDRESS); + ASSERT_TRUE(addr_attr != NULL); + EXPECT_EQ(lport->Candidates()[0].address(), addr_attr->GetAddress()); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_XOR_MAPPED_ADDRESS) == NULL); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) == NULL); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_PRIORITY) == NULL); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_FINGERPRINT) == NULL); + + // Respond with a BINDING-ERROR-RESPONSE. This wouldn't happen in real life, + // but we can do it here. + rport->SendBindingErrorResponse(request.get(), + rport->Candidates()[0].address(), + STUN_ERROR_SERVER_ERROR, + STUN_ERROR_REASON_SERVER_ERROR); + msg = rport->last_stun_msg(); + ASSERT_TRUE(msg != NULL); + EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, msg->type()); + EXPECT_FALSE(msg->IsLegacy()); + username_attr = msg->GetByteString(STUN_ATTR_USERNAME); + ASSERT_TRUE(username_attr != NULL); // GICE has a username in the response. + EXPECT_EQ("rfraglfrag", username_attr->GetString()); + const StunErrorCodeAttribute* error_attr = msg->GetErrorCode(); + ASSERT_TRUE(error_attr != NULL); + // The GICE wire format for error codes is incorrect. + EXPECT_EQ(STUN_ERROR_SERVER_ERROR_AS_GICE, error_attr->code()); + EXPECT_EQ(STUN_ERROR_SERVER_ERROR / 256, error_attr->eclass()); + EXPECT_EQ(STUN_ERROR_SERVER_ERROR % 256, error_attr->number()); + EXPECT_EQ(std::string(STUN_ERROR_REASON_SERVER_ERROR), error_attr->reason()); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_PRIORITY) == NULL); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) == NULL); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_FINGERPRINT) == NULL); +} + +// Test sending STUN messages in ICE format. +TEST_F(PortTest, TestSendStunMessageAsIce) { + rtc::scoped_ptr lport( + CreateTestPort(kLocalAddr1, "lfrag", "lpass")); + rtc::scoped_ptr rport( + CreateTestPort(kLocalAddr2, "rfrag", "rpass")); + lport->SetIceProtocolType(ICEPROTO_RFC5245); + lport->SetIceRole(cricket::ICEROLE_CONTROLLING); + lport->SetIceTiebreaker(kTiebreaker1); + rport->SetIceProtocolType(ICEPROTO_RFC5245); + rport->SetIceRole(cricket::ICEROLE_CONTROLLED); + rport->SetIceTiebreaker(kTiebreaker2); + + // Send a fake ping from lport to rport. + lport->PrepareAddress(); + rport->PrepareAddress(); + ASSERT_FALSE(rport->Candidates().empty()); + Connection* lconn = lport->CreateConnection( + rport->Candidates()[0], Port::ORIGIN_MESSAGE); + Connection* rconn = rport->CreateConnection( + lport->Candidates()[0], Port::ORIGIN_MESSAGE); + lconn->Ping(0); + + // Check that it's a proper BINDING-REQUEST. + ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000); + IceMessage* msg = lport->last_stun_msg(); + EXPECT_EQ(STUN_BINDING_REQUEST, msg->type()); + EXPECT_FALSE(msg->IsLegacy()); + const StunByteStringAttribute* username_attr = + msg->GetByteString(STUN_ATTR_USERNAME); + ASSERT_TRUE(username_attr != NULL); + const StunUInt32Attribute* priority_attr = msg->GetUInt32(STUN_ATTR_PRIORITY); + ASSERT_TRUE(priority_attr != NULL); + EXPECT_EQ(kDefaultPrflxPriority, priority_attr->value()); + EXPECT_EQ("rfrag:lfrag", username_attr->GetString()); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL); + EXPECT_TRUE(StunMessage::ValidateMessageIntegrity( + lport->last_stun_buf()->Data(), lport->last_stun_buf()->Length(), + "rpass")); + const StunUInt64Attribute* ice_controlling_attr = + msg->GetUInt64(STUN_ATTR_ICE_CONTROLLING); + ASSERT_TRUE(ice_controlling_attr != NULL); + EXPECT_EQ(lport->IceTiebreaker(), ice_controlling_attr->value()); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_ICE_CONTROLLED) == NULL); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) != NULL); + EXPECT_TRUE(msg->GetUInt32(STUN_ATTR_FINGERPRINT) != NULL); + EXPECT_TRUE(StunMessage::ValidateFingerprint( + lport->last_stun_buf()->Data(), lport->last_stun_buf()->Length())); + + // Request should not include ping count. + ASSERT_TRUE(msg->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT) == NULL); + + // Save a copy of the BINDING-REQUEST for use below. + rtc::scoped_ptr request(CopyStunMessage(msg)); + + // Respond with a BINDING-RESPONSE. + rport->SendBindingResponse(request.get(), lport->Candidates()[0].address()); + msg = rport->last_stun_msg(); + ASSERT_TRUE(msg != NULL); + EXPECT_EQ(STUN_BINDING_RESPONSE, msg->type()); + + + EXPECT_FALSE(msg->IsLegacy()); + const StunAddressAttribute* addr_attr = msg->GetAddress( + STUN_ATTR_XOR_MAPPED_ADDRESS); + ASSERT_TRUE(addr_attr != NULL); + EXPECT_EQ(lport->Candidates()[0].address(), addr_attr->GetAddress()); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL); + EXPECT_TRUE(StunMessage::ValidateMessageIntegrity( + rport->last_stun_buf()->Data(), rport->last_stun_buf()->Length(), + "rpass")); + EXPECT_TRUE(msg->GetUInt32(STUN_ATTR_FINGERPRINT) != NULL); + EXPECT_TRUE(StunMessage::ValidateFingerprint( + lport->last_stun_buf()->Data(), lport->last_stun_buf()->Length())); + // No USERNAME or PRIORITY in ICE responses. + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USERNAME) == NULL); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_PRIORITY) == NULL); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MAPPED_ADDRESS) == NULL); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_ICE_CONTROLLING) == NULL); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_ICE_CONTROLLED) == NULL); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) == NULL); + + // Response should not include ping count. + ASSERT_TRUE(msg->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT) == NULL); + + // Respond with a BINDING-ERROR-RESPONSE. This wouldn't happen in real life, + // but we can do it here. + rport->SendBindingErrorResponse(request.get(), + lport->Candidates()[0].address(), + STUN_ERROR_SERVER_ERROR, + STUN_ERROR_REASON_SERVER_ERROR); + msg = rport->last_stun_msg(); + ASSERT_TRUE(msg != NULL); + EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, msg->type()); + EXPECT_FALSE(msg->IsLegacy()); + const StunErrorCodeAttribute* error_attr = msg->GetErrorCode(); + ASSERT_TRUE(error_attr != NULL); + EXPECT_EQ(STUN_ERROR_SERVER_ERROR, error_attr->code()); + EXPECT_EQ(std::string(STUN_ERROR_REASON_SERVER_ERROR), error_attr->reason()); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL); + EXPECT_TRUE(StunMessage::ValidateMessageIntegrity( + rport->last_stun_buf()->Data(), rport->last_stun_buf()->Length(), + "rpass")); + EXPECT_TRUE(msg->GetUInt32(STUN_ATTR_FINGERPRINT) != NULL); + EXPECT_TRUE(StunMessage::ValidateFingerprint( + lport->last_stun_buf()->Data(), lport->last_stun_buf()->Length())); + // No USERNAME with ICE. + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USERNAME) == NULL); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_PRIORITY) == NULL); + + // Testing STUN binding requests from rport --> lport, having ICE_CONTROLLED + // and (incremented) RETRANSMIT_COUNT attributes. + rport->Reset(); + rport->set_send_retransmit_count_attribute(true); + rconn->Ping(0); + rconn->Ping(0); + rconn->Ping(0); + ASSERT_TRUE_WAIT(rport->last_stun_msg() != NULL, 1000); + msg = rport->last_stun_msg(); + EXPECT_EQ(STUN_BINDING_REQUEST, msg->type()); + const StunUInt64Attribute* ice_controlled_attr = + msg->GetUInt64(STUN_ATTR_ICE_CONTROLLED); + ASSERT_TRUE(ice_controlled_attr != NULL); + EXPECT_EQ(rport->IceTiebreaker(), ice_controlled_attr->value()); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) == NULL); + + // Request should include ping count. + const StunUInt32Attribute* retransmit_attr = + msg->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT); + ASSERT_TRUE(retransmit_attr != NULL); + EXPECT_EQ(2U, retransmit_attr->value()); + + // Respond with a BINDING-RESPONSE. + request.reset(CopyStunMessage(msg)); + lport->SendBindingResponse(request.get(), rport->Candidates()[0].address()); + msg = lport->last_stun_msg(); + + // Response should include same ping count. + retransmit_attr = msg->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT); + ASSERT_TRUE(retransmit_attr != NULL); + EXPECT_EQ(2U, retransmit_attr->value()); +} + +TEST_F(PortTest, TestUseCandidateAttribute) { + rtc::scoped_ptr lport( + CreateTestPort(kLocalAddr1, "lfrag", "lpass")); + rtc::scoped_ptr rport( + CreateTestPort(kLocalAddr2, "rfrag", "rpass")); + lport->SetIceProtocolType(ICEPROTO_RFC5245); + lport->SetIceRole(cricket::ICEROLE_CONTROLLING); + lport->SetIceTiebreaker(kTiebreaker1); + rport->SetIceProtocolType(ICEPROTO_RFC5245); + rport->SetIceRole(cricket::ICEROLE_CONTROLLED); + rport->SetIceTiebreaker(kTiebreaker2); + + // Send a fake ping from lport to rport. + lport->PrepareAddress(); + rport->PrepareAddress(); + ASSERT_FALSE(rport->Candidates().empty()); + Connection* lconn = lport->CreateConnection( + rport->Candidates()[0], Port::ORIGIN_MESSAGE); + lconn->Ping(0); + ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000); + IceMessage* msg = lport->last_stun_msg(); + const StunUInt64Attribute* ice_controlling_attr = + msg->GetUInt64(STUN_ATTR_ICE_CONTROLLING); + ASSERT_TRUE(ice_controlling_attr != NULL); + const StunByteStringAttribute* use_candidate_attr = msg->GetByteString( + STUN_ATTR_USE_CANDIDATE); + ASSERT_TRUE(use_candidate_attr != NULL); +} + +// Test handling STUN messages in GICE format. +TEST_F(PortTest, TestHandleStunMessageAsGice) { + // Our port will act as the "remote" port. + rtc::scoped_ptr port( + CreateTestPort(kLocalAddr2, "rfrag", "rpass")); + port->SetIceProtocolType(ICEPROTO_GOOGLE); + + rtc::scoped_ptr in_msg, out_msg; + rtc::scoped_ptr buf(new ByteBuffer()); + rtc::SocketAddress addr(kLocalAddr1); + std::string username; + + // BINDING-REQUEST from local to remote with valid GICE username and no M-I. + in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, + "rfraglfrag")); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_TRUE(out_msg.get() != NULL); // Succeeds, since this is GICE. + EXPECT_EQ("lfrag", username); + + // Add M-I; should be ignored and rest of message parsed normally. + in_msg->AddMessageIntegrity("password"); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_TRUE(out_msg.get() != NULL); + EXPECT_EQ("lfrag", username); + + // BINDING-RESPONSE with username, as done in GICE. Should succeed. + in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_RESPONSE, + "rfraglfrag")); + in_msg->AddAttribute( + new StunAddressAttribute(STUN_ATTR_MAPPED_ADDRESS, kLocalAddr2)); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_TRUE(out_msg.get() != NULL); + EXPECT_EQ("", username); + + // BINDING-RESPONSE without username. Should be tolerated as well. + in_msg.reset(CreateStunMessage(STUN_BINDING_RESPONSE)); + in_msg->AddAttribute( + new StunAddressAttribute(STUN_ATTR_MAPPED_ADDRESS, kLocalAddr2)); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_TRUE(out_msg.get() != NULL); + EXPECT_EQ("", username); + + // BINDING-ERROR-RESPONSE with username and error code. + in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_ERROR_RESPONSE, + "rfraglfrag")); + in_msg->AddAttribute(new StunErrorCodeAttribute(STUN_ATTR_ERROR_CODE, + STUN_ERROR_SERVER_ERROR_AS_GICE, STUN_ERROR_REASON_SERVER_ERROR)); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + ASSERT_TRUE(out_msg.get() != NULL); + EXPECT_EQ("", username); + ASSERT_TRUE(out_msg->GetErrorCode() != NULL); + // GetStunMessage doesn't unmunge the GICE error code (happens downstream). + EXPECT_EQ(STUN_ERROR_SERVER_ERROR_AS_GICE, out_msg->GetErrorCode()->code()); + EXPECT_EQ(std::string(STUN_ERROR_REASON_SERVER_ERROR), + out_msg->GetErrorCode()->reason()); +} + +// Test handling STUN messages in ICE format. +TEST_F(PortTest, TestHandleStunMessageAsIce) { + // Our port will act as the "remote" port. + rtc::scoped_ptr port( + CreateTestPort(kLocalAddr2, "rfrag", "rpass")); + port->SetIceProtocolType(ICEPROTO_RFC5245); + + rtc::scoped_ptr in_msg, out_msg; + rtc::scoped_ptr buf(new ByteBuffer()); + rtc::SocketAddress addr(kLocalAddr1); + std::string username; + + // BINDING-REQUEST from local to remote with valid ICE username, + // MESSAGE-INTEGRITY, and FINGERPRINT. + in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, + "rfrag:lfrag")); + in_msg->AddMessageIntegrity("rpass"); + in_msg->AddFingerprint(); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_TRUE(out_msg.get() != NULL); + EXPECT_EQ("lfrag", username); + + // BINDING-RESPONSE without username, with MESSAGE-INTEGRITY and FINGERPRINT. + in_msg.reset(CreateStunMessage(STUN_BINDING_RESPONSE)); + in_msg->AddAttribute( + new StunXorAddressAttribute(STUN_ATTR_XOR_MAPPED_ADDRESS, kLocalAddr2)); + in_msg->AddMessageIntegrity("rpass"); + in_msg->AddFingerprint(); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_TRUE(out_msg.get() != NULL); + EXPECT_EQ("", username); + + // BINDING-ERROR-RESPONSE without username, with error, M-I, and FINGERPRINT. + in_msg.reset(CreateStunMessage(STUN_BINDING_ERROR_RESPONSE)); + in_msg->AddAttribute(new StunErrorCodeAttribute(STUN_ATTR_ERROR_CODE, + STUN_ERROR_SERVER_ERROR, STUN_ERROR_REASON_SERVER_ERROR)); + in_msg->AddFingerprint(); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_TRUE(out_msg.get() != NULL); + EXPECT_EQ("", username); + ASSERT_TRUE(out_msg->GetErrorCode() != NULL); + EXPECT_EQ(STUN_ERROR_SERVER_ERROR, out_msg->GetErrorCode()->code()); + EXPECT_EQ(std::string(STUN_ERROR_REASON_SERVER_ERROR), + out_msg->GetErrorCode()->reason()); +} + +// This test verifies port can handle ICE messages in Hybrid mode and switches +// ICEPROTO_RFC5245 mode after successfully handling the message. +TEST_F(PortTest, TestHandleStunMessageAsIceInHybridMode) { + // Our port will act as the "remote" port. + rtc::scoped_ptr port( + CreateTestPort(kLocalAddr2, "rfrag", "rpass")); + port->SetIceProtocolType(ICEPROTO_HYBRID); + + rtc::scoped_ptr in_msg, out_msg; + rtc::scoped_ptr buf(new ByteBuffer()); + rtc::SocketAddress addr(kLocalAddr1); + std::string username; + + // BINDING-REQUEST from local to remote with valid ICE username, + // MESSAGE-INTEGRITY, and FINGERPRINT. + in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, + "rfrag:lfrag")); + in_msg->AddMessageIntegrity("rpass"); + in_msg->AddFingerprint(); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_TRUE(out_msg.get() != NULL); + EXPECT_EQ("lfrag", username); + EXPECT_EQ(ICEPROTO_RFC5245, port->IceProtocol()); +} + +// This test verifies port can handle GICE messages in Hybrid mode and switches +// ICEPROTO_GOOGLE mode after successfully handling the message. +TEST_F(PortTest, TestHandleStunMessageAsGiceInHybridMode) { + // Our port will act as the "remote" port. + rtc::scoped_ptr port( + CreateTestPort(kLocalAddr2, "rfrag", "rpass")); + port->SetIceProtocolType(ICEPROTO_HYBRID); + + rtc::scoped_ptr in_msg, out_msg; + rtc::scoped_ptr buf(new ByteBuffer()); + rtc::SocketAddress addr(kLocalAddr1); + std::string username; + + // BINDING-REQUEST from local to remote with valid GICE username and no M-I. + in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, + "rfraglfrag")); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_TRUE(out_msg.get() != NULL); // Succeeds, since this is GICE. + EXPECT_EQ("lfrag", username); + EXPECT_EQ(ICEPROTO_GOOGLE, port->IceProtocol()); +} + +// Verify port is not switched out of RFC5245 mode if GICE message is received +// in that mode. +TEST_F(PortTest, TestHandleStunMessageAsGiceInIceMode) { + // Our port will act as the "remote" port. + rtc::scoped_ptr port( + CreateTestPort(kLocalAddr2, "rfrag", "rpass")); + port->SetIceProtocolType(ICEPROTO_RFC5245); + + rtc::scoped_ptr in_msg, out_msg; + rtc::scoped_ptr buf(new ByteBuffer()); + rtc::SocketAddress addr(kLocalAddr1); + std::string username; + + // BINDING-REQUEST from local to remote with valid GICE username and no M-I. + in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, + "rfraglfrag")); + WriteStunMessage(in_msg.get(), buf.get()); + // Should fail as there is no MI and fingerprint. + EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_EQ(ICEPROTO_RFC5245, port->IceProtocol()); +} + + +// Tests handling of GICE binding requests with missing or incorrect usernames. +TEST_F(PortTest, TestHandleStunMessageAsGiceBadUsername) { + rtc::scoped_ptr port( + CreateTestPort(kLocalAddr2, "rfrag", "rpass")); + port->SetIceProtocolType(ICEPROTO_GOOGLE); + + rtc::scoped_ptr in_msg, out_msg; + rtc::scoped_ptr buf(new ByteBuffer()); + rtc::SocketAddress addr(kLocalAddr1); + std::string username; + + // BINDING-REQUEST with no username. + in_msg.reset(CreateStunMessage(STUN_BINDING_REQUEST)); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_TRUE(out_msg.get() == NULL); + EXPECT_EQ("", username); + EXPECT_EQ(STUN_ERROR_BAD_REQUEST_AS_GICE, port->last_stun_error_code()); + + // BINDING-REQUEST with empty username. + in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "")); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_TRUE(out_msg.get() == NULL); + EXPECT_EQ("", username); + EXPECT_EQ(STUN_ERROR_UNAUTHORIZED_AS_GICE, port->last_stun_error_code()); + + // BINDING-REQUEST with too-short username. + in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "lfra")); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_TRUE(out_msg.get() == NULL); + EXPECT_EQ("", username); + EXPECT_EQ(STUN_ERROR_UNAUTHORIZED_AS_GICE, port->last_stun_error_code()); + + // BINDING-REQUEST with reversed username. + in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, + "lfragrfrag")); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_TRUE(out_msg.get() == NULL); + EXPECT_EQ("", username); + EXPECT_EQ(STUN_ERROR_UNAUTHORIZED_AS_GICE, port->last_stun_error_code()); + + // BINDING-REQUEST with garbage username. + in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, + "abcdefgh")); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_TRUE(out_msg.get() == NULL); + EXPECT_EQ("", username); + EXPECT_EQ(STUN_ERROR_UNAUTHORIZED_AS_GICE, port->last_stun_error_code()); +} + +// Tests handling of ICE binding requests with missing or incorrect usernames. +TEST_F(PortTest, TestHandleStunMessageAsIceBadUsername) { + rtc::scoped_ptr port( + CreateTestPort(kLocalAddr2, "rfrag", "rpass")); + port->SetIceProtocolType(ICEPROTO_RFC5245); + + rtc::scoped_ptr in_msg, out_msg; + rtc::scoped_ptr buf(new ByteBuffer()); + rtc::SocketAddress addr(kLocalAddr1); + std::string username; + + // BINDING-REQUEST with no username. + in_msg.reset(CreateStunMessage(STUN_BINDING_REQUEST)); + in_msg->AddMessageIntegrity("rpass"); + in_msg->AddFingerprint(); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_TRUE(out_msg.get() == NULL); + EXPECT_EQ("", username); + EXPECT_EQ(STUN_ERROR_BAD_REQUEST, port->last_stun_error_code()); + + // BINDING-REQUEST with empty username. + in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "")); + in_msg->AddMessageIntegrity("rpass"); + in_msg->AddFingerprint(); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_TRUE(out_msg.get() == NULL); + EXPECT_EQ("", username); + EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code()); + + // BINDING-REQUEST with too-short username. + in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "rfra")); + in_msg->AddMessageIntegrity("rpass"); + in_msg->AddFingerprint(); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_TRUE(out_msg.get() == NULL); + EXPECT_EQ("", username); + EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code()); + + // BINDING-REQUEST with reversed username. + in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, + "lfrag:rfrag")); + in_msg->AddMessageIntegrity("rpass"); + in_msg->AddFingerprint(); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_TRUE(out_msg.get() == NULL); + EXPECT_EQ("", username); + EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code()); + + // BINDING-REQUEST with garbage username. + in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, + "abcd:efgh")); + in_msg->AddMessageIntegrity("rpass"); + in_msg->AddFingerprint(); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_TRUE(out_msg.get() == NULL); + EXPECT_EQ("", username); + EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code()); +} + +// Test handling STUN messages (as ICE) with missing or malformed M-I. +TEST_F(PortTest, TestHandleStunMessageAsIceBadMessageIntegrity) { + // Our port will act as the "remote" port. + rtc::scoped_ptr port( + CreateTestPort(kLocalAddr2, "rfrag", "rpass")); + port->SetIceProtocolType(ICEPROTO_RFC5245); + + rtc::scoped_ptr in_msg, out_msg; + rtc::scoped_ptr buf(new ByteBuffer()); + rtc::SocketAddress addr(kLocalAddr1); + std::string username; + + // BINDING-REQUEST from local to remote with valid ICE username and + // FINGERPRINT, but no MESSAGE-INTEGRITY. + in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, + "rfrag:lfrag")); + in_msg->AddFingerprint(); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_TRUE(out_msg.get() == NULL); + EXPECT_EQ("", username); + EXPECT_EQ(STUN_ERROR_BAD_REQUEST, port->last_stun_error_code()); + + // BINDING-REQUEST from local to remote with valid ICE username and + // FINGERPRINT, but invalid MESSAGE-INTEGRITY. + in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, + "rfrag:lfrag")); + in_msg->AddMessageIntegrity("invalid"); + in_msg->AddFingerprint(); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_TRUE(out_msg.get() == NULL); + EXPECT_EQ("", username); + EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code()); + + // TODO: BINDING-RESPONSES and BINDING-ERROR-RESPONSES are checked + // by the Connection, not the Port, since they require the remote username. + // Change this test to pass in data via Connection::OnReadPacket instead. +} + +// Test handling STUN messages (as ICE) with missing or malformed FINGERPRINT. +TEST_F(PortTest, TestHandleStunMessageAsIceBadFingerprint) { + // Our port will act as the "remote" port. + rtc::scoped_ptr port( + CreateTestPort(kLocalAddr2, "rfrag", "rpass")); + port->SetIceProtocolType(ICEPROTO_RFC5245); + + rtc::scoped_ptr in_msg, out_msg; + rtc::scoped_ptr buf(new ByteBuffer()); + rtc::SocketAddress addr(kLocalAddr1); + std::string username; + + // BINDING-REQUEST from local to remote with valid ICE username and + // MESSAGE-INTEGRITY, but no FINGERPRINT; GetStunMessage should fail. + in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, + "rfrag:lfrag")); + in_msg->AddMessageIntegrity("rpass"); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_EQ(0, port->last_stun_error_code()); + + // Now, add a fingerprint, but munge the message so it's not valid. + in_msg->AddFingerprint(); + in_msg->SetTransactionID("TESTTESTBADD"); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_EQ(0, port->last_stun_error_code()); + + // Valid BINDING-RESPONSE, except no FINGERPRINT. + in_msg.reset(CreateStunMessage(STUN_BINDING_RESPONSE)); + in_msg->AddAttribute( + new StunXorAddressAttribute(STUN_ATTR_XOR_MAPPED_ADDRESS, kLocalAddr2)); + in_msg->AddMessageIntegrity("rpass"); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_EQ(0, port->last_stun_error_code()); + + // Now, add a fingerprint, but munge the message so it's not valid. + in_msg->AddFingerprint(); + in_msg->SetTransactionID("TESTTESTBADD"); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_EQ(0, port->last_stun_error_code()); + + // Valid BINDING-ERROR-RESPONSE, except no FINGERPRINT. + in_msg.reset(CreateStunMessage(STUN_BINDING_ERROR_RESPONSE)); + in_msg->AddAttribute(new StunErrorCodeAttribute(STUN_ATTR_ERROR_CODE, + STUN_ERROR_SERVER_ERROR, STUN_ERROR_REASON_SERVER_ERROR)); + in_msg->AddMessageIntegrity("rpass"); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_EQ(0, port->last_stun_error_code()); + + // Now, add a fingerprint, but munge the message so it's not valid. + in_msg->AddFingerprint(); + in_msg->SetTransactionID("TESTTESTBADD"); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_EQ(0, port->last_stun_error_code()); +} + +// Test handling of STUN binding indication messages (as ICE). STUN binding +// indications are allowed only to the connection which is in read mode. +TEST_F(PortTest, TestHandleStunBindingIndication) { + rtc::scoped_ptr lport( + CreateTestPort(kLocalAddr2, "lfrag", "lpass")); + lport->SetIceProtocolType(ICEPROTO_RFC5245); + lport->SetIceRole(cricket::ICEROLE_CONTROLLING); + lport->SetIceTiebreaker(kTiebreaker1); + + // Verifying encoding and decoding STUN indication message. + rtc::scoped_ptr in_msg, out_msg; + rtc::scoped_ptr buf(new ByteBuffer()); + rtc::SocketAddress addr(kLocalAddr1); + std::string username; + + in_msg.reset(CreateStunMessage(STUN_BINDING_INDICATION)); + in_msg->AddFingerprint(); + WriteStunMessage(in_msg.get(), buf.get()); + EXPECT_TRUE(lport->GetStunMessage(buf->Data(), buf->Length(), addr, + out_msg.accept(), &username)); + EXPECT_TRUE(out_msg.get() != NULL); + EXPECT_EQ(out_msg->type(), STUN_BINDING_INDICATION); + EXPECT_EQ("", username); + + // Verify connection can handle STUN indication and updates + // last_ping_received. + rtc::scoped_ptr rport( + CreateTestPort(kLocalAddr2, "rfrag", "rpass")); + rport->SetIceProtocolType(ICEPROTO_RFC5245); + rport->SetIceRole(cricket::ICEROLE_CONTROLLED); + rport->SetIceTiebreaker(kTiebreaker2); + + lport->PrepareAddress(); + rport->PrepareAddress(); + ASSERT_FALSE(lport->Candidates().empty()); + ASSERT_FALSE(rport->Candidates().empty()); + + Connection* lconn = lport->CreateConnection(rport->Candidates()[0], + Port::ORIGIN_MESSAGE); + Connection* rconn = rport->CreateConnection(lport->Candidates()[0], + Port::ORIGIN_MESSAGE); + rconn->Ping(0); + + ASSERT_TRUE_WAIT(rport->last_stun_msg() != NULL, 1000); + IceMessage* msg = rport->last_stun_msg(); + EXPECT_EQ(STUN_BINDING_REQUEST, msg->type()); + // Send rport binding request to lport. + lconn->OnReadPacket(rport->last_stun_buf()->Data(), + rport->last_stun_buf()->Length(), + rtc::PacketTime()); + ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000); + EXPECT_EQ(STUN_BINDING_RESPONSE, lport->last_stun_msg()->type()); + uint32 last_ping_received1 = lconn->last_ping_received(); + + // Adding a delay of 100ms. + rtc::Thread::Current()->ProcessMessages(100); + // Pinging lconn using stun indication message. + lconn->OnReadPacket(buf->Data(), buf->Length(), rtc::PacketTime()); + uint32 last_ping_received2 = lconn->last_ping_received(); + EXPECT_GT(last_ping_received2, last_ping_received1); +} + +TEST_F(PortTest, TestComputeCandidatePriority) { + rtc::scoped_ptr port( + CreateTestPort(kLocalAddr1, "name", "pass")); + port->set_type_preference(90); + port->set_component(177); + port->AddCandidateAddress(SocketAddress("192.168.1.4", 1234)); + port->AddCandidateAddress(SocketAddress("2001:db8::1234", 1234)); + port->AddCandidateAddress(SocketAddress("fc12:3456::1234", 1234)); + port->AddCandidateAddress(SocketAddress("::ffff:192.168.1.4", 1234)); + port->AddCandidateAddress(SocketAddress("::192.168.1.4", 1234)); + port->AddCandidateAddress(SocketAddress("2002::1234:5678", 1234)); + port->AddCandidateAddress(SocketAddress("2001::1234:5678", 1234)); + port->AddCandidateAddress(SocketAddress("fecf::1234:5678", 1234)); + port->AddCandidateAddress(SocketAddress("3ffe::1234:5678", 1234)); + // These should all be: + // (90 << 24) | ([rfc3484 pref value] << 8) | (256 - 177) + uint32 expected_priority_v4 = 1509957199U; + uint32 expected_priority_v6 = 1509959759U; + uint32 expected_priority_ula = 1509962319U; + uint32 expected_priority_v4mapped = expected_priority_v4; + uint32 expected_priority_v4compat = 1509949775U; + uint32 expected_priority_6to4 = 1509954639U; + uint32 expected_priority_teredo = 1509952079U; + uint32 expected_priority_sitelocal = 1509949775U; + uint32 expected_priority_6bone = 1509949775U; + ASSERT_EQ(expected_priority_v4, port->Candidates()[0].priority()); + ASSERT_EQ(expected_priority_v6, port->Candidates()[1].priority()); + ASSERT_EQ(expected_priority_ula, port->Candidates()[2].priority()); + ASSERT_EQ(expected_priority_v4mapped, port->Candidates()[3].priority()); + ASSERT_EQ(expected_priority_v4compat, port->Candidates()[4].priority()); + ASSERT_EQ(expected_priority_6to4, port->Candidates()[5].priority()); + ASSERT_EQ(expected_priority_teredo, port->Candidates()[6].priority()); + ASSERT_EQ(expected_priority_sitelocal, port->Candidates()[7].priority()); + ASSERT_EQ(expected_priority_6bone, port->Candidates()[8].priority()); +} + +TEST_F(PortTest, TestPortProxyProperties) { + rtc::scoped_ptr port( + CreateTestPort(kLocalAddr1, "name", "pass")); + port->SetIceRole(cricket::ICEROLE_CONTROLLING); + port->SetIceTiebreaker(kTiebreaker1); + + // Create a proxy port. + rtc::scoped_ptr proxy(new PortProxy()); + proxy->set_impl(port.get()); + EXPECT_EQ(port->Type(), proxy->Type()); + EXPECT_EQ(port->Network(), proxy->Network()); + EXPECT_EQ(port->GetIceRole(), proxy->GetIceRole()); + EXPECT_EQ(port->IceTiebreaker(), proxy->IceTiebreaker()); +} + +// In the case of shared socket, one port may be shared by local and stun. +// Test that candidates with different types will have different foundation. +TEST_F(PortTest, TestFoundation) { + rtc::scoped_ptr testport( + CreateTestPort(kLocalAddr1, "name", "pass")); + testport->AddCandidateAddress(kLocalAddr1, kLocalAddr1, + LOCAL_PORT_TYPE, + cricket::ICE_TYPE_PREFERENCE_HOST, false); + testport->AddCandidateAddress(kLocalAddr2, kLocalAddr1, + STUN_PORT_TYPE, + cricket::ICE_TYPE_PREFERENCE_SRFLX, true); + EXPECT_NE(testport->Candidates()[0].foundation(), + testport->Candidates()[1].foundation()); +} + +// This test verifies the foundation of different types of ICE candidates. +TEST_F(PortTest, TestCandidateFoundation) { + rtc::scoped_ptr nat_server( + CreateNatServer(kNatAddr1, NAT_OPEN_CONE)); + rtc::scoped_ptr udpport1(CreateUdpPort(kLocalAddr1)); + udpport1->PrepareAddress(); + rtc::scoped_ptr udpport2(CreateUdpPort(kLocalAddr1)); + udpport2->PrepareAddress(); + EXPECT_EQ(udpport1->Candidates()[0].foundation(), + udpport2->Candidates()[0].foundation()); + rtc::scoped_ptr tcpport1(CreateTcpPort(kLocalAddr1)); + tcpport1->PrepareAddress(); + rtc::scoped_ptr tcpport2(CreateTcpPort(kLocalAddr1)); + tcpport2->PrepareAddress(); + EXPECT_EQ(tcpport1->Candidates()[0].foundation(), + tcpport2->Candidates()[0].foundation()); + rtc::scoped_ptr stunport( + CreateStunPort(kLocalAddr1, nat_socket_factory1())); + stunport->PrepareAddress(); + ASSERT_EQ_WAIT(1U, stunport->Candidates().size(), kTimeout); + EXPECT_NE(tcpport1->Candidates()[0].foundation(), + stunport->Candidates()[0].foundation()); + EXPECT_NE(tcpport2->Candidates()[0].foundation(), + stunport->Candidates()[0].foundation()); + EXPECT_NE(udpport1->Candidates()[0].foundation(), + stunport->Candidates()[0].foundation()); + EXPECT_NE(udpport2->Candidates()[0].foundation(), + stunport->Candidates()[0].foundation()); + // Verify GTURN candidate foundation. + rtc::scoped_ptr relayport( + CreateGturnPort(kLocalAddr1)); + relayport->AddServerAddress( + cricket::ProtocolAddress(kRelayUdpIntAddr, cricket::PROTO_UDP)); + relayport->PrepareAddress(); + ASSERT_EQ_WAIT(1U, relayport->Candidates().size(), kTimeout); + EXPECT_NE(udpport1->Candidates()[0].foundation(), + relayport->Candidates()[0].foundation()); + EXPECT_NE(udpport2->Candidates()[0].foundation(), + relayport->Candidates()[0].foundation()); + // Verifying TURN candidate foundation. + rtc::scoped_ptr turnport1(CreateTurnPort( + kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP)); + turnport1->PrepareAddress(); + ASSERT_EQ_WAIT(1U, turnport1->Candidates().size(), kTimeout); + EXPECT_NE(udpport1->Candidates()[0].foundation(), + turnport1->Candidates()[0].foundation()); + EXPECT_NE(udpport2->Candidates()[0].foundation(), + turnport1->Candidates()[0].foundation()); + EXPECT_NE(stunport->Candidates()[0].foundation(), + turnport1->Candidates()[0].foundation()); + rtc::scoped_ptr turnport2(CreateTurnPort( + kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP)); + turnport2->PrepareAddress(); + ASSERT_EQ_WAIT(1U, turnport2->Candidates().size(), kTimeout); + EXPECT_EQ(turnport1->Candidates()[0].foundation(), + turnport2->Candidates()[0].foundation()); + + // Running a second turn server, to get different base IP address. + SocketAddress kTurnUdpIntAddr2("99.99.98.4", STUN_SERVER_PORT); + SocketAddress kTurnUdpExtAddr2("99.99.98.5", 0); + TestTurnServer turn_server2( + rtc::Thread::Current(), kTurnUdpIntAddr2, kTurnUdpExtAddr2); + rtc::scoped_ptr turnport3(CreateTurnPort( + kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP, + kTurnUdpIntAddr2)); + turnport3->PrepareAddress(); + ASSERT_EQ_WAIT(1U, turnport3->Candidates().size(), kTimeout); + EXPECT_NE(turnport3->Candidates()[0].foundation(), + turnport2->Candidates()[0].foundation()); +} + +// This test verifies the related addresses of different types of +// ICE candiates. +TEST_F(PortTest, TestCandidateRelatedAddress) { + rtc::scoped_ptr nat_server( + CreateNatServer(kNatAddr1, NAT_OPEN_CONE)); + rtc::scoped_ptr udpport(CreateUdpPort(kLocalAddr1)); + udpport->PrepareAddress(); + // For UDPPort, related address will be empty. + EXPECT_TRUE(udpport->Candidates()[0].related_address().IsNil()); + // Testing related address for stun candidates. + // For stun candidate related address must be equal to the base + // socket address. + rtc::scoped_ptr stunport( + CreateStunPort(kLocalAddr1, nat_socket_factory1())); + stunport->PrepareAddress(); + ASSERT_EQ_WAIT(1U, stunport->Candidates().size(), kTimeout); + // Check STUN candidate address. + EXPECT_EQ(stunport->Candidates()[0].address().ipaddr(), + kNatAddr1.ipaddr()); + // Check STUN candidate related address. + EXPECT_EQ(stunport->Candidates()[0].related_address(), + stunport->GetLocalAddress()); + // Verifying the related address for the GTURN candidates. + // NOTE: In case of GTURN related address will be equal to the mapped + // address, but address(mapped) will not be XOR. + rtc::scoped_ptr relayport( + CreateGturnPort(kLocalAddr1)); + relayport->AddServerAddress( + cricket::ProtocolAddress(kRelayUdpIntAddr, cricket::PROTO_UDP)); + relayport->PrepareAddress(); + ASSERT_EQ_WAIT(1U, relayport->Candidates().size(), kTimeout); + // For Gturn related address is set to "0.0.0.0:0" + EXPECT_EQ(rtc::SocketAddress(), + relayport->Candidates()[0].related_address()); + // Verifying the related address for TURN candidate. + // For TURN related address must be equal to the mapped address. + rtc::scoped_ptr turnport(CreateTurnPort( + kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP)); + turnport->PrepareAddress(); + ASSERT_EQ_WAIT(1U, turnport->Candidates().size(), kTimeout); + EXPECT_EQ(kTurnUdpExtAddr.ipaddr(), + turnport->Candidates()[0].address().ipaddr()); + EXPECT_EQ(kNatAddr1.ipaddr(), + turnport->Candidates()[0].related_address().ipaddr()); +} + +// Test priority value overflow handling when preference is set to 3. +TEST_F(PortTest, TestCandidatePreference) { + cricket::Candidate cand1; + cand1.set_preference(3); + cricket::Candidate cand2; + cand2.set_preference(1); + EXPECT_TRUE(cand1.preference() > cand2.preference()); +} + +// Test the Connection priority is calculated correctly. +TEST_F(PortTest, TestConnectionPriority) { + rtc::scoped_ptr lport( + CreateTestPort(kLocalAddr1, "lfrag", "lpass")); + lport->set_type_preference(cricket::ICE_TYPE_PREFERENCE_HOST); + rtc::scoped_ptr rport( + CreateTestPort(kLocalAddr2, "rfrag", "rpass")); + rport->set_type_preference(cricket::ICE_TYPE_PREFERENCE_RELAY); + lport->set_component(123); + lport->AddCandidateAddress(SocketAddress("192.168.1.4", 1234)); + rport->set_component(23); + rport->AddCandidateAddress(SocketAddress("10.1.1.100", 1234)); + + EXPECT_EQ(0x7E001E85U, lport->Candidates()[0].priority()); + EXPECT_EQ(0x2001EE9U, rport->Candidates()[0].priority()); + + // RFC 5245 + // pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0) + lport->SetIceRole(cricket::ICEROLE_CONTROLLING); + rport->SetIceRole(cricket::ICEROLE_CONTROLLED); + Connection* lconn = lport->CreateConnection( + rport->Candidates()[0], Port::ORIGIN_MESSAGE); +#if defined(WEBRTC_WIN) + EXPECT_EQ(0x2001EE9FC003D0BU, lconn->priority()); +#else + EXPECT_EQ(0x2001EE9FC003D0BLLU, lconn->priority()); +#endif + + lport->SetIceRole(cricket::ICEROLE_CONTROLLED); + rport->SetIceRole(cricket::ICEROLE_CONTROLLING); + Connection* rconn = rport->CreateConnection( + lport->Candidates()[0], Port::ORIGIN_MESSAGE); +#if defined(WEBRTC_WIN) + EXPECT_EQ(0x2001EE9FC003D0AU, rconn->priority()); +#else + EXPECT_EQ(0x2001EE9FC003D0ALLU, rconn->priority()); +#endif +} + +TEST_F(PortTest, TestWritableState) { + UDPPort* port1 = CreateUdpPort(kLocalAddr1); + UDPPort* port2 = CreateUdpPort(kLocalAddr2); + + // Set up channels. + TestChannel ch1(port1, port2); + TestChannel ch2(port2, port1); + + // Acquire addresses. + ch1.Start(); + ch2.Start(); + ASSERT_EQ_WAIT(1, ch1.complete_count(), kTimeout); + ASSERT_EQ_WAIT(1, ch2.complete_count(), kTimeout); + + // Send a ping from src to dst. + ch1.CreateConnection(); + ASSERT_TRUE(ch1.conn() != NULL); + EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state()); + EXPECT_TRUE_WAIT(ch1.conn()->connected(), kTimeout); // for TCP connect + ch1.Ping(); + WAIT(!ch2.remote_address().IsNil(), kTimeout); + + // Data should be unsendable until the connection is accepted. + char data[] = "abcd"; + int data_size = ARRAY_SIZE(data); + rtc::PacketOptions options; + EXPECT_EQ(SOCKET_ERROR, ch1.conn()->Send(data, data_size, options)); + + // Accept the connection to return the binding response, transition to + // writable, and allow data to be sent. + ch2.AcceptConnection(); + EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(), + kTimeout); + EXPECT_EQ(data_size, ch1.conn()->Send(data, data_size, options)); + + // Ask the connection to update state as if enough time has passed to lose + // full writability and 5 pings went unresponded to. We'll accomplish the + // latter by sending pings but not pumping messages. + for (uint32 i = 1; i <= CONNECTION_WRITE_CONNECT_FAILURES; ++i) { + ch1.Ping(i); + } + uint32 unreliable_timeout_delay = CONNECTION_WRITE_CONNECT_TIMEOUT + 500u; + ch1.conn()->UpdateState(unreliable_timeout_delay); + EXPECT_EQ(Connection::STATE_WRITE_UNRELIABLE, ch1.conn()->write_state()); + + // Data should be able to be sent in this state. + EXPECT_EQ(data_size, ch1.conn()->Send(data, data_size, options)); + + // And now allow the other side to process the pings and send binding + // responses. + EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(), + kTimeout); + + // Wait long enough for a full timeout (past however long we've already + // waited). + for (uint32 i = 1; i <= CONNECTION_WRITE_CONNECT_FAILURES; ++i) { + ch1.Ping(unreliable_timeout_delay + i); + } + ch1.conn()->UpdateState(unreliable_timeout_delay + CONNECTION_WRITE_TIMEOUT + + 500u); + EXPECT_EQ(Connection::STATE_WRITE_TIMEOUT, ch1.conn()->write_state()); + + // Now that the connection has completely timed out, data send should fail. + EXPECT_EQ(SOCKET_ERROR, ch1.conn()->Send(data, data_size, options)); + + ch1.Stop(); + ch2.Stop(); +} + +TEST_F(PortTest, TestTimeoutForNeverWritable) { + UDPPort* port1 = CreateUdpPort(kLocalAddr1); + UDPPort* port2 = CreateUdpPort(kLocalAddr2); + + // Set up channels. + TestChannel ch1(port1, port2); + TestChannel ch2(port2, port1); + + // Acquire addresses. + ch1.Start(); + ch2.Start(); + + ch1.CreateConnection(); + ASSERT_TRUE(ch1.conn() != NULL); + EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state()); + + // Attempt to go directly to write timeout. + for (uint32 i = 1; i <= CONNECTION_WRITE_CONNECT_FAILURES; ++i) { + ch1.Ping(i); + } + ch1.conn()->UpdateState(CONNECTION_WRITE_TIMEOUT + 500u); + EXPECT_EQ(Connection::STATE_WRITE_TIMEOUT, ch1.conn()->write_state()); +} + +// This test verifies the connection setup between ICEMODE_FULL +// and ICEMODE_LITE. +// In this test |ch1| behaves like FULL mode client and we have created +// port which responds to the ping message just like LITE client. +TEST_F(PortTest, TestIceLiteConnectivity) { + TestPort* ice_full_port = CreateTestPort( + kLocalAddr1, "lfrag", "lpass", cricket::ICEPROTO_RFC5245, + cricket::ICEROLE_CONTROLLING, kTiebreaker1); + + rtc::scoped_ptr ice_lite_port(CreateTestPort( + kLocalAddr2, "rfrag", "rpass", cricket::ICEPROTO_RFC5245, + cricket::ICEROLE_CONTROLLED, kTiebreaker2)); + // Setup TestChannel. This behaves like FULL mode client. + TestChannel ch1(ice_full_port, ice_lite_port.get()); + ch1.SetIceMode(ICEMODE_FULL); + + // Start gathering candidates. + ch1.Start(); + ice_lite_port->PrepareAddress(); + + ASSERT_EQ_WAIT(1, ch1.complete_count(), kTimeout); + ASSERT_FALSE(ice_lite_port->Candidates().empty()); + + ch1.CreateConnection(); + ASSERT_TRUE(ch1.conn() != NULL); + EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state()); + + // Send ping from full mode client. + // This ping must not have USE_CANDIDATE_ATTR. + ch1.Ping(); + + // Verify stun ping is without USE_CANDIDATE_ATTR. Getting message directly + // from port. + ASSERT_TRUE_WAIT(ice_full_port->last_stun_msg() != NULL, 1000); + IceMessage* msg = ice_full_port->last_stun_msg(); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) == NULL); + + // Respond with a BINDING-RESPONSE from litemode client. + // NOTE: Ideally we should't create connection at this stage from lite + // port, as it should be done only after receiving ping with USE_CANDIDATE. + // But we need a connection to send a response message. + ice_lite_port->CreateConnection( + ice_full_port->Candidates()[0], cricket::Port::ORIGIN_MESSAGE); + rtc::scoped_ptr request(CopyStunMessage(msg)); + ice_lite_port->SendBindingResponse( + request.get(), ice_full_port->Candidates()[0].address()); + + // Feeding the respone message from litemode to the full mode connection. + ch1.conn()->OnReadPacket(ice_lite_port->last_stun_buf()->Data(), + ice_lite_port->last_stun_buf()->Length(), + rtc::PacketTime()); + // Verifying full mode connection becomes writable from the response. + EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(), + kTimeout); + EXPECT_TRUE_WAIT(ch1.nominated(), kTimeout); + + // Clear existing stun messsages. Otherwise we will process old stun + // message right after we send ping. + ice_full_port->Reset(); + // Send ping. This must have USE_CANDIDATE_ATTR. + ch1.Ping(); + ASSERT_TRUE_WAIT(ice_full_port->last_stun_msg() != NULL, 1000); + msg = ice_full_port->last_stun_msg(); + EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) != NULL); + ch1.Stop(); +} + +// This test case verifies that the CONTROLLING port does not time out. +TEST_F(PortTest, TestControllingNoTimeout) { + SetIceProtocolType(cricket::ICEPROTO_RFC5245); + UDPPort* port1 = CreateUdpPort(kLocalAddr1); + ConnectToSignalDestroyed(port1); + port1->set_timeout_delay(10); // milliseconds + port1->SetIceRole(cricket::ICEROLE_CONTROLLING); + port1->SetIceTiebreaker(kTiebreaker1); + + UDPPort* port2 = CreateUdpPort(kLocalAddr2); + port2->SetIceRole(cricket::ICEROLE_CONTROLLED); + port2->SetIceTiebreaker(kTiebreaker2); + + // Set up channels and ensure both ports will be deleted. + TestChannel ch1(port1, port2); + TestChannel ch2(port2, port1); + + // Simulate a connection that succeeds, and then is destroyed. + ConnectAndDisconnectChannels(&ch1, &ch2); + + // After the connection is destroyed, the port should not be destroyed. + rtc::Thread::Current()->ProcessMessages(kTimeout); + EXPECT_FALSE(destroyed()); +} + +// This test case verifies that the CONTROLLED port does time out, but only +// after connectivity is lost. +TEST_F(PortTest, TestControlledTimeout) { + SetIceProtocolType(cricket::ICEPROTO_RFC5245); + UDPPort* port1 = CreateUdpPort(kLocalAddr1); + port1->SetIceRole(cricket::ICEROLE_CONTROLLING); + port1->SetIceTiebreaker(kTiebreaker1); + + UDPPort* port2 = CreateUdpPort(kLocalAddr2); + ConnectToSignalDestroyed(port2); + port2->set_timeout_delay(10); // milliseconds + port2->SetIceRole(cricket::ICEROLE_CONTROLLED); + port2->SetIceTiebreaker(kTiebreaker2); + + // The connection must not be destroyed before a connection is attempted. + EXPECT_FALSE(destroyed()); + + port1->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT); + port2->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT); + + // Set up channels and ensure both ports will be deleted. + TestChannel ch1(port1, port2); + TestChannel ch2(port2, port1); + + // Simulate a connection that succeeds, and then is destroyed. + ConnectAndDisconnectChannels(&ch1, &ch2); + + // The controlled port should be destroyed after 10 milliseconds. + EXPECT_TRUE_WAIT(destroyed(), kTimeout); +} diff --git a/webrtc/p2p/base/portallocator.cc b/webrtc/p2p/base/portallocator.cc new file mode 100644 index 000000000..86133474d --- /dev/null +++ b/webrtc/p2p/base/portallocator.cc @@ -0,0 +1,92 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/portallocator.h" + +#include "webrtc/p2p/base/portallocatorsessionproxy.h" + +namespace cricket { + +PortAllocatorSession::PortAllocatorSession(const std::string& content_name, + int component, + const std::string& ice_ufrag, + const std::string& ice_pwd, + uint32 flags) + : content_name_(content_name), + component_(component), + flags_(flags), + generation_(0), + // If PORTALLOCATOR_ENABLE_SHARED_UFRAG flag is not enabled, ignore the + // incoming ufrag and pwd, which will cause each Port to generate one + // by itself. + username_(flags_ & PORTALLOCATOR_ENABLE_SHARED_UFRAG ? ice_ufrag : ""), + password_(flags_ & PORTALLOCATOR_ENABLE_SHARED_UFRAG ? ice_pwd : "") { +} + +PortAllocator::~PortAllocator() { + for (SessionMuxerMap::iterator iter = muxers_.begin(); + iter != muxers_.end(); ++iter) { + delete iter->second; + } +} + +PortAllocatorSession* PortAllocator::CreateSession( + const std::string& sid, + const std::string& content_name, + int component, + const std::string& ice_ufrag, + const std::string& ice_pwd) { + if (flags_ & PORTALLOCATOR_ENABLE_BUNDLE) { + // If we just use |sid| as key in identifying PortAllocatorSessionMuxer, + // ICE restart will not result in different candidates, as |sid| will + // be same. To yield different candiates we are using combination of + // |ice_ufrag| and |ice_pwd|. + // Ideally |ice_ufrag| and |ice_pwd| should change together, but + // there can be instances where only ice_pwd will be changed. + std::string key_str = ice_ufrag + ":" + ice_pwd; + PortAllocatorSessionMuxer* muxer = GetSessionMuxer(key_str); + if (!muxer) { + PortAllocatorSession* session_impl = CreateSessionInternal( + content_name, component, ice_ufrag, ice_pwd); + // Create PortAllocatorSessionMuxer object for |session_impl|. + muxer = new PortAllocatorSessionMuxer(session_impl); + muxer->SignalDestroyed.connect( + this, &PortAllocator::OnSessionMuxerDestroyed); + // Add PortAllocatorSession to the map. + muxers_[key_str] = muxer; + } + PortAllocatorSessionProxy* proxy = + new PortAllocatorSessionProxy(content_name, component, flags_); + muxer->RegisterSessionProxy(proxy); + return proxy; + } + return CreateSessionInternal(content_name, component, ice_ufrag, ice_pwd); +} + +PortAllocatorSessionMuxer* PortAllocator::GetSessionMuxer( + const std::string& key) const { + SessionMuxerMap::const_iterator iter = muxers_.find(key); + if (iter != muxers_.end()) + return iter->second; + return NULL; +} + +void PortAllocator::OnSessionMuxerDestroyed( + PortAllocatorSessionMuxer* session) { + SessionMuxerMap::iterator iter; + for (iter = muxers_.begin(); iter != muxers_.end(); ++iter) { + if (iter->second == session) + break; + } + if (iter != muxers_.end()) + muxers_.erase(iter); +} + +} // namespace cricket diff --git a/webrtc/p2p/base/portallocator.h b/webrtc/p2p/base/portallocator.h new file mode 100644 index 000000000..65aab44c6 --- /dev/null +++ b/webrtc/p2p/base/portallocator.h @@ -0,0 +1,192 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_PORTALLOCATOR_H_ +#define WEBRTC_P2P_BASE_PORTALLOCATOR_H_ + +#include +#include + +#include "webrtc/p2p/base/portinterface.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/proxyinfo.h" +#include "webrtc/base/sigslot.h" + +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. + +enum { + PORTALLOCATOR_DISABLE_UDP = 0x01, + PORTALLOCATOR_DISABLE_STUN = 0x02, + PORTALLOCATOR_DISABLE_RELAY = 0x04, + PORTALLOCATOR_DISABLE_TCP = 0x08, + PORTALLOCATOR_ENABLE_SHAKER = 0x10, + PORTALLOCATOR_ENABLE_BUNDLE = 0x20, + PORTALLOCATOR_ENABLE_IPV6 = 0x40, + PORTALLOCATOR_ENABLE_SHARED_UFRAG = 0x80, + PORTALLOCATOR_ENABLE_SHARED_SOCKET = 0x100, + PORTALLOCATOR_ENABLE_STUN_RETRANSMIT_ATTRIBUTE = 0x200, +}; + +const uint32 kDefaultPortAllocatorFlags = 0; + +const uint32 kDefaultStepDelay = 1000; // 1 sec step delay. +// As per RFC 5245 Appendix B.1, STUN transactions need to be paced at certain +// internal. Less than 20ms is not acceptable. We choose 50ms as our default. +const uint32 kMinimumStepDelay = 50; + +// CF = CANDIDATE FILTER +enum { + CF_NONE = 0x0, + CF_HOST = 0x1, + CF_REFLEXIVE = 0x2, + CF_RELAY = 0x4, + CF_ALL = 0x7, +}; + +class PortAllocatorSessionMuxer; + +class PortAllocatorSession : public sigslot::has_slots<> { + public: + // Content name passed in mostly for logging and debugging. + // TODO(mallinath) - Change username and password to ice_ufrag and ice_pwd. + PortAllocatorSession(const std::string& content_name, + int component, + const std::string& username, + const std::string& password, + uint32 flags); + + // Subclasses should clean up any ports created. + virtual ~PortAllocatorSession() {} + + uint32 flags() const { return flags_; } + void set_flags(uint32 flags) { flags_ = flags; } + std::string content_name() const { return content_name_; } + int component() const { return component_; } + + // Starts gathering STUN and Relay configurations. + virtual void StartGettingPorts() = 0; + virtual void StopGettingPorts() = 0; + virtual bool IsGettingPorts() = 0; + + sigslot::signal2 SignalPortReady; + sigslot::signal2&> SignalCandidatesReady; + sigslot::signal1 SignalCandidatesAllocationDone; + + virtual uint32 generation() { return generation_; } + virtual void set_generation(uint32 generation) { generation_ = generation; } + sigslot::signal1 SignalDestroyed; + + protected: + const std::string& username() const { return username_; } + const std::string& password() const { return password_; } + + std::string content_name_; + int component_; + + private: + uint32 flags_; + uint32 generation_; + std::string username_; + std::string password_; +}; + +class PortAllocator : public sigslot::has_slots<> { + public: + PortAllocator() : + flags_(kDefaultPortAllocatorFlags), + min_port_(0), + max_port_(0), + step_delay_(kDefaultStepDelay), + allow_tcp_listen_(true), + candidate_filter_(CF_ALL) { + // This will allow us to have old behavior on non webrtc clients. + } + virtual ~PortAllocator(); + + PortAllocatorSession* CreateSession( + const std::string& sid, + const std::string& content_name, + int component, + const std::string& ice_ufrag, + const std::string& ice_pwd); + + PortAllocatorSessionMuxer* GetSessionMuxer(const std::string& key) const; + void OnSessionMuxerDestroyed(PortAllocatorSessionMuxer* session); + + uint32 flags() const { return flags_; } + void set_flags(uint32 flags) { flags_ = flags; } + + const std::string& user_agent() const { return agent_; } + const rtc::ProxyInfo& proxy() const { return proxy_; } + void set_proxy(const std::string& agent, const rtc::ProxyInfo& proxy) { + agent_ = agent; + proxy_ = proxy; + } + + // Gets/Sets the port range to use when choosing client ports. + int min_port() const { return min_port_; } + int max_port() const { return max_port_; } + bool SetPortRange(int min_port, int max_port) { + if (min_port > max_port) { + return false; + } + + min_port_ = min_port; + max_port_ = max_port; + return true; + } + + uint32 step_delay() const { return step_delay_; } + void set_step_delay(uint32 delay) { + step_delay_ = delay; + } + + bool allow_tcp_listen() const { return allow_tcp_listen_; } + void set_allow_tcp_listen(bool allow_tcp_listen) { + allow_tcp_listen_ = allow_tcp_listen; + } + + uint32 candidate_filter() { return candidate_filter_; } + bool set_candidate_filter(uint32 filter) { + // TODO(mallinath) - Do transition check? + candidate_filter_ = filter; + return true; + } + + protected: + virtual PortAllocatorSession* CreateSessionInternal( + const std::string& content_name, + int component, + const std::string& ice_ufrag, + const std::string& ice_pwd) = 0; + + typedef std::map SessionMuxerMap; + + uint32 flags_; + std::string agent_; + rtc::ProxyInfo proxy_; + int min_port_; + int max_port_; + uint32 step_delay_; + SessionMuxerMap muxers_; + bool allow_tcp_listen_; + uint32 candidate_filter_; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_PORTALLOCATOR_H_ diff --git a/webrtc/p2p/base/portallocatorsessionproxy.cc b/webrtc/p2p/base/portallocatorsessionproxy.cc new file mode 100644 index 000000000..f5ce9a4a6 --- /dev/null +++ b/webrtc/p2p/base/portallocatorsessionproxy.cc @@ -0,0 +1,222 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/portallocatorsessionproxy.h" + +#include "webrtc/p2p/base/portallocator.h" +#include "webrtc/p2p/base/portproxy.h" +#include "webrtc/base/thread.h" + +namespace cricket { + +enum { + MSG_SEND_ALLOCATION_DONE = 1, + MSG_SEND_ALLOCATED_PORTS, +}; + +typedef rtc::TypedMessageData ProxyObjData; + +PortAllocatorSessionMuxer::PortAllocatorSessionMuxer( + PortAllocatorSession* session) + : worker_thread_(rtc::Thread::Current()), + session_(session), + candidate_done_signal_received_(false) { + session_->SignalPortReady.connect( + this, &PortAllocatorSessionMuxer::OnPortReady); + session_->SignalCandidatesAllocationDone.connect( + this, &PortAllocatorSessionMuxer::OnCandidatesAllocationDone); +} + +PortAllocatorSessionMuxer::~PortAllocatorSessionMuxer() { + for (size_t i = 0; i < session_proxies_.size(); ++i) + delete session_proxies_[i]; + + SignalDestroyed(this); +} + +void PortAllocatorSessionMuxer::RegisterSessionProxy( + PortAllocatorSessionProxy* session_proxy) { + session_proxies_.push_back(session_proxy); + session_proxy->SignalDestroyed.connect( + this, &PortAllocatorSessionMuxer::OnSessionProxyDestroyed); + session_proxy->set_impl(session_.get()); + + // Populate new proxy session with the information available in the actual + // implementation. + if (!ports_.empty()) { + worker_thread_->Post( + this, MSG_SEND_ALLOCATED_PORTS, new ProxyObjData(session_proxy)); + } + + if (candidate_done_signal_received_) { + worker_thread_->Post( + this, MSG_SEND_ALLOCATION_DONE, new ProxyObjData(session_proxy)); + } +} + +void PortAllocatorSessionMuxer::OnCandidatesAllocationDone( + PortAllocatorSession* session) { + candidate_done_signal_received_ = true; +} + +void PortAllocatorSessionMuxer::OnPortReady(PortAllocatorSession* session, + PortInterface* port) { + ASSERT(session == session_.get()); + ports_.push_back(port); + port->SignalDestroyed.connect( + this, &PortAllocatorSessionMuxer::OnPortDestroyed); +} + +void PortAllocatorSessionMuxer::OnPortDestroyed(PortInterface* port) { + std::vector::iterator it = + std::find(ports_.begin(), ports_.end(), port); + if (it != ports_.end()) + ports_.erase(it); +} + +void PortAllocatorSessionMuxer::OnSessionProxyDestroyed( + PortAllocatorSession* proxy) { + + std::vector::iterator it = + std::find(session_proxies_.begin(), session_proxies_.end(), proxy); + if (it != session_proxies_.end()) { + session_proxies_.erase(it); + } + + if (session_proxies_.empty()) { + // Destroy PortAllocatorSession and its associated muxer object if all + // proxies belonging to this session are already destroyed. + delete this; + } +} + +void PortAllocatorSessionMuxer::OnMessage(rtc::Message *pmsg) { + ProxyObjData* proxy = static_cast(pmsg->pdata); + switch (pmsg->message_id) { + case MSG_SEND_ALLOCATION_DONE: + SendAllocationDone_w(proxy->data()); + delete proxy; + break; + case MSG_SEND_ALLOCATED_PORTS: + SendAllocatedPorts_w(proxy->data()); + delete proxy; + break; + default: + ASSERT(false); + break; + } +} + +void PortAllocatorSessionMuxer::SendAllocationDone_w( + PortAllocatorSessionProxy* proxy) { + std::vector::iterator iter = + std::find(session_proxies_.begin(), session_proxies_.end(), proxy); + if (iter != session_proxies_.end()) { + proxy->OnCandidatesAllocationDone(session_.get()); + } +} + +void PortAllocatorSessionMuxer::SendAllocatedPorts_w( + PortAllocatorSessionProxy* proxy) { + std::vector::iterator iter = + std::find(session_proxies_.begin(), session_proxies_.end(), proxy); + if (iter != session_proxies_.end()) { + for (size_t i = 0; i < ports_.size(); ++i) { + PortInterface* port = ports_[i]; + proxy->OnPortReady(session_.get(), port); + // If port already has candidates, send this to the clients of proxy + // session. This can happen if proxy is created later than the actual + // implementation. + if (!port->Candidates().empty()) { + proxy->OnCandidatesReady(session_.get(), port->Candidates()); + } + } + } +} + +PortAllocatorSessionProxy::~PortAllocatorSessionProxy() { + std::map::iterator it; + for (it = proxy_ports_.begin(); it != proxy_ports_.end(); it++) + delete it->second; + + SignalDestroyed(this); +} + +void PortAllocatorSessionProxy::set_impl( + PortAllocatorSession* session) { + impl_ = session; + + impl_->SignalCandidatesReady.connect( + this, &PortAllocatorSessionProxy::OnCandidatesReady); + impl_->SignalPortReady.connect( + this, &PortAllocatorSessionProxy::OnPortReady); + impl_->SignalCandidatesAllocationDone.connect( + this, &PortAllocatorSessionProxy::OnCandidatesAllocationDone); +} + +void PortAllocatorSessionProxy::StartGettingPorts() { + ASSERT(impl_ != NULL); + // Since all proxies share a common PortAllocatorSession, this check will + // prohibit sending multiple STUN ping messages to the stun server, which + // is a problem on Chrome. GetInitialPorts() and StartGetAllPorts() called + // from the worker thread and are called together from TransportChannel, + // checking for IsGettingAllPorts() for GetInitialPorts() will not be a + // problem. + if (!impl_->IsGettingPorts()) { + impl_->StartGettingPorts(); + } +} + +void PortAllocatorSessionProxy::StopGettingPorts() { + ASSERT(impl_ != NULL); + if (impl_->IsGettingPorts()) { + impl_->StopGettingPorts(); + } +} + +bool PortAllocatorSessionProxy::IsGettingPorts() { + ASSERT(impl_ != NULL); + return impl_->IsGettingPorts(); +} + +void PortAllocatorSessionProxy::OnPortReady(PortAllocatorSession* session, + PortInterface* port) { + ASSERT(session == impl_); + + PortProxy* proxy_port = new PortProxy(); + proxy_port->set_impl(port); + proxy_ports_[port] = proxy_port; + SignalPortReady(this, proxy_port); +} + +void PortAllocatorSessionProxy::OnCandidatesReady( + PortAllocatorSession* session, + const std::vector& candidates) { + ASSERT(session == impl_); + + // Since all proxy sessions share a common PortAllocatorSession, + // all Candidates will have name associated with the common PAS. + // Change Candidate name with the PortAllocatorSessionProxy name. + std::vector our_candidates; + for (size_t i = 0; i < candidates.size(); ++i) { + Candidate new_local_candidate = candidates[i]; + new_local_candidate.set_component(component_); + our_candidates.push_back(new_local_candidate); + } + SignalCandidatesReady(this, our_candidates); +} + +void PortAllocatorSessionProxy::OnCandidatesAllocationDone( + PortAllocatorSession* session) { + ASSERT(session == impl_); + SignalCandidatesAllocationDone(this); +} + +} // namespace cricket diff --git a/webrtc/p2p/base/portallocatorsessionproxy.h b/webrtc/p2p/base/portallocatorsessionproxy.h new file mode 100644 index 000000000..94ae19d9c --- /dev/null +++ b/webrtc/p2p/base/portallocatorsessionproxy.h @@ -0,0 +1,106 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_PORTALLOCATORSESSIONPROXY_H_ +#define WEBRTC_P2P_BASE_PORTALLOCATORSESSIONPROXY_H_ + +#include + +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/portallocator.h" + +namespace cricket { +class PortAllocator; +class PortAllocatorSessionProxy; +class PortProxy; + +// This class maintains the list of cricket::Port* objects. Ports will be +// deleted upon receiving SignalDestroyed signal. This class is used when +// PORTALLOCATOR_ENABLE_BUNDLE flag is set. + +class PortAllocatorSessionMuxer : public rtc::MessageHandler, + public sigslot::has_slots<> { + public: + explicit PortAllocatorSessionMuxer(PortAllocatorSession* session); + virtual ~PortAllocatorSessionMuxer(); + + void RegisterSessionProxy(PortAllocatorSessionProxy* session_proxy); + + void OnPortReady(PortAllocatorSession* session, PortInterface* port); + void OnPortDestroyed(PortInterface* port); + void OnCandidatesAllocationDone(PortAllocatorSession* session); + + const std::vector& ports() { return ports_; } + + sigslot::signal1 SignalDestroyed; + + private: + virtual void OnMessage(rtc::Message *pmsg); + void OnSessionProxyDestroyed(PortAllocatorSession* proxy); + void SendAllocationDone_w(PortAllocatorSessionProxy* proxy); + void SendAllocatedPorts_w(PortAllocatorSessionProxy* proxy); + + // Port will be deleted when SignalDestroyed received, otherwise delete + // happens when PortAllocatorSession dtor is called. + rtc::Thread* worker_thread_; + std::vector ports_; + rtc::scoped_ptr session_; + std::vector session_proxies_; + bool candidate_done_signal_received_; +}; + +class PortAllocatorSessionProxy : public PortAllocatorSession { + public: + PortAllocatorSessionProxy(const std::string& content_name, + int component, + uint32 flags) + // Use empty string as the ufrag and pwd because the proxy always uses + // the ufrag and pwd from the underlying implementation. + : PortAllocatorSession(content_name, component, "", "", flags), + impl_(NULL) { + } + + virtual ~PortAllocatorSessionProxy(); + + PortAllocatorSession* impl() { return impl_; } + void set_impl(PortAllocatorSession* session); + + // Forwards call to the actual PortAllocatorSession. + virtual void StartGettingPorts(); + virtual void StopGettingPorts(); + virtual bool IsGettingPorts(); + + virtual void set_generation(uint32 generation) { + ASSERT(impl_ != NULL); + impl_->set_generation(generation); + } + + virtual uint32 generation() { + ASSERT(impl_ != NULL); + return impl_->generation(); + } + + private: + void OnPortReady(PortAllocatorSession* session, PortInterface* port); + void OnCandidatesReady(PortAllocatorSession* session, + const std::vector& candidates); + void OnPortDestroyed(PortInterface* port); + void OnCandidatesAllocationDone(PortAllocatorSession* session); + + // This is the actual PortAllocatorSession, owned by PortAllocator. + PortAllocatorSession* impl_; + std::map proxy_ports_; + + friend class PortAllocatorSessionMuxer; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_PORTALLOCATORSESSIONPROXY_H_ diff --git a/webrtc/p2p/base/portallocatorsessionproxy_unittest.cc b/webrtc/p2p/base/portallocatorsessionproxy_unittest.cc new file mode 100644 index 000000000..61a9e9896 --- /dev/null +++ b/webrtc/p2p/base/portallocatorsessionproxy_unittest.cc @@ -0,0 +1,146 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/portallocatorsessionproxy.h" +#include "webrtc/p2p/client/basicportallocator.h" +#include "webrtc/p2p/client/fakeportallocator.h" +#include "webrtc/base/fakenetwork.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/thread.h" + +using cricket::Candidate; +using cricket::PortAllocatorSession; +using cricket::PortAllocatorSessionMuxer; +using cricket::PortAllocatorSessionProxy; + +// Based on ICE_UFRAG_LENGTH +static const char kIceUfrag0[] = "TESTICEUFRAG0000"; +// Based on ICE_PWD_LENGTH +static const char kIcePwd0[] = "TESTICEPWD00000000000000"; + +class TestSessionChannel : public sigslot::has_slots<> { + public: + explicit TestSessionChannel(PortAllocatorSessionProxy* proxy) + : proxy_session_(proxy), + candidates_count_(0), + allocation_complete_(false), + ports_count_(0) { + proxy_session_->SignalCandidatesAllocationDone.connect( + this, &TestSessionChannel::OnCandidatesAllocationDone); + proxy_session_->SignalCandidatesReady.connect( + this, &TestSessionChannel::OnCandidatesReady); + proxy_session_->SignalPortReady.connect( + this, &TestSessionChannel::OnPortReady); + } + virtual ~TestSessionChannel() { + delete proxy_session_; + } + void OnCandidatesReady(PortAllocatorSession* session, + const std::vector& candidates) { + EXPECT_EQ(proxy_session_, session); + candidates_count_ += static_cast(candidates.size()); + } + void OnCandidatesAllocationDone(PortAllocatorSession* session) { + EXPECT_EQ(proxy_session_, session); + allocation_complete_ = true; + } + void OnPortReady(PortAllocatorSession* session, + cricket::PortInterface* port) { + EXPECT_EQ(proxy_session_, session); + ++ports_count_; + } + int candidates_count() { return candidates_count_; } + bool allocation_complete() { return allocation_complete_; } + int ports_count() { return ports_count_; } + + void StartGettingPorts() { + proxy_session_->StartGettingPorts(); + } + + void StopGettingPorts() { + proxy_session_->StopGettingPorts(); + } + + bool IsGettingPorts() { + return proxy_session_->IsGettingPorts(); + } + + private: + PortAllocatorSessionProxy* proxy_session_; + int candidates_count_; + bool allocation_complete_; + int ports_count_; +}; + +class PortAllocatorSessionProxyTest : public testing::Test { + public: + PortAllocatorSessionProxyTest() + : socket_factory_(rtc::Thread::Current()), + allocator_(rtc::Thread::Current(), NULL), + session_(new cricket::FakePortAllocatorSession( + rtc::Thread::Current(), &socket_factory_, + "test content", 1, + kIceUfrag0, kIcePwd0)), + session_muxer_(new PortAllocatorSessionMuxer(session_)) { + } + virtual ~PortAllocatorSessionProxyTest() {} + void RegisterSessionProxy(PortAllocatorSessionProxy* proxy) { + session_muxer_->RegisterSessionProxy(proxy); + } + + TestSessionChannel* CreateChannel() { + PortAllocatorSessionProxy* proxy = + new PortAllocatorSessionProxy("test content", 1, 0); + TestSessionChannel* channel = new TestSessionChannel(proxy); + session_muxer_->RegisterSessionProxy(proxy); + channel->StartGettingPorts(); + return channel; + } + + protected: + rtc::BasicPacketSocketFactory socket_factory_; + cricket::FakePortAllocator allocator_; + cricket::FakePortAllocatorSession* session_; + // Muxer object will be delete itself after all registered session proxies + // are deleted. + PortAllocatorSessionMuxer* session_muxer_; +}; + +TEST_F(PortAllocatorSessionProxyTest, TestBasic) { + TestSessionChannel* channel = CreateChannel(); + EXPECT_EQ_WAIT(1, channel->candidates_count(), 1000); + EXPECT_EQ(1, channel->ports_count()); + EXPECT_TRUE(channel->allocation_complete()); + delete channel; +} + +TEST_F(PortAllocatorSessionProxyTest, TestLateBinding) { + TestSessionChannel* channel1 = CreateChannel(); + EXPECT_EQ_WAIT(1, channel1->candidates_count(), 1000); + EXPECT_EQ(1, channel1->ports_count()); + EXPECT_TRUE(channel1->allocation_complete()); + EXPECT_EQ(1, session_->port_config_count()); + // Creating another PortAllocatorSessionProxy and it also should receive + // already happened events. + PortAllocatorSessionProxy* proxy = + new PortAllocatorSessionProxy("test content", 2, 0); + TestSessionChannel* channel2 = new TestSessionChannel(proxy); + session_muxer_->RegisterSessionProxy(proxy); + EXPECT_TRUE(channel2->IsGettingPorts()); + EXPECT_EQ_WAIT(1, channel2->candidates_count(), 1000); + EXPECT_EQ(1, channel2->ports_count()); + EXPECT_TRUE_WAIT(channel2->allocation_complete(), 1000); + EXPECT_EQ(1, session_->port_config_count()); + delete channel1; + delete channel2; +} diff --git a/webrtc/p2p/base/portinterface.h b/webrtc/p2p/base/portinterface.h new file mode 100644 index 000000000..ee6835ebf --- /dev/null +++ b/webrtc/p2p/base/portinterface.h @@ -0,0 +1,126 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_PORTINTERFACE_H_ +#define WEBRTC_P2P_BASE_PORTINTERFACE_H_ + +#include + +#include "webrtc/p2p/base/transport.h" +#include "webrtc/base/socketaddress.h" + +namespace rtc { +class Network; +struct PacketOptions; +} + +namespace cricket { +class Connection; +class IceMessage; +class StunMessage; + +enum ProtocolType { + PROTO_UDP, + PROTO_TCP, + PROTO_SSLTCP, + PROTO_LAST = PROTO_SSLTCP +}; + +// Defines the interface for a port, which represents a local communication +// mechanism that can be used to create connections to similar mechanisms of +// the other client. Various types of ports will implement this interface. +class PortInterface { + public: + virtual ~PortInterface() {} + + virtual const std::string& Type() const = 0; + virtual rtc::Network* Network() const = 0; + + virtual void SetIceProtocolType(IceProtocolType protocol) = 0; + virtual IceProtocolType IceProtocol() const = 0; + + // Methods to set/get ICE role and tiebreaker values. + virtual void SetIceRole(IceRole role) = 0; + virtual IceRole GetIceRole() const = 0; + + virtual void SetIceTiebreaker(uint64 tiebreaker) = 0; + virtual uint64 IceTiebreaker() const = 0; + + virtual bool SharedSocket() const = 0; + + // 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 ready. + // Once it is ready, we will send SignalAddressReady. If errors are + // preventing the port from getting an address, it may send + // SignalAddressError. + virtual void PrepareAddress() = 0; + + // Returns the connection to the given address or NULL if none exists. + virtual Connection* GetConnection( + const rtc::SocketAddress& remote_addr) = 0; + + // 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; + + // Functions on the underlying socket(s). + virtual int SetOption(rtc::Socket::Option opt, int value) = 0; + virtual int GetOption(rtc::Socket::Option opt, int* value) = 0; + virtual int GetError() = 0; + + virtual const std::vector& Candidates() const = 0; + + // 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 rtc::SocketAddress& addr, + const rtc::PacketOptions& options, 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::signal6 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. + virtual void SendBindingResponse(StunMessage* request, + const rtc::SocketAddress& addr) = 0; + virtual void SendBindingErrorResponse( + StunMessage* request, const rtc::SocketAddress& addr, + int error_code, const std::string& reason) = 0; + + // Signaled when this port decides to delete itself because it no longer has + // any usefulness. + sigslot::signal1 SignalDestroyed; + + // Signaled when Port discovers ice role conflict with the peer. + sigslot::signal1 SignalRoleConflict; + + // Normally, packets arrive through a connection (or they result signaling of + // unknown address). Calling this method turns off delivery of packets + // through their respective connection and instead delivers every packet + // through this port. + virtual void EnablePortPackets() = 0; + sigslot::signal4 SignalReadPacket; + + virtual std::string ToString() const = 0; + + protected: + PortInterface() {} +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_PORTINTERFACE_H_ diff --git a/webrtc/p2p/base/portproxy.cc b/webrtc/p2p/base/portproxy.cc new file mode 100644 index 000000000..e28af279f --- /dev/null +++ b/webrtc/p2p/base/portproxy.cc @@ -0,0 +1,163 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/portproxy.h" + +namespace cricket { + +void PortProxy::set_impl(PortInterface* port) { + impl_ = port; + impl_->SignalUnknownAddress.connect( + this, &PortProxy::OnUnknownAddress); + impl_->SignalDestroyed.connect(this, &PortProxy::OnPortDestroyed); + impl_->SignalRoleConflict.connect(this, &PortProxy::OnRoleConflict); +} + +const std::string& PortProxy::Type() const { + ASSERT(impl_ != NULL); + return impl_->Type(); +} + +rtc::Network* PortProxy::Network() const { + ASSERT(impl_ != NULL); + return impl_->Network(); +} + +void PortProxy::SetIceProtocolType(IceProtocolType protocol) { + ASSERT(impl_ != NULL); + impl_->SetIceProtocolType(protocol); +} + +IceProtocolType PortProxy::IceProtocol() const { + ASSERT(impl_ != NULL); + return impl_->IceProtocol(); +} + +// Methods to set/get ICE role and tiebreaker values. +void PortProxy::SetIceRole(IceRole role) { + ASSERT(impl_ != NULL); + impl_->SetIceRole(role); +} + +IceRole PortProxy::GetIceRole() const { + ASSERT(impl_ != NULL); + return impl_->GetIceRole(); +} + +void PortProxy::SetIceTiebreaker(uint64 tiebreaker) { + ASSERT(impl_ != NULL); + impl_->SetIceTiebreaker(tiebreaker); +} + +uint64 PortProxy::IceTiebreaker() const { + ASSERT(impl_ != NULL); + return impl_->IceTiebreaker(); +} + +bool PortProxy::SharedSocket() const { + ASSERT(impl_ != NULL); + return impl_->SharedSocket(); +} + +void PortProxy::PrepareAddress() { + ASSERT(impl_ != NULL); + impl_->PrepareAddress(); +} + +Connection* PortProxy::CreateConnection(const Candidate& remote_candidate, + CandidateOrigin origin) { + ASSERT(impl_ != NULL); + return impl_->CreateConnection(remote_candidate, origin); +} + +int PortProxy::SendTo(const void* data, + size_t size, + const rtc::SocketAddress& addr, + const rtc::PacketOptions& options, + bool payload) { + ASSERT(impl_ != NULL); + return impl_->SendTo(data, size, addr, options, payload); +} + +int PortProxy::SetOption(rtc::Socket::Option opt, + int value) { + ASSERT(impl_ != NULL); + return impl_->SetOption(opt, value); +} + +int PortProxy::GetOption(rtc::Socket::Option opt, + int* value) { + ASSERT(impl_ != NULL); + return impl_->GetOption(opt, value); +} + +int PortProxy::GetError() { + ASSERT(impl_ != NULL); + return impl_->GetError(); +} + +const std::vector& PortProxy::Candidates() const { + ASSERT(impl_ != NULL); + return impl_->Candidates(); +} + +void PortProxy::SendBindingResponse( + StunMessage* request, const rtc::SocketAddress& addr) { + ASSERT(impl_ != NULL); + impl_->SendBindingResponse(request, addr); +} + +Connection* PortProxy::GetConnection( + const rtc::SocketAddress& remote_addr) { + ASSERT(impl_ != NULL); + return impl_->GetConnection(remote_addr); +} + +void PortProxy::SendBindingErrorResponse( + StunMessage* request, const rtc::SocketAddress& addr, + int error_code, const std::string& reason) { + ASSERT(impl_ != NULL); + impl_->SendBindingErrorResponse(request, addr, error_code, reason); +} + +void PortProxy::EnablePortPackets() { + ASSERT(impl_ != NULL); + impl_->EnablePortPackets(); +} + +std::string PortProxy::ToString() const { + ASSERT(impl_ != NULL); + return impl_->ToString(); +} + +void PortProxy::OnUnknownAddress( + PortInterface *port, + const rtc::SocketAddress &addr, + ProtocolType proto, + IceMessage *stun_msg, + const std::string &remote_username, + bool port_muxed) { + ASSERT(port == impl_); + ASSERT(!port_muxed); + SignalUnknownAddress(this, addr, proto, stun_msg, remote_username, true); +} + +void PortProxy::OnRoleConflict(PortInterface* port) { + ASSERT(port == impl_); + SignalRoleConflict(this); +} + +void PortProxy::OnPortDestroyed(PortInterface* port) { + ASSERT(port == impl_); + // |port| will be destroyed in PortAllocatorSessionMuxer. + SignalDestroyed(this); +} + +} // namespace cricket diff --git a/webrtc/p2p/base/portproxy.h b/webrtc/p2p/base/portproxy.h new file mode 100644 index 000000000..79507fea8 --- /dev/null +++ b/webrtc/p2p/base/portproxy.h @@ -0,0 +1,87 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_PORTPROXY_H_ +#define WEBRTC_P2P_BASE_PORTPROXY_H_ + +#include "webrtc/p2p/base/portinterface.h" +#include "webrtc/base/sigslot.h" + +namespace rtc { +class Network; +} + +namespace cricket { + +class PortProxy : public PortInterface, public sigslot::has_slots<> { + public: + PortProxy() {} + virtual ~PortProxy() {} + + PortInterface* impl() { return impl_; } + void set_impl(PortInterface* port); + + virtual const std::string& Type() const; + virtual rtc::Network* Network() const; + + virtual void SetIceProtocolType(IceProtocolType protocol); + virtual IceProtocolType IceProtocol() const; + + // Methods to set/get ICE role and tiebreaker values. + virtual void SetIceRole(IceRole role); + virtual IceRole GetIceRole() const; + + virtual void SetIceTiebreaker(uint64 tiebreaker); + virtual uint64 IceTiebreaker() const; + + virtual bool SharedSocket() const; + + // Forwards call to the actual Port. + virtual void PrepareAddress(); + virtual Connection* CreateConnection(const Candidate& remote_candidate, + CandidateOrigin origin); + virtual Connection* GetConnection( + const rtc::SocketAddress& remote_addr); + + virtual int SendTo(const void* data, size_t size, + const rtc::SocketAddress& addr, + const rtc::PacketOptions& options, + bool payload); + virtual int SetOption(rtc::Socket::Option opt, int value); + virtual int GetOption(rtc::Socket::Option opt, int* value); + virtual int GetError(); + + virtual const std::vector& Candidates() const; + + virtual void SendBindingResponse(StunMessage* request, + const rtc::SocketAddress& addr); + virtual void SendBindingErrorResponse( + StunMessage* request, const rtc::SocketAddress& addr, + int error_code, const std::string& reason); + + virtual void EnablePortPackets(); + virtual std::string ToString() const; + + private: + void OnUnknownAddress(PortInterface *port, + const rtc::SocketAddress &addr, + ProtocolType proto, + IceMessage *stun_msg, + const std::string &remote_username, + bool port_muxed); + void OnRoleConflict(PortInterface* port); + void OnPortDestroyed(PortInterface* port); + + PortInterface* impl_; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_PORTPROXY_H_ diff --git a/webrtc/p2p/base/pseudotcp.cc b/webrtc/p2p/base/pseudotcp.cc new file mode 100644 index 000000000..0dfe7d829 --- /dev/null +++ b/webrtc/p2p/base/pseudotcp.cc @@ -0,0 +1,1274 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/pseudotcp.h" + +#include +#include + +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/bytebuffer.h" +#include "webrtc/base/byteorder.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/socket.h" +#include "webrtc/base/stringutils.h" +#include "webrtc/base/timeutils.h" + +// The following logging is for detailed (packet-level) analysis only. +#define _DBG_NONE 0 +#define _DBG_NORMAL 1 +#define _DBG_VERBOSE 2 +#define _DEBUGMSG _DBG_NONE + +namespace cricket { + +////////////////////////////////////////////////////////////////////// +// Network Constants +////////////////////////////////////////////////////////////////////// + +// 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 MAX_PACKET = 65535; +// Note: we removed lowest level because packet overhead was larger! +const uint32 MIN_PACKET = 296; + +const uint32 IP_HEADER_SIZE = 20; // (+ up to 40 bytes of options?) +const uint32 UDP_HEADER_SIZE = 8; +// TODO: Make JINGLE_HEADER_SIZE transparent to this code? +const uint32 JINGLE_HEADER_SIZE = 64; // when relay framing is in use + +// Default size for receive and send buffer. +const uint32 DEFAULT_RCV_BUF_SIZE = 60 * 1024; +const uint32 DEFAULT_SND_BUF_SIZE = 90 * 1024; + +////////////////////////////////////////////////////////////////////// +// Global Constants and Functions +////////////////////////////////////////////////////////////////////// +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 0 | Conversation Number | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | Sequence Number | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 8 | Acknowledgment Number | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | | |U|A|P|R|S|F| | +// 12 | Control | |R|C|S|S|Y|I| Window | +// | | |G|K|H|T|N|N| | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 16 | Timestamp sending | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 20 | Timestamp receiving | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 24 | data | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +////////////////////////////////////////////////////////////////////// + +#define PSEUDO_KEEPALIVE 0 + +const uint32 HEADER_SIZE = 24; +const uint32 PACKET_OVERHEAD = HEADER_SIZE + UDP_HEADER_SIZE + IP_HEADER_SIZE + JINGLE_HEADER_SIZE; + +const uint32 MIN_RTO = 250; // 250 ms (RFC1122, Sec 4.2.3.1 "fractions of a second") +const uint32 DEF_RTO = 3000; // 3 seconds (RFC1122, Sec 4.2.3.1) +const uint32 MAX_RTO = 60000; // 60 seconds +const uint32 DEF_ACK_DELAY = 100; // 100 milliseconds + +const uint8 FLAG_CTL = 0x02; +const uint8 FLAG_RST = 0x04; + +const uint8 CTL_CONNECT = 0; + +// TCP options. +const uint8 TCP_OPT_EOL = 0; // End of list. +const uint8 TCP_OPT_NOOP = 1; // No-op. +const uint8 TCP_OPT_MSS = 2; // Maximum segment size. +const uint8 TCP_OPT_WND_SCALE = 3; // Window scale factor. + +const long DEFAULT_TIMEOUT = 4000; // If there are no pending clocks, wake up every 4 seconds +const long CLOSED_TIMEOUT = 60 * 1000; // If the connection is closed, once per minute + +#if PSEUDO_KEEPALIVE +// !?! Rethink these times +const uint32 IDLE_PING = 20 * 1000; // 20 seconds (note: WinXP SP2 firewall udp timeout is 90 seconds) +const uint32 IDLE_TIMEOUT = 90 * 1000; // 90 seconds; +#endif // PSEUDO_KEEPALIVE + +////////////////////////////////////////////////////////////////////// +// Helper Functions +////////////////////////////////////////////////////////////////////// + +inline void long_to_bytes(uint32 val, void* buf) { + *static_cast(buf) = rtc::HostToNetwork32(val); +} + +inline void short_to_bytes(uint16 val, void* buf) { + *static_cast(buf) = rtc::HostToNetwork16(val); +} + +inline uint32 bytes_to_long(const void* buf) { + return rtc::NetworkToHost32(*static_cast(buf)); +} + +inline uint16 bytes_to_short(const void* buf) { + return rtc::NetworkToHost16(*static_cast(buf)); +} + +uint32 bound(uint32 lower, uint32 middle, uint32 upper) { + return rtc::_min(rtc::_max(lower, middle), upper); +} + +////////////////////////////////////////////////////////////////////// +// Debugging Statistics +////////////////////////////////////////////////////////////////////// + +#if 0 // Not used yet + +enum Stat { + S_SENT_PACKET, // All packet sends + S_RESENT_PACKET, // All packet sends that are retransmits + S_RECV_PACKET, // All packet receives + S_RECV_NEW, // All packet receives that are too new + S_RECV_OLD, // All packet receives that are too old + S_NUM_STATS +}; + +const char* const STAT_NAMES[S_NUM_STATS] = { + "snt", + "snt-r", + "rcv" + "rcv-n", + "rcv-o" +}; + +int g_stats[S_NUM_STATS]; +inline void Incr(Stat s) { ++g_stats[s]; } +void ReportStats() { + char buffer[256]; + size_t len = 0; + for (int i = 0; i < S_NUM_STATS; ++i) { + len += rtc::sprintfn(buffer, ARRAY_SIZE(buffer), "%s%s:%d", + (i == 0) ? "" : ",", STAT_NAMES[i], g_stats[i]); + g_stats[i] = 0; + } + LOG(LS_INFO) << "Stats[" << buffer << "]"; +} + +#endif + +////////////////////////////////////////////////////////////////////// +// PseudoTcp +////////////////////////////////////////////////////////////////////// + +uint32 PseudoTcp::Now() { +#if 0 // Use this to synchronize timers with logging timestamps (easier debug) + return rtc::TimeSince(StartTime()); +#else + return rtc::Time(); +#endif +} + +PseudoTcp::PseudoTcp(IPseudoTcpNotify* notify, uint32 conv) + : m_notify(notify), + m_shutdown(SD_NONE), + m_error(0), + m_rbuf_len(DEFAULT_RCV_BUF_SIZE), + m_rbuf(m_rbuf_len), + m_sbuf_len(DEFAULT_SND_BUF_SIZE), + m_sbuf(m_sbuf_len) { + + // Sanity check on buffer sizes (needed for OnTcpWriteable notification logic) + ASSERT(m_rbuf_len + MIN_PACKET < m_sbuf_len); + + uint32 now = Now(); + + m_state = TCP_LISTEN; + m_conv = conv; + m_rcv_wnd = m_rbuf_len; + m_rwnd_scale = m_swnd_scale = 0; + m_snd_nxt = 0; + m_snd_wnd = 1; + m_snd_una = m_rcv_nxt = 0; + m_bReadEnable = true; + m_bWriteEnable = false; + m_t_ack = 0; + + m_msslevel = 0; + m_largest = 0; + ASSERT(MIN_PACKET > PACKET_OVERHEAD); + m_mss = MIN_PACKET - PACKET_OVERHEAD; + m_mtu_advise = MAX_PACKET; + + m_rto_base = 0; + + m_cwnd = 2 * m_mss; + m_ssthresh = m_rbuf_len; + m_lastrecv = m_lastsend = m_lasttraffic = now; + m_bOutgoing = false; + + m_dup_acks = 0; + m_recover = 0; + + m_ts_recent = m_ts_lastack = 0; + + m_rx_rto = DEF_RTO; + m_rx_srtt = m_rx_rttvar = 0; + + m_use_nagling = true; + m_ack_delay = DEF_ACK_DELAY; + m_support_wnd_scale = true; +} + +PseudoTcp::~PseudoTcp() { +} + +int PseudoTcp::Connect() { + if (m_state != TCP_LISTEN) { + m_error = EINVAL; + return -1; + } + + m_state = TCP_SYN_SENT; + LOG(LS_INFO) << "State: TCP_SYN_SENT"; + + queueConnectMessage(); + attemptSend(); + + return 0; +} + +void PseudoTcp::NotifyMTU(uint16 mtu) { + m_mtu_advise = mtu; + if (m_state == TCP_ESTABLISHED) { + adjustMTU(); + } +} + +void PseudoTcp::NotifyClock(uint32 now) { + if (m_state == TCP_CLOSED) + return; + + // Check if it's time to retransmit a segment + if (m_rto_base && (rtc::TimeDiff(m_rto_base + m_rx_rto, now) <= 0)) { + if (m_slist.empty()) { + ASSERT(false); + } else { + // Note: (m_slist.front().xmit == 0)) { + // retransmit segments +#if _DEBUGMSG >= _DBG_NORMAL + LOG(LS_INFO) << "timeout retransmit (rto: " << m_rx_rto + << ") (rto_base: " << m_rto_base + << ") (now: " << now + << ") (dup_acks: " << static_cast(m_dup_acks) + << ")"; +#endif // _DEBUGMSG + if (!transmit(m_slist.begin(), now)) { + closedown(ECONNABORTED); + return; + } + + uint32 nInFlight = m_snd_nxt - m_snd_una; + m_ssthresh = rtc::_max(nInFlight / 2, 2 * m_mss); + //LOG(LS_INFO) << "m_ssthresh: " << m_ssthresh << " nInFlight: " << nInFlight << " m_mss: " << m_mss; + m_cwnd = m_mss; + + // Back off retransmit timer. Note: the limit is lower when connecting. + uint32 rto_limit = (m_state < TCP_ESTABLISHED) ? DEF_RTO : MAX_RTO; + m_rx_rto = rtc::_min(rto_limit, m_rx_rto * 2); + m_rto_base = now; + } + } + + // Check if it's time to probe closed windows + if ((m_snd_wnd == 0) + && (rtc::TimeDiff(m_lastsend + m_rx_rto, now) <= 0)) { + if (rtc::TimeDiff(now, m_lastrecv) >= 15000) { + closedown(ECONNABORTED); + return; + } + + // probe the window + packet(m_snd_nxt - 1, 0, 0, 0); + m_lastsend = now; + + // back off retransmit timer + m_rx_rto = rtc::_min(MAX_RTO, m_rx_rto * 2); + } + + // Check if it's time to send delayed acks + if (m_t_ack && (rtc::TimeDiff(m_t_ack + m_ack_delay, now) <= 0)) { + packet(m_snd_nxt, 0, 0, 0); + } + +#if PSEUDO_KEEPALIVE + // Check for idle timeout + if ((m_state == TCP_ESTABLISHED) && (TimeDiff(m_lastrecv + IDLE_TIMEOUT, now) <= 0)) { + closedown(ECONNABORTED); + return; + } + + // Check for ping timeout (to keep udp mapping open) + if ((m_state == TCP_ESTABLISHED) && (TimeDiff(m_lasttraffic + (m_bOutgoing ? IDLE_PING * 3/2 : IDLE_PING), now) <= 0)) { + packet(m_snd_nxt, 0, 0, 0); + } +#endif // PSEUDO_KEEPALIVE +} + +bool PseudoTcp::NotifyPacket(const char* buffer, size_t len) { + if (len > MAX_PACKET) { + LOG_F(WARNING) << "packet too large"; + return false; + } + return parse(reinterpret_cast(buffer), uint32(len)); +} + +bool PseudoTcp::GetNextClock(uint32 now, long& timeout) { + return clock_check(now, timeout); +} + +void PseudoTcp::GetOption(Option opt, int* value) { + if (opt == OPT_NODELAY) { + *value = m_use_nagling ? 0 : 1; + } else if (opt == OPT_ACKDELAY) { + *value = m_ack_delay; + } else if (opt == OPT_SNDBUF) { + *value = m_sbuf_len; + } else if (opt == OPT_RCVBUF) { + *value = m_rbuf_len; + } else { + ASSERT(false); + } +} +void PseudoTcp::SetOption(Option opt, int value) { + if (opt == OPT_NODELAY) { + m_use_nagling = value == 0; + } else if (opt == OPT_ACKDELAY) { + m_ack_delay = value; + } else if (opt == OPT_SNDBUF) { + ASSERT(m_state == TCP_LISTEN); + resizeSendBuffer(value); + } else if (opt == OPT_RCVBUF) { + ASSERT(m_state == TCP_LISTEN); + resizeReceiveBuffer(value); + } else { + ASSERT(false); + } +} + +uint32 PseudoTcp::GetCongestionWindow() const { + return m_cwnd; +} + +uint32 PseudoTcp::GetBytesInFlight() const { + return m_snd_nxt - m_snd_una; +} + +uint32 PseudoTcp::GetBytesBufferedNotSent() const { + size_t buffered_bytes = 0; + m_sbuf.GetBuffered(&buffered_bytes); + return static_cast(m_snd_una + buffered_bytes - m_snd_nxt); +} + +uint32 PseudoTcp::GetRoundTripTimeEstimateMs() const { + return m_rx_srtt; +} + +// +// IPStream Implementation +// + +int PseudoTcp::Recv(char* buffer, size_t len) { + if (m_state != TCP_ESTABLISHED) { + m_error = ENOTCONN; + return SOCKET_ERROR; + } + + size_t read = 0; + rtc::StreamResult result = m_rbuf.Read(buffer, len, &read, NULL); + + // If there's no data in |m_rbuf|. + if (result == rtc::SR_BLOCK) { + m_bReadEnable = true; + m_error = EWOULDBLOCK; + return SOCKET_ERROR; + } + ASSERT(result == rtc::SR_SUCCESS); + + size_t available_space = 0; + m_rbuf.GetWriteRemaining(&available_space); + + if (uint32(available_space) - m_rcv_wnd >= + rtc::_min(m_rbuf_len / 2, m_mss)) { + // TODO(jbeda): !?! Not sure about this was closed business + bool bWasClosed = (m_rcv_wnd == 0); + m_rcv_wnd = static_cast(available_space); + + if (bWasClosed) { + attemptSend(sfImmediateAck); + } + } + + return static_cast(read); +} + +int PseudoTcp::Send(const char* buffer, size_t len) { + if (m_state != TCP_ESTABLISHED) { + m_error = ENOTCONN; + return SOCKET_ERROR; + } + + size_t available_space = 0; + m_sbuf.GetWriteRemaining(&available_space); + + if (!available_space) { + m_bWriteEnable = true; + m_error = EWOULDBLOCK; + return SOCKET_ERROR; + } + + int written = queue(buffer, uint32(len), false); + attemptSend(); + return written; +} + +void PseudoTcp::Close(bool force) { + LOG_F(LS_VERBOSE) << "(" << (force ? "true" : "false") << ")"; + m_shutdown = force ? SD_FORCEFUL : SD_GRACEFUL; +} + +int PseudoTcp::GetError() { + return m_error; +} + +// +// Internal Implementation +// + +uint32 PseudoTcp::queue(const char* data, uint32 len, bool bCtrl) { + size_t available_space = 0; + m_sbuf.GetWriteRemaining(&available_space); + + if (len > static_cast(available_space)) { + ASSERT(!bCtrl); + len = static_cast(available_space); + } + + // We can concatenate data if the last segment is the same type + // (control v. regular data), and has not been transmitted yet + if (!m_slist.empty() && (m_slist.back().bCtrl == bCtrl) && + (m_slist.back().xmit == 0)) { + m_slist.back().len += len; + } else { + size_t snd_buffered = 0; + m_sbuf.GetBuffered(&snd_buffered); + SSegment sseg(static_cast(m_snd_una + snd_buffered), len, bCtrl); + m_slist.push_back(sseg); + } + + size_t written = 0; + m_sbuf.Write(data, len, &written, NULL); + return static_cast(written); +} + +IPseudoTcpNotify::WriteResult PseudoTcp::packet(uint32 seq, uint8 flags, + uint32 offset, uint32 len) { + ASSERT(HEADER_SIZE + len <= MAX_PACKET); + + uint32 now = Now(); + + rtc::scoped_ptr buffer(new uint8[MAX_PACKET]); + long_to_bytes(m_conv, buffer.get()); + long_to_bytes(seq, buffer.get() + 4); + long_to_bytes(m_rcv_nxt, buffer.get() + 8); + buffer[12] = 0; + buffer[13] = flags; + short_to_bytes( + static_cast(m_rcv_wnd >> m_rwnd_scale), buffer.get() + 14); + + // Timestamp computations + long_to_bytes(now, buffer.get() + 16); + long_to_bytes(m_ts_recent, buffer.get() + 20); + m_ts_lastack = m_rcv_nxt; + + if (len) { + size_t bytes_read = 0; + rtc::StreamResult result = m_sbuf.ReadOffset( + buffer.get() + HEADER_SIZE, len, offset, &bytes_read); + RTC_UNUSED(result); + ASSERT(result == rtc::SR_SUCCESS); + ASSERT(static_cast(bytes_read) == len); + } + +#if _DEBUGMSG >= _DBG_VERBOSE + LOG(LS_INFO) << "<-- (flags) + << ">"; +#endif // _DEBUGMSG + + IPseudoTcpNotify::WriteResult wres = m_notify->TcpWritePacket( + this, reinterpret_cast(buffer.get()), len + HEADER_SIZE); + // Note: When len is 0, this is an ACK packet. We don't read the return value for those, + // and thus we won't retry. So go ahead and treat the packet as a success (basically simulate + // as if it were dropped), which will prevent our timers from being messed up. + if ((wres != IPseudoTcpNotify::WR_SUCCESS) && (0 != len)) + return wres; + + m_t_ack = 0; + if (len > 0) { + m_lastsend = now; + } + m_lasttraffic = now; + m_bOutgoing = true; + + return IPseudoTcpNotify::WR_SUCCESS; +} + +bool PseudoTcp::parse(const uint8* buffer, uint32 size) { + if (size < 12) + return false; + + Segment seg; + seg.conv = bytes_to_long(buffer); + seg.seq = bytes_to_long(buffer + 4); + seg.ack = bytes_to_long(buffer + 8); + seg.flags = buffer[13]; + seg.wnd = bytes_to_short(buffer + 14); + + seg.tsval = bytes_to_long(buffer + 16); + seg.tsecr = bytes_to_long(buffer + 20); + + seg.data = reinterpret_cast(buffer) + HEADER_SIZE; + seg.len = size - HEADER_SIZE; + +#if _DEBUGMSG >= _DBG_VERBOSE + LOG(LS_INFO) << "--> (seg.flags) + << ">"; +#endif // _DEBUGMSG + + return process(seg); +} + +bool PseudoTcp::clock_check(uint32 now, long& nTimeout) { + if (m_shutdown == SD_FORCEFUL) + return false; + + size_t snd_buffered = 0; + m_sbuf.GetBuffered(&snd_buffered); + if ((m_shutdown == SD_GRACEFUL) + && ((m_state != TCP_ESTABLISHED) + || ((snd_buffered == 0) && (m_t_ack == 0)))) { + return false; + } + + if (m_state == TCP_CLOSED) { + nTimeout = CLOSED_TIMEOUT; + return true; + } + + nTimeout = DEFAULT_TIMEOUT; + + if (m_t_ack) { + nTimeout = rtc::_min(nTimeout, + rtc::TimeDiff(m_t_ack + m_ack_delay, now)); + } + if (m_rto_base) { + nTimeout = rtc::_min(nTimeout, + rtc::TimeDiff(m_rto_base + m_rx_rto, now)); + } + if (m_snd_wnd == 0) { + nTimeout = rtc::_min(nTimeout, rtc::TimeDiff(m_lastsend + m_rx_rto, now)); + } +#if PSEUDO_KEEPALIVE + if (m_state == TCP_ESTABLISHED) { + nTimeout = rtc::_min(nTimeout, + rtc::TimeDiff(m_lasttraffic + (m_bOutgoing ? IDLE_PING * 3/2 : IDLE_PING), now)); + } +#endif // PSEUDO_KEEPALIVE + return true; +} + +bool PseudoTcp::process(Segment& seg) { + // If this is the wrong conversation, send a reset!?! (with the correct conversation?) + if (seg.conv != m_conv) { + //if ((seg.flags & FLAG_RST) == 0) { + // packet(tcb, seg.ack, 0, FLAG_RST, 0, 0); + //} + LOG_F(LS_ERROR) << "wrong conversation"; + return false; + } + + uint32 now = Now(); + m_lasttraffic = m_lastrecv = now; + m_bOutgoing = false; + + if (m_state == TCP_CLOSED) { + // !?! send reset? + LOG_F(LS_ERROR) << "closed"; + return false; + } + + // Check if this is a reset segment + if (seg.flags & FLAG_RST) { + closedown(ECONNRESET); + return false; + } + + // Check for control data + bool bConnect = false; + if (seg.flags & FLAG_CTL) { + if (seg.len == 0) { + LOG_F(LS_ERROR) << "Missing control code"; + return false; + } else if (seg.data[0] == CTL_CONNECT) { + bConnect = true; + + // TCP options are in the remainder of the payload after CTL_CONNECT. + parseOptions(&seg.data[1], seg.len - 1); + + if (m_state == TCP_LISTEN) { + m_state = TCP_SYN_RECEIVED; + LOG(LS_INFO) << "State: TCP_SYN_RECEIVED"; + //m_notify->associate(addr); + queueConnectMessage(); + } else if (m_state == TCP_SYN_SENT) { + m_state = TCP_ESTABLISHED; + LOG(LS_INFO) << "State: TCP_ESTABLISHED"; + adjustMTU(); + if (m_notify) { + m_notify->OnTcpOpen(this); + } + //notify(evOpen); + } + } else { + LOG_F(LS_WARNING) << "Unknown control code: " << seg.data[0]; + return false; + } + } + + // Update timestamp + if ((seg.seq <= m_ts_lastack) && (m_ts_lastack < seg.seq + seg.len)) { + m_ts_recent = seg.tsval; + } + + // Check if this is a valuable ack + if ((seg.ack > m_snd_una) && (seg.ack <= m_snd_nxt)) { + // Calculate round-trip time + if (seg.tsecr) { + int32 rtt = rtc::TimeDiff(now, seg.tsecr); + if (rtt >= 0) { + if (m_rx_srtt == 0) { + m_rx_srtt = rtt; + m_rx_rttvar = rtt / 2; + } else { + uint32 unsigned_rtt = static_cast(rtt); + uint32 abs_err = unsigned_rtt > m_rx_srtt ? unsigned_rtt - m_rx_srtt + : m_rx_srtt - unsigned_rtt; + m_rx_rttvar = (3 * m_rx_rttvar + abs_err) / 4; + m_rx_srtt = (7 * m_rx_srtt + rtt) / 8; + } + m_rx_rto = bound(MIN_RTO, m_rx_srtt + + rtc::_max(1, 4 * m_rx_rttvar), MAX_RTO); +#if _DEBUGMSG >= _DBG_VERBOSE + LOG(LS_INFO) << "rtt: " << rtt + << " srtt: " << m_rx_srtt + << " rto: " << m_rx_rto; +#endif // _DEBUGMSG + } else { + ASSERT(false); + } + } + + m_snd_wnd = static_cast(seg.wnd) << m_swnd_scale; + + uint32 nAcked = seg.ack - m_snd_una; + m_snd_una = seg.ack; + + m_rto_base = (m_snd_una == m_snd_nxt) ? 0 : now; + + m_sbuf.ConsumeReadData(nAcked); + + for (uint32 nFree = nAcked; nFree > 0; ) { + ASSERT(!m_slist.empty()); + if (nFree < m_slist.front().len) { + m_slist.front().len -= nFree; + nFree = 0; + } else { + if (m_slist.front().len > m_largest) { + m_largest = m_slist.front().len; + } + nFree -= m_slist.front().len; + m_slist.pop_front(); + } + } + + if (m_dup_acks >= 3) { + if (m_snd_una >= m_recover) { // NewReno + uint32 nInFlight = m_snd_nxt - m_snd_una; + m_cwnd = rtc::_min(m_ssthresh, nInFlight + m_mss); // (Fast Retransmit) +#if _DEBUGMSG >= _DBG_NORMAL + LOG(LS_INFO) << "exit recovery"; +#endif // _DEBUGMSG + m_dup_acks = 0; + } else { +#if _DEBUGMSG >= _DBG_NORMAL + LOG(LS_INFO) << "recovery retransmit"; +#endif // _DEBUGMSG + if (!transmit(m_slist.begin(), now)) { + closedown(ECONNABORTED); + return false; + } + m_cwnd += m_mss - rtc::_min(nAcked, m_cwnd); + } + } else { + m_dup_acks = 0; + // Slow start, congestion avoidance + if (m_cwnd < m_ssthresh) { + m_cwnd += m_mss; + } else { + m_cwnd += rtc::_max(1, m_mss * m_mss / m_cwnd); + } + } + } else if (seg.ack == m_snd_una) { + // !?! Note, tcp says don't do this... but otherwise how does a closed window become open? + m_snd_wnd = static_cast(seg.wnd) << m_swnd_scale; + + // Check duplicate acks + if (seg.len > 0) { + // it's a dup ack, but with a data payload, so don't modify m_dup_acks + } else if (m_snd_una != m_snd_nxt) { + m_dup_acks += 1; + if (m_dup_acks == 3) { // (Fast Retransmit) +#if _DEBUGMSG >= _DBG_NORMAL + LOG(LS_INFO) << "enter recovery"; + LOG(LS_INFO) << "recovery retransmit"; +#endif // _DEBUGMSG + if (!transmit(m_slist.begin(), now)) { + closedown(ECONNABORTED); + return false; + } + m_recover = m_snd_nxt; + uint32 nInFlight = m_snd_nxt - m_snd_una; + m_ssthresh = rtc::_max(nInFlight / 2, 2 * m_mss); + //LOG(LS_INFO) << "m_ssthresh: " << m_ssthresh << " nInFlight: " << nInFlight << " m_mss: " << m_mss; + m_cwnd = m_ssthresh + 3 * m_mss; + } else if (m_dup_acks > 3) { + m_cwnd += m_mss; + } + } else { + m_dup_acks = 0; + } + } + + // !?! A bit hacky + if ((m_state == TCP_SYN_RECEIVED) && !bConnect) { + m_state = TCP_ESTABLISHED; + LOG(LS_INFO) << "State: TCP_ESTABLISHED"; + adjustMTU(); + if (m_notify) { + m_notify->OnTcpOpen(this); + } + //notify(evOpen); + } + + // If we make room in the send queue, notify the user + // The goal it to make sure we always have at least enough data to fill the + // window. We'd like to notify the app when we are halfway to that point. + const uint32 kIdealRefillSize = (m_sbuf_len + m_rbuf_len) / 2; + size_t snd_buffered = 0; + m_sbuf.GetBuffered(&snd_buffered); + if (m_bWriteEnable && static_cast(snd_buffered) < kIdealRefillSize) { + m_bWriteEnable = false; + if (m_notify) { + m_notify->OnTcpWriteable(this); + } + //notify(evWrite); + } + + // Conditions were acks must be sent: + // 1) Segment is too old (they missed an ACK) (immediately) + // 2) Segment is too new (we missed a segment) (immediately) + // 3) Segment has data (so we need to ACK!) (delayed) + // ... so the only time we don't need to ACK, is an empty segment that points to rcv_nxt! + + SendFlags sflags = sfNone; + if (seg.seq != m_rcv_nxt) { + sflags = sfImmediateAck; // (Fast Recovery) + } else if (seg.len != 0) { + if (m_ack_delay == 0) { + sflags = sfImmediateAck; + } else { + sflags = sfDelayedAck; + } + } +#if _DEBUGMSG >= _DBG_NORMAL + if (sflags == sfImmediateAck) { + if (seg.seq > m_rcv_nxt) { + LOG_F(LS_INFO) << "too new"; + } else if (seg.seq + seg.len <= m_rcv_nxt) { + LOG_F(LS_INFO) << "too old"; + } + } +#endif // _DEBUGMSG + + // Adjust the incoming segment to fit our receive buffer + if (seg.seq < m_rcv_nxt) { + uint32 nAdjust = m_rcv_nxt - seg.seq; + if (nAdjust < seg.len) { + seg.seq += nAdjust; + seg.data += nAdjust; + seg.len -= nAdjust; + } else { + seg.len = 0; + } + } + + size_t available_space = 0; + m_rbuf.GetWriteRemaining(&available_space); + + if ((seg.seq + seg.len - m_rcv_nxt) > static_cast(available_space)) { + uint32 nAdjust = seg.seq + seg.len - m_rcv_nxt - static_cast(available_space); + if (nAdjust < seg.len) { + seg.len -= nAdjust; + } else { + seg.len = 0; + } + } + + bool bIgnoreData = (seg.flags & FLAG_CTL) || (m_shutdown != SD_NONE); + bool bNewData = false; + + if (seg.len > 0) { + if (bIgnoreData) { + if (seg.seq == m_rcv_nxt) { + m_rcv_nxt += seg.len; + } + } else { + uint32 nOffset = seg.seq - m_rcv_nxt; + + rtc::StreamResult result = m_rbuf.WriteOffset(seg.data, seg.len, + nOffset, NULL); + ASSERT(result == rtc::SR_SUCCESS); + RTC_UNUSED(result); + + if (seg.seq == m_rcv_nxt) { + m_rbuf.ConsumeWriteBuffer(seg.len); + m_rcv_nxt += seg.len; + m_rcv_wnd -= seg.len; + bNewData = true; + + RList::iterator it = m_rlist.begin(); + while ((it != m_rlist.end()) && (it->seq <= m_rcv_nxt)) { + if (it->seq + it->len > m_rcv_nxt) { + sflags = sfImmediateAck; // (Fast Recovery) + uint32 nAdjust = (it->seq + it->len) - m_rcv_nxt; +#if _DEBUGMSG >= _DBG_NORMAL + LOG(LS_INFO) << "Recovered " << nAdjust << " bytes (" << m_rcv_nxt << " -> " << m_rcv_nxt + nAdjust << ")"; +#endif // _DEBUGMSG + m_rbuf.ConsumeWriteBuffer(nAdjust); + m_rcv_nxt += nAdjust; + m_rcv_wnd -= nAdjust; + } + it = m_rlist.erase(it); + } + } else { +#if _DEBUGMSG >= _DBG_NORMAL + LOG(LS_INFO) << "Saving " << seg.len << " bytes (" << seg.seq << " -> " << seg.seq + seg.len << ")"; +#endif // _DEBUGMSG + RSegment rseg; + rseg.seq = seg.seq; + rseg.len = seg.len; + RList::iterator it = m_rlist.begin(); + while ((it != m_rlist.end()) && (it->seq < rseg.seq)) { + ++it; + } + m_rlist.insert(it, rseg); + } + } + } + + attemptSend(sflags); + + // If we have new data, notify the user + if (bNewData && m_bReadEnable) { + m_bReadEnable = false; + if (m_notify) { + m_notify->OnTcpReadable(this); + } + //notify(evRead); + } + + return true; +} + +bool PseudoTcp::transmit(const SList::iterator& seg, uint32 now) { + if (seg->xmit >= ((m_state == TCP_ESTABLISHED) ? 15 : 30)) { + LOG_F(LS_VERBOSE) << "too many retransmits"; + return false; + } + + uint32 nTransmit = rtc::_min(seg->len, m_mss); + + while (true) { + uint32 seq = seg->seq; + uint8 flags = (seg->bCtrl ? FLAG_CTL : 0); + IPseudoTcpNotify::WriteResult wres = packet(seq, + flags, + seg->seq - m_snd_una, + nTransmit); + + if (wres == IPseudoTcpNotify::WR_SUCCESS) + break; + + if (wres == IPseudoTcpNotify::WR_FAIL) { + LOG_F(LS_VERBOSE) << "packet failed"; + return false; + } + + ASSERT(wres == IPseudoTcpNotify::WR_TOO_LARGE); + + while (true) { + if (PACKET_MAXIMUMS[m_msslevel + 1] == 0) { + LOG_F(LS_VERBOSE) << "MTU too small"; + return false; + } + // !?! We need to break up all outstanding and pending packets and then retransmit!?! + + m_mss = PACKET_MAXIMUMS[++m_msslevel] - PACKET_OVERHEAD; + m_cwnd = 2 * m_mss; // I added this... haven't researched actual formula + if (m_mss < nTransmit) { + nTransmit = m_mss; + break; + } + } +#if _DEBUGMSG >= _DBG_NORMAL + LOG(LS_INFO) << "Adjusting mss to " << m_mss << " bytes"; +#endif // _DEBUGMSG + } + + if (nTransmit < seg->len) { + LOG_F(LS_VERBOSE) << "mss reduced to " << m_mss; + + SSegment subseg(seg->seq + nTransmit, seg->len - nTransmit, seg->bCtrl); + //subseg.tstamp = seg->tstamp; + subseg.xmit = seg->xmit; + seg->len = nTransmit; + + SList::iterator next = seg; + m_slist.insert(++next, subseg); + } + + if (seg->xmit == 0) { + m_snd_nxt += seg->len; + } + seg->xmit += 1; + //seg->tstamp = now; + if (m_rto_base == 0) { + m_rto_base = now; + } + + return true; +} + +void PseudoTcp::attemptSend(SendFlags sflags) { + uint32 now = Now(); + + if (rtc::TimeDiff(now, m_lastsend) > static_cast(m_rx_rto)) { + m_cwnd = m_mss; + } + +#if _DEBUGMSG + bool bFirst = true; + RTC_UNUSED(bFirst); +#endif // _DEBUGMSG + + while (true) { + uint32 cwnd = m_cwnd; + if ((m_dup_acks == 1) || (m_dup_acks == 2)) { // Limited Transmit + cwnd += m_dup_acks * m_mss; + } + uint32 nWindow = rtc::_min(m_snd_wnd, cwnd); + uint32 nInFlight = m_snd_nxt - m_snd_una; + uint32 nUseable = (nInFlight < nWindow) ? (nWindow - nInFlight) : 0; + + size_t snd_buffered = 0; + m_sbuf.GetBuffered(&snd_buffered); + uint32 nAvailable = + rtc::_min(static_cast(snd_buffered) - nInFlight, m_mss); + + if (nAvailable > nUseable) { + if (nUseable * 4 < nWindow) { + // RFC 813 - avoid SWS + nAvailable = 0; + } else { + nAvailable = nUseable; + } + } + +#if _DEBUGMSG >= _DBG_VERBOSE + if (bFirst) { + size_t available_space = 0; + m_sbuf.GetWriteRemaining(&available_space); + + bFirst = false; + LOG(LS_INFO) << "[cwnd: " << m_cwnd + << " nWindow: " << nWindow + << " nInFlight: " << nInFlight + << " nAvailable: " << nAvailable + << " nQueued: " << snd_buffered + << " nEmpty: " << available_space + << " ssthresh: " << m_ssthresh << "]"; + } +#endif // _DEBUGMSG + + if (nAvailable == 0) { + if (sflags == sfNone) + return; + + // If this is an immediate ack, or the second delayed ack + if ((sflags == sfImmediateAck) || m_t_ack) { + packet(m_snd_nxt, 0, 0, 0); + } else { + m_t_ack = Now(); + } + return; + } + + // Nagle's algorithm. + // If there is data already in-flight, and we haven't a full segment of + // data ready to send then hold off until we get more to send, or the + // in-flight data is acknowledged. + if (m_use_nagling && (m_snd_nxt > m_snd_una) && (nAvailable < m_mss)) { + return; + } + + // Find the next segment to transmit + SList::iterator it = m_slist.begin(); + while (it->xmit > 0) { + ++it; + ASSERT(it != m_slist.end()); + } + SList::iterator seg = it; + + // If the segment is too large, break it into two + if (seg->len > nAvailable) { + SSegment subseg(seg->seq + nAvailable, seg->len - nAvailable, seg->bCtrl); + seg->len = nAvailable; + m_slist.insert(++it, subseg); + } + + if (!transmit(seg, now)) { + LOG_F(LS_VERBOSE) << "transmit failed"; + // TODO: consider closing socket + return; + } + + sflags = sfNone; + } +} + +void +PseudoTcp::closedown(uint32 err) { + LOG(LS_INFO) << "State: TCP_CLOSED"; + m_state = TCP_CLOSED; + if (m_notify) { + m_notify->OnTcpClosed(this, err); + } + //notify(evClose, err); +} + +void +PseudoTcp::adjustMTU() { + // Determine our current mss level, so that we can adjust appropriately later + for (m_msslevel = 0; PACKET_MAXIMUMS[m_msslevel + 1] > 0; ++m_msslevel) { + if (static_cast(PACKET_MAXIMUMS[m_msslevel]) <= m_mtu_advise) { + break; + } + } + m_mss = m_mtu_advise - PACKET_OVERHEAD; + // !?! Should we reset m_largest here? +#if _DEBUGMSG >= _DBG_NORMAL + LOG(LS_INFO) << "Adjusting mss to " << m_mss << " bytes"; +#endif // _DEBUGMSG + // Enforce minimums on ssthresh and cwnd + m_ssthresh = rtc::_max(m_ssthresh, 2 * m_mss); + m_cwnd = rtc::_max(m_cwnd, m_mss); +} + +bool +PseudoTcp::isReceiveBufferFull() const { + size_t available_space = 0; + m_rbuf.GetWriteRemaining(&available_space); + return !available_space; +} + +void +PseudoTcp::disableWindowScale() { + m_support_wnd_scale = false; +} + +void +PseudoTcp::queueConnectMessage() { + rtc::ByteBuffer buf(rtc::ByteBuffer::ORDER_NETWORK); + + buf.WriteUInt8(CTL_CONNECT); + if (m_support_wnd_scale) { + buf.WriteUInt8(TCP_OPT_WND_SCALE); + buf.WriteUInt8(1); + buf.WriteUInt8(m_rwnd_scale); + } + m_snd_wnd = static_cast(buf.Length()); + queue(buf.Data(), static_cast(buf.Length()), true); +} + +void +PseudoTcp::parseOptions(const char* data, uint32 len) { + std::set options_specified; + + // See http://www.freesoft.org/CIE/Course/Section4/8.htm for + // parsing the options list. + rtc::ByteBuffer buf(data, len); + while (buf.Length()) { + uint8 kind = TCP_OPT_EOL; + buf.ReadUInt8(&kind); + + if (kind == TCP_OPT_EOL) { + // End of option list. + break; + } else if (kind == TCP_OPT_NOOP) { + // No op. + continue; + } + + // Length of this option. + ASSERT(len != 0); + RTC_UNUSED(len); + uint8 opt_len = 0; + buf.ReadUInt8(&opt_len); + + // Content of this option. + if (opt_len <= buf.Length()) { + applyOption(kind, buf.Data(), opt_len); + buf.Consume(opt_len); + } else { + LOG(LS_ERROR) << "Invalid option length received."; + return; + } + options_specified.insert(kind); + } + + if (options_specified.find(TCP_OPT_WND_SCALE) == options_specified.end()) { + LOG(LS_WARNING) << "Peer doesn't support window scaling"; + + if (m_rwnd_scale > 0) { + // Peer doesn't support TCP options and window scaling. + // Revert receive buffer size to default value. + resizeReceiveBuffer(DEFAULT_RCV_BUF_SIZE); + m_swnd_scale = 0; + } + } +} + +void +PseudoTcp::applyOption(char kind, const char* data, uint32 len) { + if (kind == TCP_OPT_MSS) { + LOG(LS_WARNING) << "Peer specified MSS option which is not supported."; + // TODO: Implement. + } else if (kind == TCP_OPT_WND_SCALE) { + // Window scale factor. + // http://www.ietf.org/rfc/rfc1323.txt + if (len != 1) { + LOG_F(WARNING) << "Invalid window scale option received."; + return; + } + applyWindowScaleOption(data[0]); + } +} + +void +PseudoTcp::applyWindowScaleOption(uint8 scale_factor) { + m_swnd_scale = scale_factor; +} + +void +PseudoTcp::resizeSendBuffer(uint32 new_size) { + m_sbuf_len = new_size; + m_sbuf.SetCapacity(new_size); +} + +void +PseudoTcp::resizeReceiveBuffer(uint32 new_size) { + uint8 scale_factor = 0; + + // Determine the scale factor such that the scaled window size can fit + // in a 16-bit unsigned integer. + while (new_size > 0xFFFF) { + ++scale_factor; + new_size >>= 1; + } + + // Determine the proper size of the buffer. + new_size <<= scale_factor; + bool result = m_rbuf.SetCapacity(new_size); + + // Make sure the new buffer is large enough to contain data in the old + // buffer. This should always be true because this method is called either + // before connection is established or when peers are exchanging connect + // messages. + ASSERT(result); + RTC_UNUSED(result); + m_rbuf_len = new_size; + m_rwnd_scale = scale_factor; + m_ssthresh = new_size; + + size_t available_space = 0; + m_rbuf.GetWriteRemaining(&available_space); + m_rcv_wnd = static_cast(available_space); +} + +} // namespace cricket diff --git a/webrtc/p2p/base/pseudotcp.h b/webrtc/p2p/base/pseudotcp.h new file mode 100644 index 000000000..b2cfcb79e --- /dev/null +++ b/webrtc/p2p/base/pseudotcp.h @@ -0,0 +1,241 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_PSEUDOTCP_H_ +#define WEBRTC_P2P_BASE_PSEUDOTCP_H_ + +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/stream.h" + +namespace cricket { + +////////////////////////////////////////////////////////////////////// +// IPseudoTcpNotify +////////////////////////////////////////////////////////////////////// + +class PseudoTcp; + +class IPseudoTcpNotify { + public: + // Notification of tcp events + virtual void OnTcpOpen(PseudoTcp* tcp) = 0; + virtual void OnTcpReadable(PseudoTcp* tcp) = 0; + virtual void OnTcpWriteable(PseudoTcp* tcp) = 0; + virtual void OnTcpClosed(PseudoTcp* tcp, uint32 error) = 0; + + // Write the packet onto the network + enum WriteResult { WR_SUCCESS, WR_TOO_LARGE, WR_FAIL }; + virtual WriteResult TcpWritePacket(PseudoTcp* tcp, + const char* buffer, size_t len) = 0; + + protected: + virtual ~IPseudoTcpNotify() {} +}; + +////////////////////////////////////////////////////////////////////// +// PseudoTcp +////////////////////////////////////////////////////////////////////// + +class PseudoTcp { + public: + static uint32 Now(); + + PseudoTcp(IPseudoTcpNotify* notify, uint32 conv); + virtual ~PseudoTcp(); + + int Connect(); + int Recv(char* buffer, size_t len); + int Send(const char* buffer, size_t len); + void Close(bool force); + int GetError(); + + enum TcpState { + TCP_LISTEN, TCP_SYN_SENT, TCP_SYN_RECEIVED, TCP_ESTABLISHED, TCP_CLOSED + }; + TcpState State() const { return m_state; } + + // Call this when the PMTU changes. + void NotifyMTU(uint16 mtu); + + // Call this based on timeout value returned from GetNextClock. + // It's ok to call this too frequently. + void NotifyClock(uint32 now); + + // Call this whenever a packet arrives. + // Returns true if the packet was processed successfully. + bool NotifyPacket(const char * buffer, size_t len); + + // Call this to determine the next time NotifyClock should be called. + // Returns false if the socket is ready to be destroyed. + bool GetNextClock(uint32 now, long& timeout); + + // Call these to get/set option values to tailor this PseudoTcp + // instance's behaviour for the kind of data it will carry. + // If an unrecognized option is set or got, an assertion will fire. + // + // Setting options for OPT_RCVBUF or OPT_SNDBUF after Connect() is called + // will result in an assertion. + enum Option { + OPT_NODELAY, // Whether to enable Nagle's algorithm (0 == off) + OPT_ACKDELAY, // The Delayed ACK timeout (0 == off). + OPT_RCVBUF, // Set the receive buffer size, in bytes. + OPT_SNDBUF, // Set the send buffer size, in bytes. + }; + void GetOption(Option opt, int* value); + void SetOption(Option opt, int value); + + // Returns current congestion window in bytes. + uint32 GetCongestionWindow() const; + + // Returns amount of data in bytes that has been sent, but haven't + // been acknowledged. + uint32 GetBytesInFlight() const; + + // Returns number of bytes that were written in buffer and haven't + // been sent. + uint32 GetBytesBufferedNotSent() const; + + // Returns current round-trip time estimate in milliseconds. + uint32 GetRoundTripTimeEstimateMs() const; + + protected: + enum SendFlags { sfNone, sfDelayedAck, sfImmediateAck }; + + struct Segment { + uint32 conv, seq, ack; + uint8 flags; + uint16 wnd; + const char * data; + uint32 len; + uint32 tsval, tsecr; + }; + + struct SSegment { + SSegment(uint32 s, uint32 l, bool c) + : seq(s), len(l), /*tstamp(0),*/ xmit(0), bCtrl(c) { + } + uint32 seq, len; + //uint32 tstamp; + uint8 xmit; + bool bCtrl; + }; + typedef std::list SList; + + struct RSegment { + uint32 seq, len; + }; + + uint32 queue(const char* data, uint32 len, bool bCtrl); + + // Creates a packet and submits it to the network. This method can either + // send payload or just an ACK packet. + // + // |seq| is the sequence number of this packet. + // |flags| is the flags for sending this packet. + // |offset| is the offset to read from |m_sbuf|. + // |len| is the number of bytes to read from |m_sbuf| as payload. If this + // value is 0 then this is an ACK packet, otherwise this packet has payload. + IPseudoTcpNotify::WriteResult packet(uint32 seq, uint8 flags, + uint32 offset, uint32 len); + bool parse(const uint8* buffer, uint32 size); + + void attemptSend(SendFlags sflags = sfNone); + + void closedown(uint32 err = 0); + + bool clock_check(uint32 now, long& nTimeout); + + bool process(Segment& seg); + bool transmit(const SList::iterator& seg, uint32 now); + + void adjustMTU(); + + protected: + // This method is used in test only to query receive buffer state. + bool isReceiveBufferFull() const; + + // This method is only used in tests, to disable window scaling + // support for testing backward compatibility. + void disableWindowScale(); + + private: + // Queue the connect message with TCP options. + void queueConnectMessage(); + + // Parse TCP options in the header. + void parseOptions(const char* data, uint32 len); + + // Apply a TCP option that has been read from the header. + void applyOption(char kind, const char* data, uint32 len); + + // Apply window scale option. + void applyWindowScaleOption(uint8 scale_factor); + + // Resize the send buffer with |new_size| in bytes. + void resizeSendBuffer(uint32 new_size); + + // Resize the receive buffer with |new_size| in bytes. This call adjusts + // window scale factor |m_swnd_scale| accordingly. + void resizeReceiveBuffer(uint32 new_size); + + IPseudoTcpNotify* m_notify; + enum Shutdown { SD_NONE, SD_GRACEFUL, SD_FORCEFUL } m_shutdown; + int m_error; + + // TCB data + TcpState m_state; + uint32 m_conv; + bool m_bReadEnable, m_bWriteEnable, m_bOutgoing; + uint32 m_lasttraffic; + + // Incoming data + typedef std::list RList; + RList m_rlist; + uint32 m_rbuf_len, m_rcv_nxt, m_rcv_wnd, m_lastrecv; + uint8 m_rwnd_scale; // Window scale factor. + rtc::FifoBuffer m_rbuf; + + // Outgoing data + SList m_slist; + uint32 m_sbuf_len, m_snd_nxt, m_snd_wnd, m_lastsend, m_snd_una; + uint8 m_swnd_scale; // Window scale factor. + rtc::FifoBuffer m_sbuf; + + // Maximum segment size, estimated protocol level, largest segment sent + uint32 m_mss, m_msslevel, m_largest, m_mtu_advise; + // Retransmit timer + uint32 m_rto_base; + + // Timestamp tracking + uint32 m_ts_recent, m_ts_lastack; + + // Round-trip calculation + uint32 m_rx_rttvar, m_rx_srtt, m_rx_rto; + + // Congestion avoidance, Fast retransmit/recovery, Delayed ACKs + uint32 m_ssthresh, m_cwnd; + uint8 m_dup_acks; + uint32 m_recover; + uint32 m_t_ack; + + // Configuration options + bool m_use_nagling; + uint32 m_ack_delay; + + // This is used by unit tests to test backward compatibility of + // PseudoTcp implementations that don't support window scaling. + bool m_support_wnd_scale; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_PSEUDOTCP_H_ diff --git a/webrtc/p2p/base/pseudotcp_unittest.cc b/webrtc/p2p/base/pseudotcp_unittest.cc new file mode 100644 index 000000000..f5ea7ace2 --- /dev/null +++ b/webrtc/p2p/base/pseudotcp_unittest.cc @@ -0,0 +1,841 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/p2p/base/pseudotcp.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/messagehandler.h" +#include "webrtc/base/stream.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" + +using cricket::PseudoTcp; + +static const int kConnectTimeoutMs = 10000; // ~3 * default RTO of 3000ms +static const int kTransferTimeoutMs = 15000; +static const int kBlockSize = 4096; + +class PseudoTcpForTest : public cricket::PseudoTcp { + public: + PseudoTcpForTest(cricket::IPseudoTcpNotify* notify, uint32 conv) + : PseudoTcp(notify, conv) { + } + + bool isReceiveBufferFull() const { + return PseudoTcp::isReceiveBufferFull(); + } + + void disableWindowScale() { + PseudoTcp::disableWindowScale(); + } +}; + +class PseudoTcpTestBase : public testing::Test, + public rtc::MessageHandler, + public cricket::IPseudoTcpNotify { + public: + PseudoTcpTestBase() + : local_(this, 1), + remote_(this, 1), + have_connected_(false), + have_disconnected_(false), + local_mtu_(65535), + remote_mtu_(65535), + delay_(0), + loss_(0) { + // Set use of the test RNG to get predictable loss patterns. + rtc::SetRandomTestMode(true); + } + ~PseudoTcpTestBase() { + // Put it back for the next test. + rtc::SetRandomTestMode(false); + } + void SetLocalMtu(int mtu) { + local_.NotifyMTU(mtu); + local_mtu_ = mtu; + } + void SetRemoteMtu(int mtu) { + remote_.NotifyMTU(mtu); + remote_mtu_ = mtu; + } + void SetDelay(int delay) { + delay_ = delay; + } + void SetLoss(int percent) { + loss_ = percent; + } + void SetOptNagling(bool enable_nagles) { + local_.SetOption(PseudoTcp::OPT_NODELAY, !enable_nagles); + remote_.SetOption(PseudoTcp::OPT_NODELAY, !enable_nagles); + } + void SetOptAckDelay(int ack_delay) { + local_.SetOption(PseudoTcp::OPT_ACKDELAY, ack_delay); + remote_.SetOption(PseudoTcp::OPT_ACKDELAY, ack_delay); + } + void SetOptSndBuf(int size) { + local_.SetOption(PseudoTcp::OPT_SNDBUF, size); + remote_.SetOption(PseudoTcp::OPT_SNDBUF, size); + } + void SetRemoteOptRcvBuf(int size) { + remote_.SetOption(PseudoTcp::OPT_RCVBUF, size); + } + void SetLocalOptRcvBuf(int size) { + local_.SetOption(PseudoTcp::OPT_RCVBUF, size); + } + void DisableRemoteWindowScale() { + remote_.disableWindowScale(); + } + void DisableLocalWindowScale() { + local_.disableWindowScale(); + } + + protected: + int Connect() { + int ret = local_.Connect(); + if (ret == 0) { + UpdateLocalClock(); + } + return ret; + } + void Close() { + local_.Close(false); + UpdateLocalClock(); + } + + enum { MSG_LPACKET, MSG_RPACKET, MSG_LCLOCK, MSG_RCLOCK, MSG_IOCOMPLETE, + MSG_WRITE}; + virtual void OnTcpOpen(PseudoTcp* tcp) { + // Consider ourselves connected when the local side gets OnTcpOpen. + // OnTcpWriteable isn't fired at open, so we trigger it now. + LOG(LS_VERBOSE) << "Opened"; + if (tcp == &local_) { + have_connected_ = true; + OnTcpWriteable(tcp); + } + } + // Test derived from the base should override + // virtual void OnTcpReadable(PseudoTcp* tcp) + // and + // virtual void OnTcpWritable(PseudoTcp* tcp) + virtual void OnTcpClosed(PseudoTcp* tcp, uint32 error) { + // Consider ourselves closed when the remote side gets OnTcpClosed. + // TODO: OnTcpClosed is only ever notified in case of error in + // the current implementation. Solicited close is not (yet) supported. + LOG(LS_VERBOSE) << "Closed"; + EXPECT_EQ(0U, error); + if (tcp == &remote_) { + have_disconnected_ = true; + } + } + virtual WriteResult TcpWritePacket(PseudoTcp* tcp, + const char* buffer, size_t len) { + // Randomly drop the desired percentage of packets. + // Also drop packets that are larger than the configured MTU. + if (rtc::CreateRandomId() % 100 < static_cast(loss_)) { + LOG(LS_VERBOSE) << "Randomly dropping packet, size=" << len; + } else if (len > static_cast( + rtc::_min(local_mtu_, remote_mtu_))) { + LOG(LS_VERBOSE) << "Dropping packet that exceeds path MTU, size=" << len; + } else { + int id = (tcp == &local_) ? MSG_RPACKET : MSG_LPACKET; + std::string packet(buffer, len); + rtc::Thread::Current()->PostDelayed(delay_, this, id, + rtc::WrapMessageData(packet)); + } + return WR_SUCCESS; + } + + void UpdateLocalClock() { UpdateClock(&local_, MSG_LCLOCK); } + void UpdateRemoteClock() { UpdateClock(&remote_, MSG_RCLOCK); } + void UpdateClock(PseudoTcp* tcp, uint32 message) { + long interval = 0; // NOLINT + tcp->GetNextClock(PseudoTcp::Now(), interval); + interval = rtc::_max(interval, 0L); // sometimes interval is < 0 + rtc::Thread::Current()->Clear(this, message); + rtc::Thread::Current()->PostDelayed(interval, this, message); + } + + virtual void OnMessage(rtc::Message* message) { + switch (message->message_id) { + case MSG_LPACKET: { + const std::string& s( + rtc::UseMessageData(message->pdata)); + local_.NotifyPacket(s.c_str(), s.size()); + UpdateLocalClock(); + break; + } + case MSG_RPACKET: { + const std::string& s( + rtc::UseMessageData(message->pdata)); + remote_.NotifyPacket(s.c_str(), s.size()); + UpdateRemoteClock(); + break; + } + case MSG_LCLOCK: + local_.NotifyClock(PseudoTcp::Now()); + UpdateLocalClock(); + break; + case MSG_RCLOCK: + remote_.NotifyClock(PseudoTcp::Now()); + UpdateRemoteClock(); + break; + default: + break; + } + delete message->pdata; + } + + PseudoTcpForTest local_; + PseudoTcpForTest remote_; + rtc::MemoryStream send_stream_; + rtc::MemoryStream recv_stream_; + bool have_connected_; + bool have_disconnected_; + int local_mtu_; + int remote_mtu_; + int delay_; + int loss_; +}; + +class PseudoTcpTest : public PseudoTcpTestBase { + public: + void TestTransfer(int size) { + uint32 start, elapsed; + size_t received; + // Create some dummy data to send. + send_stream_.ReserveSize(size); + for (int i = 0; i < size; ++i) { + char ch = static_cast(i); + send_stream_.Write(&ch, 1, NULL, NULL); + } + send_stream_.Rewind(); + // Prepare the receive stream. + recv_stream_.ReserveSize(size); + // Connect and wait until connected. + start = rtc::Time(); + EXPECT_EQ(0, Connect()); + EXPECT_TRUE_WAIT(have_connected_, kConnectTimeoutMs); + // Sending will start from OnTcpWriteable and complete when all data has + // been received. + EXPECT_TRUE_WAIT(have_disconnected_, kTransferTimeoutMs); + elapsed = rtc::TimeSince(start); + recv_stream_.GetSize(&received); + // Ensure we closed down OK and we got the right data. + // TODO: Ensure the errors are cleared properly. + //EXPECT_EQ(0, local_.GetError()); + //EXPECT_EQ(0, remote_.GetError()); + EXPECT_EQ(static_cast(size), received); + EXPECT_EQ(0, memcmp(send_stream_.GetBuffer(), + recv_stream_.GetBuffer(), size)); + LOG(LS_INFO) << "Transferred " << received << " bytes in " << elapsed + << " ms (" << size * 8 / elapsed << " Kbps)"; + } + + private: + // IPseudoTcpNotify interface + + virtual void OnTcpReadable(PseudoTcp* tcp) { + // Stream bytes to the recv stream as they arrive. + if (tcp == &remote_) { + ReadData(); + + // TODO: OnTcpClosed() is currently only notified on error - + // there is no on-the-wire equivalent of TCP FIN. + // So we fake the notification when all the data has been read. + size_t received, required; + recv_stream_.GetPosition(&received); + send_stream_.GetSize(&required); + if (received == required) + OnTcpClosed(&remote_, 0); + } + } + virtual void OnTcpWriteable(PseudoTcp* tcp) { + // Write bytes from the send stream when we can. + // Shut down when we've sent everything. + if (tcp == &local_) { + LOG(LS_VERBOSE) << "Flow Control Lifted"; + bool done; + WriteData(&done); + if (done) { + Close(); + } + } + } + + void ReadData() { + char block[kBlockSize]; + size_t position; + int rcvd; + do { + rcvd = remote_.Recv(block, sizeof(block)); + if (rcvd != -1) { + recv_stream_.Write(block, rcvd, NULL, NULL); + recv_stream_.GetPosition(&position); + LOG(LS_VERBOSE) << "Received: " << position; + } + } while (rcvd > 0); + } + void WriteData(bool* done) { + size_t position, tosend; + int sent; + char block[kBlockSize]; + do { + send_stream_.GetPosition(&position); + if (send_stream_.Read(block, sizeof(block), &tosend, NULL) != + rtc::SR_EOS) { + sent = local_.Send(block, tosend); + UpdateLocalClock(); + if (sent != -1) { + send_stream_.SetPosition(position + sent); + LOG(LS_VERBOSE) << "Sent: " << position + sent; + } else { + send_stream_.SetPosition(position); + LOG(LS_VERBOSE) << "Flow Controlled"; + } + } else { + sent = static_cast(tosend = 0); + } + } while (sent > 0); + *done = (tosend == 0); + } + + private: + rtc::MemoryStream send_stream_; + rtc::MemoryStream recv_stream_; +}; + + +class PseudoTcpTestPingPong : public PseudoTcpTestBase { + public: + PseudoTcpTestPingPong() + : iterations_remaining_(0), + sender_(NULL), + receiver_(NULL), + bytes_per_send_(0) { + } + void SetBytesPerSend(int bytes) { + bytes_per_send_ = bytes; + } + void TestPingPong(int size, int iterations) { + uint32 start, elapsed; + iterations_remaining_ = iterations; + receiver_ = &remote_; + sender_ = &local_; + // Create some dummy data to send. + send_stream_.ReserveSize(size); + for (int i = 0; i < size; ++i) { + char ch = static_cast(i); + send_stream_.Write(&ch, 1, NULL, NULL); + } + send_stream_.Rewind(); + // Prepare the receive stream. + recv_stream_.ReserveSize(size); + // Connect and wait until connected. + start = rtc::Time(); + EXPECT_EQ(0, Connect()); + EXPECT_TRUE_WAIT(have_connected_, kConnectTimeoutMs); + // Sending will start from OnTcpWriteable and stop when the required + // number of iterations have completed. + EXPECT_TRUE_WAIT(have_disconnected_, kTransferTimeoutMs); + elapsed = rtc::TimeSince(start); + LOG(LS_INFO) << "Performed " << iterations << " pings in " + << elapsed << " ms"; + } + + private: + // IPseudoTcpNotify interface + + virtual void OnTcpReadable(PseudoTcp* tcp) { + if (tcp != receiver_) { + LOG_F(LS_ERROR) << "unexpected OnTcpReadable"; + return; + } + // Stream bytes to the recv stream as they arrive. + ReadData(); + // If we've received the desired amount of data, rewind things + // and send it back the other way! + size_t position, desired; + recv_stream_.GetPosition(&position); + send_stream_.GetSize(&desired); + if (position == desired) { + if (receiver_ == &local_ && --iterations_remaining_ == 0) { + Close(); + // TODO: Fake OnTcpClosed() on the receiver for now. + OnTcpClosed(&remote_, 0); + return; + } + PseudoTcp* tmp = receiver_; + receiver_ = sender_; + sender_ = tmp; + recv_stream_.Rewind(); + send_stream_.Rewind(); + OnTcpWriteable(sender_); + } + } + virtual void OnTcpWriteable(PseudoTcp* tcp) { + if (tcp != sender_) + return; + // Write bytes from the send stream when we can. + // Shut down when we've sent everything. + LOG(LS_VERBOSE) << "Flow Control Lifted"; + WriteData(); + } + + void ReadData() { + char block[kBlockSize]; + size_t position; + int rcvd; + do { + rcvd = receiver_->Recv(block, sizeof(block)); + if (rcvd != -1) { + recv_stream_.Write(block, rcvd, NULL, NULL); + recv_stream_.GetPosition(&position); + LOG(LS_VERBOSE) << "Received: " << position; + } + } while (rcvd > 0); + } + void WriteData() { + size_t position, tosend; + int sent; + char block[kBlockSize]; + do { + send_stream_.GetPosition(&position); + tosend = bytes_per_send_ ? bytes_per_send_ : sizeof(block); + if (send_stream_.Read(block, tosend, &tosend, NULL) != + rtc::SR_EOS) { + sent = sender_->Send(block, tosend); + UpdateLocalClock(); + if (sent != -1) { + send_stream_.SetPosition(position + sent); + LOG(LS_VERBOSE) << "Sent: " << position + sent; + } else { + send_stream_.SetPosition(position); + LOG(LS_VERBOSE) << "Flow Controlled"; + } + } else { + sent = static_cast(tosend = 0); + } + } while (sent > 0); + } + + private: + int iterations_remaining_; + PseudoTcp* sender_; + PseudoTcp* receiver_; + int bytes_per_send_; +}; + +// Fill the receiver window until it is full, drain it and then +// fill it with the same amount. This is to test that receiver window +// contracts and enlarges correctly. +class PseudoTcpTestReceiveWindow : public PseudoTcpTestBase { + public: + // Not all the data are transfered, |size| just need to be big enough + // to fill up the receiver window twice. + void TestTransfer(int size) { + // Create some dummy data to send. + send_stream_.ReserveSize(size); + for (int i = 0; i < size; ++i) { + char ch = static_cast(i); + send_stream_.Write(&ch, 1, NULL, NULL); + } + send_stream_.Rewind(); + + // Prepare the receive stream. + recv_stream_.ReserveSize(size); + + // Connect and wait until connected. + EXPECT_EQ(0, Connect()); + EXPECT_TRUE_WAIT(have_connected_, kConnectTimeoutMs); + + rtc::Thread::Current()->Post(this, MSG_WRITE); + EXPECT_TRUE_WAIT(have_disconnected_, kTransferTimeoutMs); + + ASSERT_EQ(2u, send_position_.size()); + ASSERT_EQ(2u, recv_position_.size()); + + const size_t estimated_recv_window = EstimateReceiveWindowSize(); + + // The difference in consecutive send positions should equal the + // receive window size or match very closely. This verifies that receive + // window is open after receiver drained all the data. + const size_t send_position_diff = send_position_[1] - send_position_[0]; + EXPECT_GE(1024u, estimated_recv_window - send_position_diff); + + // Receiver drained the receive window twice. + EXPECT_EQ(2 * estimated_recv_window, recv_position_[1]); + } + + virtual void OnMessage(rtc::Message* message) { + int message_id = message->message_id; + PseudoTcpTestBase::OnMessage(message); + + switch (message_id) { + case MSG_WRITE: { + WriteData(); + break; + } + default: + break; + } + } + + uint32 EstimateReceiveWindowSize() const { + return static_cast(recv_position_[0]); + } + + uint32 EstimateSendWindowSize() const { + return static_cast(send_position_[0] - recv_position_[0]); + } + + private: + // IPseudoTcpNotify interface + virtual void OnTcpReadable(PseudoTcp* tcp) { + } + + virtual void OnTcpWriteable(PseudoTcp* tcp) { + } + + void ReadUntilIOPending() { + char block[kBlockSize]; + size_t position; + int rcvd; + + do { + rcvd = remote_.Recv(block, sizeof(block)); + if (rcvd != -1) { + recv_stream_.Write(block, rcvd, NULL, NULL); + recv_stream_.GetPosition(&position); + LOG(LS_VERBOSE) << "Received: " << position; + } + } while (rcvd > 0); + + recv_stream_.GetPosition(&position); + recv_position_.push_back(position); + + // Disconnect if we have done two transfers. + if (recv_position_.size() == 2u) { + Close(); + OnTcpClosed(&remote_, 0); + } else { + WriteData(); + } + } + + void WriteData() { + size_t position, tosend; + int sent; + char block[kBlockSize]; + do { + send_stream_.GetPosition(&position); + if (send_stream_.Read(block, sizeof(block), &tosend, NULL) != + rtc::SR_EOS) { + sent = local_.Send(block, tosend); + UpdateLocalClock(); + if (sent != -1) { + send_stream_.SetPosition(position + sent); + LOG(LS_VERBOSE) << "Sent: " << position + sent; + } else { + send_stream_.SetPosition(position); + LOG(LS_VERBOSE) << "Flow Controlled"; + } + } else { + sent = static_cast(tosend = 0); + } + } while (sent > 0); + // At this point, we've filled up the available space in the send queue. + + int message_queue_size = + static_cast(rtc::Thread::Current()->size()); + // The message queue will always have at least 2 messages, an RCLOCK and + // an LCLOCK, since they are added back on the delay queue at the same time + // they are pulled off and therefore are never really removed. + if (message_queue_size > 2) { + // If there are non-clock messages remaining, attempt to continue sending + // after giving those messages time to process, which should free up the + // send buffer. + rtc::Thread::Current()->PostDelayed(10, this, MSG_WRITE); + } else { + if (!remote_.isReceiveBufferFull()) { + LOG(LS_ERROR) << "This shouldn't happen - the send buffer is full, " + << "the receive buffer is not, and there are no " + << "remaining messages to process."; + } + send_stream_.GetPosition(&position); + send_position_.push_back(position); + + // Drain the receiver buffer. + ReadUntilIOPending(); + } + } + + private: + rtc::MemoryStream send_stream_; + rtc::MemoryStream recv_stream_; + + std::vector send_position_; + std::vector recv_position_; +}; + +// Basic end-to-end data transfer tests + +// Test the normal case of sending data from one side to the other. +TEST_F(PseudoTcpTest, TestSend) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + TestTransfer(1000000); +} + +// Test sending data with a 50 ms RTT. Transmission should take longer due +// to a slower ramp-up in send rate. +TEST_F(PseudoTcpTest, TestSendWithDelay) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + SetDelay(50); + TestTransfer(1000000); +} + +// Test sending data with packet loss. Transmission should take much longer due +// to send back-off when loss occurs. +TEST_F(PseudoTcpTest, TestSendWithLoss) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + SetLoss(10); + TestTransfer(100000); // less data so test runs faster +} + +// Test sending data with a 50 ms RTT and 10% packet loss. Transmission should +// take much longer due to send back-off and slower detection of loss. +TEST_F(PseudoTcpTest, TestSendWithDelayAndLoss) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + SetDelay(50); + SetLoss(10); + TestTransfer(100000); // less data so test runs faster +} + +// Test sending data with 10% packet loss and Nagling disabled. Transmission +// should take about the same time as with Nagling enabled. +TEST_F(PseudoTcpTest, TestSendWithLossAndOptNaglingOff) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + SetLoss(10); + SetOptNagling(false); + TestTransfer(100000); // less data so test runs faster +} + +// Test sending data with 10% packet loss and Delayed ACK disabled. +// Transmission should be slightly faster than with it enabled. +TEST_F(PseudoTcpTest, TestSendWithLossAndOptAckDelayOff) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + SetLoss(10); + SetOptAckDelay(0); + TestTransfer(100000); +} + +// Test sending data with 50ms delay and Nagling disabled. +TEST_F(PseudoTcpTest, TestSendWithDelayAndOptNaglingOff) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + SetDelay(50); + SetOptNagling(false); + TestTransfer(100000); // less data so test runs faster +} + +// Test sending data with 50ms delay and Delayed ACK disabled. +TEST_F(PseudoTcpTest, TestSendWithDelayAndOptAckDelayOff) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + SetDelay(50); + SetOptAckDelay(0); + TestTransfer(100000); // less data so test runs faster +} + +// Test a large receive buffer with a sender that doesn't support scaling. +TEST_F(PseudoTcpTest, TestSendRemoteNoWindowScale) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + SetLocalOptRcvBuf(100000); + DisableRemoteWindowScale(); + TestTransfer(1000000); +} + +// Test a large sender-side receive buffer with a receiver that doesn't support +// scaling. +TEST_F(PseudoTcpTest, TestSendLocalNoWindowScale) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + SetRemoteOptRcvBuf(100000); + DisableLocalWindowScale(); + TestTransfer(1000000); +} + +// Test when both sides use window scaling. +TEST_F(PseudoTcpTest, TestSendBothUseWindowScale) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + SetRemoteOptRcvBuf(100000); + SetLocalOptRcvBuf(100000); + TestTransfer(1000000); +} + +// Test using a large window scale value. +TEST_F(PseudoTcpTest, TestSendLargeInFlight) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + SetRemoteOptRcvBuf(100000); + SetLocalOptRcvBuf(100000); + SetOptSndBuf(150000); + TestTransfer(1000000); +} + +TEST_F(PseudoTcpTest, TestSendBothUseLargeWindowScale) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + SetRemoteOptRcvBuf(1000000); + SetLocalOptRcvBuf(1000000); + TestTransfer(10000000); +} + +// Test using a small receive buffer. +TEST_F(PseudoTcpTest, TestSendSmallReceiveBuffer) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + SetRemoteOptRcvBuf(10000); + SetLocalOptRcvBuf(10000); + TestTransfer(1000000); +} + +// Test using a very small receive buffer. +TEST_F(PseudoTcpTest, TestSendVerySmallReceiveBuffer) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + SetRemoteOptRcvBuf(100); + SetLocalOptRcvBuf(100); + TestTransfer(100000); +} + +// Ping-pong (request/response) tests + +// Test sending <= 1x MTU of data in each ping/pong. Should take <10ms. +TEST_F(PseudoTcpTestPingPong, TestPingPong1xMtu) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + TestPingPong(100, 100); +} + +// Test sending 2x-3x MTU of data in each ping/pong. Should take <10ms. +TEST_F(PseudoTcpTestPingPong, TestPingPong3xMtu) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + TestPingPong(400, 100); +} + +// Test sending 1x-2x MTU of data in each ping/pong. +// Should take ~1s, due to interaction between Nagling and Delayed ACK. +TEST_F(PseudoTcpTestPingPong, TestPingPong2xMtu) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + TestPingPong(2000, 5); +} + +// Test sending 1x-2x MTU of data in each ping/pong with Delayed ACK off. +// Should take <10ms. +TEST_F(PseudoTcpTestPingPong, TestPingPong2xMtuWithAckDelayOff) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + SetOptAckDelay(0); + TestPingPong(2000, 100); +} + +// Test sending 1x-2x MTU of data in each ping/pong with Nagling off. +// Should take <10ms. +TEST_F(PseudoTcpTestPingPong, TestPingPong2xMtuWithNaglingOff) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + SetOptNagling(false); + TestPingPong(2000, 5); +} + +// Test sending a ping as pair of short (non-full) segments. +// Should take ~1s, due to Delayed ACK interaction with Nagling. +TEST_F(PseudoTcpTestPingPong, TestPingPongShortSegments) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + SetOptAckDelay(5000); + SetBytesPerSend(50); // i.e. two Send calls per payload + TestPingPong(100, 5); +} + +// Test sending ping as a pair of short (non-full) segments, with Nagling off. +// Should take <10ms. +TEST_F(PseudoTcpTestPingPong, TestPingPongShortSegmentsWithNaglingOff) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + SetOptNagling(false); + SetBytesPerSend(50); // i.e. two Send calls per payload + TestPingPong(100, 5); +} + +// Test sending <= 1x MTU of data ping/pong, in two segments, no Delayed ACK. +// Should take ~1s. +TEST_F(PseudoTcpTestPingPong, TestPingPongShortSegmentsWithAckDelayOff) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + SetBytesPerSend(50); // i.e. two Send calls per payload + SetOptAckDelay(0); + TestPingPong(100, 5); +} + +// Test that receive window expands and contract correctly. +TEST_F(PseudoTcpTestReceiveWindow, TestReceiveWindow) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + SetOptNagling(false); + SetOptAckDelay(0); + TestTransfer(1024 * 1000); +} + +// Test setting send window size to a very small value. +TEST_F(PseudoTcpTestReceiveWindow, TestSetVerySmallSendWindowSize) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + SetOptNagling(false); + SetOptAckDelay(0); + SetOptSndBuf(900); + TestTransfer(1024 * 1000); + EXPECT_EQ(900u, EstimateSendWindowSize()); +} + +// Test setting receive window size to a value other than default. +TEST_F(PseudoTcpTestReceiveWindow, TestSetReceiveWindowSize) { + SetLocalMtu(1500); + SetRemoteMtu(1500); + SetOptNagling(false); + SetOptAckDelay(0); + SetRemoteOptRcvBuf(100000); + SetLocalOptRcvBuf(100000); + TestTransfer(1024 * 1000); + EXPECT_EQ(100000u, EstimateReceiveWindowSize()); +} + +/* Test sending data with mismatched MTUs. We should detect this and reduce +// our packet size accordingly. +// TODO: This doesn't actually work right now. The current code +// doesn't detect if the MTU is set too high on either side. +TEST_F(PseudoTcpTest, TestSendWithMismatchedMtus) { + SetLocalMtu(1500); + SetRemoteMtu(1280); + TestTransfer(1000000); +} +*/ diff --git a/webrtc/p2p/base/rawtransport.cc b/webrtc/p2p/base/rawtransport.cc new file mode 100644 index 000000000..374ed984e --- /dev/null +++ b/webrtc/p2p/base/rawtransport.cc @@ -0,0 +1,115 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/parsing.h" +#include "webrtc/p2p/base/rawtransport.h" +#include "webrtc/p2p/base/rawtransportchannel.h" +#include "webrtc/p2p/base/sessionmanager.h" +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/base/common.h" + +#if defined(FEATURE_ENABLE_PSTN) +namespace cricket { + +RawTransport::RawTransport(rtc::Thread* signaling_thread, + rtc::Thread* worker_thread, + const std::string& content_name, + PortAllocator* allocator) + : Transport(signaling_thread, worker_thread, + content_name, NS_GINGLE_RAW, allocator) { +} + +RawTransport::~RawTransport() { + DestroyAllChannels(); +} + +bool RawTransport::ParseCandidates(SignalingProtocol protocol, + const buzz::XmlElement* elem, + const CandidateTranslator* translator, + Candidates* candidates, + ParseError* error) { + for (const buzz::XmlElement* cand_elem = elem->FirstElement(); + cand_elem != NULL; + cand_elem = cand_elem->NextElement()) { + if (cand_elem->Name() == QN_GINGLE_RAW_CHANNEL) { + if (!cand_elem->HasAttr(buzz::QN_NAME)) { + return BadParse("no channel name given", error); + } + if (type() != cand_elem->Attr(buzz::QN_NAME)) { + return BadParse("channel named does not exist", error); + } + rtc::SocketAddress addr; + if (!ParseRawAddress(cand_elem, &addr, error)) + return false; + + Candidate candidate; + candidate.set_component(1); + candidate.set_address(addr); + candidates->push_back(candidate); + } + } + return true; +} + +bool RawTransport::WriteCandidates(SignalingProtocol protocol, + const Candidates& candidates, + const CandidateTranslator* translator, + XmlElements* candidate_elems, + WriteError* error) { + for (std::vector::const_iterator + cand = candidates.begin(); + cand != candidates.end(); + ++cand) { + ASSERT(cand->component() == 1); + ASSERT(cand->protocol() == "udp"); + rtc::SocketAddress addr = cand->address(); + + buzz::XmlElement* elem = new buzz::XmlElement(QN_GINGLE_RAW_CHANNEL); + elem->SetAttr(buzz::QN_NAME, type()); + elem->SetAttr(QN_ADDRESS, addr.ipaddr().ToString()); + elem->SetAttr(QN_PORT, addr.PortAsString()); + candidate_elems->push_back(elem); + } + return true; +} + +bool RawTransport::ParseRawAddress(const buzz::XmlElement* elem, + rtc::SocketAddress* addr, + ParseError* error) { + // Make sure the required attributes exist + if (!elem->HasAttr(QN_ADDRESS) || + !elem->HasAttr(QN_PORT)) { + return BadParse("channel missing required attribute", error); + } + + // Parse the address. + if (!ParseAddress(elem, QN_ADDRESS, QN_PORT, addr, error)) + return false; + + return true; +} + +TransportChannelImpl* RawTransport::CreateTransportChannel(int component) { + return new RawTransportChannel(content_name(), component, this, + worker_thread(), + port_allocator()); +} + +void RawTransport::DestroyTransportChannel(TransportChannelImpl* channel) { + delete channel; +} + +} // namespace cricket +#endif // defined(FEATURE_ENABLE_PSTN) diff --git a/webrtc/p2p/base/rawtransport.h b/webrtc/p2p/base/rawtransport.h new file mode 100644 index 000000000..dbe8f9868 --- /dev/null +++ b/webrtc/p2p/base/rawtransport.h @@ -0,0 +1,64 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_RAWTRANSPORT_H_ +#define WEBRTC_P2P_BASE_RAWTRANSPORT_H_ + +#include +#include "webrtc/p2p/base/transport.h" + +#if defined(FEATURE_ENABLE_PSTN) +namespace cricket { + +// Implements a transport that only sends raw packets, no STUN. As a result, +// it cannot do pings to determine connectivity, so it only uses a single port +// that it thinks will work. +class RawTransport : public Transport, public TransportParser { + public: + RawTransport(rtc::Thread* signaling_thread, + rtc::Thread* worker_thread, + const std::string& content_name, + PortAllocator* allocator); + virtual ~RawTransport(); + + virtual bool ParseCandidates(SignalingProtocol protocol, + const buzz::XmlElement* elem, + const CandidateTranslator* translator, + Candidates* candidates, + ParseError* error); + virtual bool WriteCandidates(SignalingProtocol protocol, + const Candidates& candidates, + const CandidateTranslator* translator, + XmlElements* candidate_elems, + WriteError* error); + + protected: + // Creates and destroys raw channels. + virtual TransportChannelImpl* CreateTransportChannel(int component); + virtual void DestroyTransportChannel(TransportChannelImpl* channel); + + private: + // Parses the given element, which should describe the address to use for a + // given channel. This will return false and signal an error if the address + // or channel name is bad. + bool ParseRawAddress(const buzz::XmlElement* elem, + rtc::SocketAddress* addr, + ParseError* error); + + friend class RawTransportChannel; // For ParseAddress. + + DISALLOW_EVIL_CONSTRUCTORS(RawTransport); +}; + +} // namespace cricket + +#endif // defined(FEATURE_ENABLE_PSTN) + +#endif // WEBRTC_P2P_BASE_RAWTRANSPORT_H_ diff --git a/webrtc/p2p/base/rawtransportchannel.cc b/webrtc/p2p/base/rawtransportchannel.cc new file mode 100644 index 000000000..5779c6e56 --- /dev/null +++ b/webrtc/p2p/base/rawtransportchannel.cc @@ -0,0 +1,260 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/rawtransportchannel.h" + +#include +#include +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/portallocator.h" +#include "webrtc/p2p/base/portinterface.h" +#include "webrtc/p2p/base/rawtransport.h" +#include "webrtc/p2p/base/relayport.h" +#include "webrtc/p2p/base/sessionmanager.h" +#include "webrtc/p2p/base/stunport.h" +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/base/common.h" + +#if defined(FEATURE_ENABLE_PSTN) + +namespace { + +const uint32 MSG_DESTROY_RTC_UNUSED_PORTS = 1; + +} // namespace + +namespace cricket { + +RawTransportChannel::RawTransportChannel(const std::string& content_name, + int component, + RawTransport* transport, + rtc::Thread *worker_thread, + PortAllocator *allocator) + : TransportChannelImpl(content_name, component), + raw_transport_(transport), + allocator_(allocator), + allocator_session_(NULL), + stun_port_(NULL), + relay_port_(NULL), + port_(NULL), + use_relay_(false) { + if (worker_thread == NULL) + worker_thread_ = raw_transport_->worker_thread(); + else + worker_thread_ = worker_thread; +} + +RawTransportChannel::~RawTransportChannel() { + delete allocator_session_; +} + +int RawTransportChannel::SendPacket(const char *data, size_t size, + const rtc::PacketOptions& options, + int flags) { + if (port_ == NULL) + return -1; + if (remote_address_.IsNil()) + return -1; + if (flags != 0) + return -1; + return port_->SendTo(data, size, remote_address_, options, true); +} + +int RawTransportChannel::SetOption(rtc::Socket::Option opt, int value) { + // TODO: allow these to be set before we have a port + if (port_ == NULL) + return -1; + return port_->SetOption(opt, value); +} + +int RawTransportChannel::GetError() { + return (port_ != NULL) ? port_->GetError() : 0; +} + +void RawTransportChannel::Connect() { + // Create an allocator that only returns stun and relay ports. + // Use empty string for ufrag and pwd here. There won't be any STUN or relay + // interactions when using RawTC. + // TODO: Change raw to only use local udp ports. + allocator_session_ = allocator_->CreateSession( + SessionId(), content_name(), component(), "", ""); + + uint32 flags = PORTALLOCATOR_DISABLE_UDP | PORTALLOCATOR_DISABLE_TCP; + +#if !defined(FEATURE_ENABLE_STUN_CLASSIFICATION) + flags |= PORTALLOCATOR_DISABLE_RELAY; +#endif + allocator_session_->set_flags(flags); + allocator_session_->SignalPortReady.connect( + this, &RawTransportChannel::OnPortReady); + allocator_session_->SignalCandidatesReady.connect( + this, &RawTransportChannel::OnCandidatesReady); + + // The initial ports will include stun. + allocator_session_->StartGettingPorts(); +} + +void RawTransportChannel::Reset() { + set_readable(false); + set_writable(false); + + delete allocator_session_; + + allocator_session_ = NULL; + stun_port_ = NULL; + relay_port_ = NULL; + port_ = NULL; + remote_address_ = rtc::SocketAddress(); +} + +void RawTransportChannel::OnCandidate(const Candidate& candidate) { + remote_address_ = candidate.address(); + ASSERT(!remote_address_.IsNil()); + set_readable(true); + + // We can write once we have a port and a remote address. + if (port_ != NULL) + SetWritable(); +} + +void RawTransportChannel::OnRemoteAddress( + const rtc::SocketAddress& remote_address) { + remote_address_ = remote_address; + set_readable(true); + + if (port_ != NULL) + SetWritable(); +} + +// Note about stun classification +// Code to classify our NAT type and use the relay port if we are behind an +// asymmetric NAT is under a FEATURE_ENABLE_STUN_CLASSIFICATION #define. +// To turn this one we will have to enable a second stun address and make sure +// that the relay server works for raw UDP. +// +// Another option is to classify the NAT type early and not offer the raw +// transport type at all if we can't support it. + +void RawTransportChannel::OnPortReady( + PortAllocatorSession* session, PortInterface* port) { + ASSERT(session == allocator_session_); + + if (port->Type() == STUN_PORT_TYPE) { + stun_port_ = static_cast(port); + } else if (port->Type() == RELAY_PORT_TYPE) { + relay_port_ = static_cast(port); + } else { + ASSERT(false); + } +} + +void RawTransportChannel::OnCandidatesReady( + PortAllocatorSession *session, const std::vector& candidates) { + ASSERT(session == allocator_session_); + ASSERT(candidates.size() >= 1); + + // The most recent candidate is the one we haven't seen yet. + Candidate c = candidates[candidates.size() - 1]; + + if (c.type() == STUN_PORT_TYPE) { + ASSERT(stun_port_ != NULL); + +#if defined(FEATURE_ENABLE_STUN_CLASSIFICATION) + // We need to wait until we have two addresses. + if (stun_port_->candidates().size() < 2) + return; + + // This is the second address. If these addresses are the same, then we + // are not behind a symmetric NAT. Hence, a stun port should be sufficient. + if (stun_port_->candidates()[0].address() == + stun_port_->candidates()[1].address()) { + SetPort(stun_port_); + return; + } + + // We will need to use relay. + use_relay_ = true; + + // If we already have a relay address, we're good. Otherwise, we will need + // to wait until one arrives. + if (relay_port_->candidates().size() > 0) + SetPort(relay_port_); +#else // defined(FEATURE_ENABLE_STUN_CLASSIFICATION) + // Always use the stun port. We don't classify right now so just assume it + // will work fine. + SetPort(stun_port_); +#endif + } else if (c.type() == RELAY_PORT_TYPE) { + if (use_relay_) + SetPort(relay_port_); + } else { + ASSERT(false); + } +} + +void RawTransportChannel::SetPort(PortInterface* port) { + ASSERT(port_ == NULL); + port_ = port; + + // We don't need any ports other than the one we picked. + allocator_session_->StopGettingPorts(); + worker_thread_->Post( + this, MSG_DESTROY_RTC_UNUSED_PORTS, NULL); + + // Send a message to the other client containing our address. + + ASSERT(port_->Candidates().size() >= 1); + ASSERT(port_->Candidates()[0].protocol() == "udp"); + SignalCandidateReady(this, port_->Candidates()[0]); + + // Read all packets from this port. + port_->EnablePortPackets(); + port_->SignalReadPacket.connect(this, &RawTransportChannel::OnReadPacket); + + // We can write once we have a port and a remote address. + if (!remote_address_.IsAny()) + SetWritable(); +} + +void RawTransportChannel::SetWritable() { + ASSERT(port_ != NULL); + ASSERT(!remote_address_.IsAny()); + + set_writable(true); + + Candidate remote_candidate; + remote_candidate.set_address(remote_address_); + SignalRouteChange(this, remote_candidate); +} + +void RawTransportChannel::OnReadPacket( + PortInterface* port, const char* data, size_t size, + const rtc::SocketAddress& addr) { + ASSERT(port_ == port); + SignalReadPacket(this, data, size, rtc::CreatePacketTime(0), 0); +} + +void RawTransportChannel::OnMessage(rtc::Message* msg) { + ASSERT(msg->message_id == MSG_DESTROY_RTC_UNUSED_PORTS); + ASSERT(port_ != NULL); + if (port_ != stun_port_) { + stun_port_->Destroy(); + stun_port_ = NULL; + } + if (port_ != relay_port_ && relay_port_ != NULL) { + relay_port_->Destroy(); + relay_port_ = NULL; + } +} + +} // namespace cricket +#endif // defined(FEATURE_ENABLE_PSTN) diff --git a/webrtc/p2p/base/rawtransportchannel.h b/webrtc/p2p/base/rawtransportchannel.h new file mode 100644 index 000000000..3041cad2c --- /dev/null +++ b/webrtc/p2p/base/rawtransportchannel.h @@ -0,0 +1,189 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_RAWTRANSPORTCHANNEL_H_ +#define WEBRTC_P2P_BASE_RAWTRANSPORTCHANNEL_H_ + +#include +#include +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/rawtransport.h" +#include "webrtc/p2p/base/transportchannelimpl.h" +#include "webrtc/base/messagequeue.h" + +#if defined(FEATURE_ENABLE_PSTN) + +namespace rtc { +class Thread; +} + +namespace cricket { + +class Connection; +class PortAllocator; +class PortAllocatorSession; +class PortInterface; +class RelayPort; +class StunPort; + +// Implements a channel that just sends bare packets once we have received the +// address of the other side. We pick a single address to send them based on +// a simple investigation of NAT type. +class RawTransportChannel : public TransportChannelImpl, + public rtc::MessageHandler { + public: + RawTransportChannel(const std::string& content_name, + int component, + RawTransport* transport, + rtc::Thread *worker_thread, + PortAllocator *allocator); + virtual ~RawTransportChannel(); + + // Implementation of normal channel packet sending. + virtual int SendPacket(const char *data, size_t len, + const rtc::PacketOptions& options, int flags); + virtual int SetOption(rtc::Socket::Option opt, int value); + virtual int GetError(); + + // Implements TransportChannelImpl. + virtual Transport* GetTransport() { return raw_transport_; } + virtual void SetIceCredentials(const std::string& ice_ufrag, + const std::string& ice_pwd) {} + virtual void SetRemoteIceCredentials(const std::string& ice_ufrag, + const std::string& ice_pwd) {} + + // Creates an allocator session to start figuring out which type of + // port we should send to the other client. This will send + // SignalAvailableCandidate once we have decided. + virtual void Connect(); + + // Resets state back to unconnected. + virtual void Reset(); + + // We don't actually worry about signaling since we can't send new candidates. + virtual void OnSignalingReady() {} + + // Handles a message setting the remote address. We are writable once we + // have this since we now know where to send. + virtual void OnCandidate(const Candidate& candidate); + + void OnRemoteAddress(const rtc::SocketAddress& remote_address); + + // Below ICE specific virtual methods not implemented. + virtual IceRole GetIceRole() const { return ICEROLE_UNKNOWN; } + virtual void SetIceRole(IceRole role) {} + virtual void SetIceTiebreaker(uint64 tiebreaker) {} + + virtual bool GetIceProtocolType(IceProtocolType* type) const { return false; } + virtual void SetIceProtocolType(IceProtocolType type) {} + + virtual void SetIceUfrag(const std::string& ice_ufrag) {} + virtual void SetIcePwd(const std::string& ice_pwd) {} + virtual void SetRemoteIceMode(IceMode mode) {} + virtual size_t GetConnectionCount() const { return 1; } + + virtual bool GetStats(ConnectionInfos* infos) { + return false; + } + + // DTLS methods. + virtual bool IsDtlsActive() const { return false; } + + // Default implementation. + virtual bool GetSslRole(rtc::SSLRole* role) const { + return false; + } + + virtual bool SetSslRole(rtc::SSLRole role) { + return false; + } + + // Set up the ciphers to use for DTLS-SRTP. + virtual bool SetSrtpCiphers(const std::vector& ciphers) { + return false; + } + + // Find out which DTLS-SRTP cipher was negotiated + virtual bool GetSrtpCipher(std::string* cipher) { + return false; + } + + // Returns false because the channel is not DTLS. + virtual bool GetLocalIdentity(rtc::SSLIdentity** identity) const { + return false; + } + + virtual bool GetRemoteCertificate(rtc::SSLCertificate** cert) const { + return false; + } + + // Allows key material to be extracted for external encryption. + virtual bool ExportKeyingMaterial( + const std::string& label, + const uint8* context, + size_t context_len, + bool use_context, + uint8* result, + size_t result_len) { + return false; + } + + virtual bool SetLocalIdentity(rtc::SSLIdentity* identity) { + return false; + } + + // Set DTLS Remote fingerprint. Must be after local identity set. + virtual bool SetRemoteFingerprint( + const std::string& digest_alg, + const uint8* digest, + size_t digest_len) { + return false; + } + + private: + RawTransport* raw_transport_; + rtc::Thread *worker_thread_; + PortAllocator* allocator_; + PortAllocatorSession* allocator_session_; + StunPort* stun_port_; + RelayPort* relay_port_; + PortInterface* port_; + bool use_relay_; + rtc::SocketAddress remote_address_; + + // Called when the allocator creates another port. + void OnPortReady(PortAllocatorSession* session, PortInterface* port); + + // Called when one of the ports we are using has determined its address. + void OnCandidatesReady(PortAllocatorSession *session, + const std::vector& candidates); + + // Called once we have chosen the port to use for communication with the + // other client. This will send its address and prepare the port for use. + void SetPort(PortInterface* port); + + // Called once we have a port and a remote address. This will set mark the + // channel as writable and signal the route to the client. + void SetWritable(); + + // Called when we receive a packet from the other client. + void OnReadPacket(PortInterface* port, const char* data, size_t size, + const rtc::SocketAddress& addr); + + // Handles a message to destroy unused ports. + virtual void OnMessage(rtc::Message *msg); + + DISALLOW_EVIL_CONSTRUCTORS(RawTransportChannel); +}; + +} // namespace cricket + +#endif // defined(FEATURE_ENABLE_PSTN) +#endif // WEBRTC_P2P_BASE_RAWTRANSPORTCHANNEL_H_ diff --git a/webrtc/p2p/base/relayport.cc b/webrtc/p2p/base/relayport.cc new file mode 100644 index 000000000..4c40b3da8 --- /dev/null +++ b/webrtc/p2p/base/relayport.cc @@ -0,0 +1,818 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/relayport.h" +#include "webrtc/base/asyncpacketsocket.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" + +namespace cricket { + +static const uint32 kMessageConnectTimeout = 1; +static const int kKeepAliveDelay = 10 * 60 * 1000; +static const int kRetryTimeout = 50 * 1000; // ICE says 50 secs +// How long to wait for a socket to connect to remote host in milliseconds +// before trying another connection. +static const int kSoftConnectTimeoutMs = 3 * 1000; + +// Handles a connection to one address/port/protocol combination for a +// particular RelayEntry. +class RelayConnection : public sigslot::has_slots<> { + public: + RelayConnection(const ProtocolAddress* protocol_address, + rtc::AsyncPacketSocket* socket, + rtc::Thread* thread); + ~RelayConnection(); + rtc::AsyncPacketSocket* socket() const { return socket_; } + + const ProtocolAddress* protocol_address() { + return protocol_address_; + } + + rtc::SocketAddress GetAddress() const { + return protocol_address_->address; + } + + ProtocolType GetProtocol() const { + return protocol_address_->proto; + } + + int SetSocketOption(rtc::Socket::Option opt, int value); + + // Validates a response to a STUN allocate request. + bool CheckResponse(StunMessage* msg); + + // Sends data to the relay server. + int Send(const void* pv, size_t cb, const rtc::PacketOptions& options); + + // Sends a STUN allocate request message to the relay server. + void SendAllocateRequest(RelayEntry* entry, int delay); + + // Return the latest error generated by the socket. + int GetError() { return socket_->GetError(); } + + // 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, StunRequest* req); + + private: + rtc::AsyncPacketSocket* socket_; + const ProtocolAddress* protocol_address_; + StunRequestManager *request_manager_; +}; + +// Manages a number of connections to the relayserver, one for each +// available protocol. 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 rtc::MessageHandler, + public sigslot::has_slots<> { + public: + RelayEntry(RelayPort* port, const rtc::SocketAddress& ext_addr); + ~RelayEntry(); + + RelayPort* port() { return port_; } + + const rtc::SocketAddress& address() const { return ext_addr_; } + void set_address(const rtc::SocketAddress& addr) { ext_addr_ = addr; } + + bool connected() const { return connected_; } + bool locked() const { return locked_; } + + // Returns the last error on the socket of this entry. + int GetError(); + + // Returns the most preferred connection of the given + // ones. Connections are rated based on protocol in the order of: + // UDP, TCP and SSLTCP, where UDP is the most preferred protocol + static RelayConnection* GetBestConnection(RelayConnection* conn1, + RelayConnection* conn2); + + // 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 rtc::SocketAddress& mapped_addr, + RelayConnection* socket); + + // 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 rtc::SocketAddress& addr, + const rtc::PacketOptions& options); + + // Schedules a keep-alive allocate request. + void ScheduleKeepAlive(); + + void SetServerIndex(size_t sindex) { server_index_ = sindex; } + + // Sets this option on the socket of each connection. + int SetSocketOption(rtc::Socket::Option opt, int value); + + size_t ServerIndex() const { return server_index_; } + + // Try a different server address + void HandleConnectFailure(rtc::AsyncPacketSocket* socket); + + // Implementation of the MessageHandler Interface. + virtual void OnMessage(rtc::Message *pmsg); + + private: + RelayPort* port_; + rtc::SocketAddress ext_addr_; + size_t server_index_; + bool connected_; + bool locked_; + RelayConnection* current_connection_; + + // Called when a TCP connection is established or fails + void OnSocketConnect(rtc::AsyncPacketSocket* socket); + void OnSocketClose(rtc::AsyncPacketSocket* socket, int error); + + // Called when a packet is received on this socket. + void OnReadPacket( + rtc::AsyncPacketSocket* socket, + const char* data, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time); + // Called when the socket is currently able to send. + void OnReadyToSend(rtc::AsyncPacketSocket* socket); + + // 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, + const rtc::PacketOptions& options); +}; + +// Handles an allocate request for a particular RelayEntry. +class AllocateRequest : public StunRequest { + public: + AllocateRequest(RelayEntry* entry, RelayConnection* connection); + 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_; + RelayConnection* connection_; + uint32 start_time_; +}; + +RelayPort::RelayPort( + rtc::Thread* thread, rtc::PacketSocketFactory* factory, + rtc::Network* network, const rtc::IPAddress& ip, + int min_port, int max_port, const std::string& username, + const std::string& password) + : Port(thread, RELAY_PORT_TYPE, factory, network, ip, min_port, max_port, + username, password), + ready_(false), + error_(0) { + entries_.push_back( + new RelayEntry(this, rtc::SocketAddress())); + // TODO: set local preference value for TCP based candidates. +} + +RelayPort::~RelayPort() { + for (size_t 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 == rtc::PROXY_HTTPS || + proxy().type == rtc::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::iterator it = external_addr_.begin(); + it != external_addr_.end(); ++it) { + if ((it->address == addr.address) && (it->proto == addr.proto)) { + LOG(INFO) << "Redundant relay address: " << proto_name + << " @ " << addr.address.ToSensitiveString(); + return; + } + } + external_addr_.push_back(addr); +} + +void RelayPort::SetReady() { + if (!ready_) { + std::vector::iterator iter; + for (iter = external_addr_.begin(); + iter != external_addr_.end(); ++iter) { + std::string proto_name = ProtoToString(iter->proto); + // In case of Gturn, related address is set to null socket address. + // This is due to as mapped address stun attribute is used for allocated + // address. + AddAddress(iter->address, iter->address, rtc::SocketAddress(), + proto_name, "", RELAY_PORT_TYPE, + ICE_TYPE_PREFERENCE_RELAY, 0, false); + } + ready_ = true; + SignalPortComplete(this); + } +} + +const ProtocolAddress * RelayPort::ServerAddress(size_t index) const { + if (index < server_addr_.size()) + return &server_addr_[index]; + return NULL; +} + +bool RelayPort::HasMagicCookie(const char* data, size_t size) { + if (size < 24 + sizeof(TURN_MAGIC_COOKIE_VALUE)) { + return false; + } else { + return memcmp(data + 24, + TURN_MAGIC_COOKIE_VALUE, + sizeof(TURN_MAGIC_COOKIE_VALUE)) == 0; + } +} + +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 conns to non-udp sockets if they are incoming on this port + if ((address.protocol() != UDP_PROTOCOL_NAME) && + (origin != ORIGIN_THIS_PORT)) { + return 0; + } + + // We don't support loopback on relays + if (address.type() == Type()) { + return 0; + } + + if (!IsCompatibleAddress(address.address())) { + 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 rtc::SocketAddress& addr, + const rtc::PacketOptions& options, + 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 (size_t i = 0; i < entries_.size(); ++i) { + if (entries_[i]->address().IsNil() && 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); + if (!entries_.empty()) { + 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, options); + 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 static_cast(size); +} + +int RelayPort::SetOption(rtc::Socket::Option opt, int value) { + int result = 0; + for (size_t i = 0; i < entries_.size(); ++i) { + if (entries_[i]->SetSocketOption(opt, value) < 0) { + result = -1; + error_ = entries_[i]->GetError(); + } + } + options_.push_back(OptionValue(opt, value)); + return result; +} + +int RelayPort::GetOption(rtc::Socket::Option opt, int* value) { + std::vector::iterator it; + for (it = options_.begin(); it < options_.end(); ++it) { + if (it->first == opt) { + *value = it->second; + return 0; + } + } + return SOCKET_ERROR; +} + +int RelayPort::GetError() { + return error_; +} + +void RelayPort::OnReadPacket( + const char* data, size_t size, + const rtc::SocketAddress& remote_addr, + ProtocolType proto, + const rtc::PacketTime& packet_time) { + if (Connection* conn = GetConnection(remote_addr)) { + conn->OnReadPacket(data, size, packet_time); + } else { + Port::OnReadPacket(data, size, remote_addr, proto); + } +} + +RelayConnection::RelayConnection(const ProtocolAddress* protocol_address, + rtc::AsyncPacketSocket* socket, + rtc::Thread* thread) + : socket_(socket), + protocol_address_(protocol_address) { + request_manager_ = new StunRequestManager(thread); + request_manager_->SignalSendPacket.connect(this, + &RelayConnection::OnSendPacket); +} + +RelayConnection::~RelayConnection() { + delete request_manager_; + delete socket_; +} + +int RelayConnection::SetSocketOption(rtc::Socket::Option opt, + int value) { + if (socket_) { + return socket_->SetOption(opt, value); + } + return 0; +} + +bool RelayConnection::CheckResponse(StunMessage* msg) { + return request_manager_->CheckResponse(msg); +} + +void RelayConnection::OnSendPacket(const void* data, size_t size, + StunRequest* req) { + // TODO(mallinath) Find a way to get DSCP value from Port. + rtc::PacketOptions options; // Default dscp set to NO_CHANGE. + int sent = socket_->SendTo(data, size, GetAddress(), options); + if (sent <= 0) { + LOG(LS_VERBOSE) << "OnSendPacket: failed sending to " << GetAddress() << + strerror(socket_->GetError()); + ASSERT(sent < 0); + } +} + +int RelayConnection::Send(const void* pv, size_t cb, + const rtc::PacketOptions& options) { + return socket_->SendTo(pv, cb, GetAddress(), options); +} + +void RelayConnection::SendAllocateRequest(RelayEntry* entry, int delay) { + request_manager_->SendDelayed(new AllocateRequest(entry, this), delay); +} + +RelayEntry::RelayEntry(RelayPort* port, + const rtc::SocketAddress& ext_addr) + : port_(port), ext_addr_(ext_addr), + server_index_(0), connected_(false), locked_(false), + current_connection_(NULL) { +} + +RelayEntry::~RelayEntry() { + // Remove all RelayConnections and dispose sockets. + delete current_connection_; + current_connection_ = NULL; +} + +void RelayEntry::Connect() { + // If we're already connected, return. + if (connected_) + return; + + // If we've exhausted all options, bail out. + const ProtocolAddress* ra = port()->ServerAddress(server_index_); + if (!ra) { + LOG(LS_WARNING) << "No more relay addresses left to try"; + return; + } + + // Remove any previous connection. + if (current_connection_) { + port()->thread()->Dispose(current_connection_); + current_connection_ = NULL; + } + + // Try to set up our new socket. + LOG(LS_INFO) << "Connecting to relay via " << ProtoToString(ra->proto) << + " @ " << ra->address.ToSensitiveString(); + + rtc::AsyncPacketSocket* socket = NULL; + + if (ra->proto == PROTO_UDP) { + // UDP sockets are simple. + socket = port_->socket_factory()->CreateUdpSocket( + rtc::SocketAddress(port_->ip(), 0), + port_->min_port(), port_->max_port()); + } else if (ra->proto == PROTO_TCP || ra->proto == PROTO_SSLTCP) { + int opts = (ra->proto == PROTO_SSLTCP) ? + rtc::PacketSocketFactory::OPT_SSLTCP : 0; + socket = port_->socket_factory()->CreateClientTcpSocket( + rtc::SocketAddress(port_->ip(), 0), ra->address, + port_->proxy(), port_->user_agent(), opts); + } else { + LOG(LS_WARNING) << "Unknown protocol (" << ra->proto << ")"; + } + + if (!socket) { + LOG(LS_WARNING) << "Socket creation failed"; + } + + // If we failed to get a socket, move on to the next protocol. + if (!socket) { + port()->thread()->Post(this, kMessageConnectTimeout); + return; + } + + // Otherwise, create the new connection and configure any socket options. + socket->SignalReadPacket.connect(this, &RelayEntry::OnReadPacket); + socket->SignalReadyToSend.connect(this, &RelayEntry::OnReadyToSend); + current_connection_ = new RelayConnection(ra, socket, port()->thread()); + for (size_t i = 0; i < port_->options().size(); ++i) { + current_connection_->SetSocketOption(port_->options()[i].first, + port_->options()[i].second); + } + + // If we're trying UDP, start binding requests. + // If we're trying TCP, wait for connection with a fixed timeout. + if ((ra->proto == PROTO_TCP) || (ra->proto == PROTO_SSLTCP)) { + socket->SignalClose.connect(this, &RelayEntry::OnSocketClose); + socket->SignalConnect.connect(this, &RelayEntry::OnSocketConnect); + port()->thread()->PostDelayed(kSoftConnectTimeoutMs, this, + kMessageConnectTimeout); + } else { + current_connection_->SendAllocateRequest(this, 0); + } +} + +int RelayEntry::GetError() { + if (current_connection_ != NULL) { + return current_connection_->GetError(); + } + return 0; +} + +RelayConnection* RelayEntry::GetBestConnection(RelayConnection* conn1, + RelayConnection* conn2) { + return conn1->GetProtocol() <= conn2->GetProtocol() ? conn1 : conn2; +} + +void RelayEntry::OnConnect(const rtc::SocketAddress& mapped_addr, + RelayConnection* connection) { + // We are connected, notify our parent. + ProtocolType proto = PROTO_UDP; + LOG(INFO) << "Relay allocate succeeded: " << ProtoToString(proto) + << " @ " << mapped_addr.ToSensitiveString(); + connected_ = true; + + port_->AddExternalAddress(ProtocolAddress(mapped_addr, proto)); + port_->SetReady(); +} + +int RelayEntry::SendTo(const void* data, size_t size, + const rtc::SocketAddress& addr, + const rtc::PacketOptions& options) { + // 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, options); + + // 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. + + RelayMessage request; + request.SetType(STUN_SEND_REQUEST); + + StunByteStringAttribute* magic_cookie_attr = + StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE); + magic_cookie_attr->CopyBytes(TURN_MAGIC_COOKIE_VALUE, + sizeof(TURN_MAGIC_COOKIE_VALUE)); + VERIFY(request.AddAttribute(magic_cookie_attr)); + + StunByteStringAttribute* username_attr = + StunAttribute::CreateByteString(STUN_ATTR_USERNAME); + username_attr->CopyBytes(port_->username_fragment().c_str(), + port_->username_fragment().size()); + VERIFY(request.AddAttribute(username_attr)); + + StunAddressAttribute* addr_attr = + StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS); + addr_attr->SetIP(addr.ipaddr()); + addr_attr->SetPort(addr.port()); + VERIFY(request.AddAttribute(addr_attr)); + + // Attempt to lock + if (ext_addr_ == addr) { + StunUInt32Attribute* options_attr = + StunAttribute::CreateUInt32(STUN_ATTR_OPTIONS); + options_attr->SetValue(0x1); + VERIFY(request.AddAttribute(options_attr)); + } + + StunByteStringAttribute* data_attr = + StunAttribute::CreateByteString(STUN_ATTR_DATA); + data_attr->CopyBytes(data, size); + VERIFY(request.AddAttribute(data_attr)); + + // TODO: compute the HMAC. + + rtc::ByteBuffer buf; + request.Write(&buf); + + return SendPacket(buf.Data(), buf.Length(), options); +} + +void RelayEntry::ScheduleKeepAlive() { + if (current_connection_) { + current_connection_->SendAllocateRequest(this, kKeepAliveDelay); + } +} + +int RelayEntry::SetSocketOption(rtc::Socket::Option opt, int value) { + // Set the option on all available sockets. + int socket_error = 0; + if (current_connection_) { + socket_error = current_connection_->SetSocketOption(opt, value); + } + return socket_error; +} + +void RelayEntry::HandleConnectFailure( + rtc::AsyncPacketSocket* socket) { + // Make sure it's the current connection that has failed, it might + // be an old socked that has not yet been disposed. + if (!socket || + (current_connection_ && socket == current_connection_->socket())) { + if (current_connection_) + port()->SignalConnectFailure(current_connection_->protocol_address()); + + // Try to connect to the next server address. + server_index_ += 1; + Connect(); + } +} + +void RelayEntry::OnMessage(rtc::Message *pmsg) { + ASSERT(pmsg->message_id == kMessageConnectTimeout); + if (current_connection_) { + const ProtocolAddress* ra = current_connection_->protocol_address(); + LOG(LS_WARNING) << "Relay " << ra->proto << " connection to " << + ra->address << " timed out"; + + // Currently we connect to each server address in sequence. If we + // have more addresses to try, treat this is an error and move on to + // the next address, otherwise give this connection more time and + // await the real timeout. + // + // TODO: Connect to servers in parallel to speed up connect time + // and to avoid giving up too early. + port_->SignalSoftTimeout(ra); + HandleConnectFailure(current_connection_->socket()); + } else { + HandleConnectFailure(NULL); + } +} + +void RelayEntry::OnSocketConnect(rtc::AsyncPacketSocket* socket) { + LOG(INFO) << "relay tcp connected to " << + socket->GetRemoteAddress().ToSensitiveString(); + if (current_connection_ != NULL) { + current_connection_->SendAllocateRequest(this, 0); + } +} + +void RelayEntry::OnSocketClose(rtc::AsyncPacketSocket* socket, + int error) { + PLOG(LERROR, error) << "Relay connection failed: socket closed"; + HandleConnectFailure(socket); +} + +void RelayEntry::OnReadPacket( + rtc::AsyncPacketSocket* socket, + const char* data, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time) { + // ASSERT(remote_addr == port_->server_addr()); + // TODO: are we worried about this? + + if (current_connection_ == NULL || socket != current_connection_->socket()) { + // This packet comes from an unknown address. + LOG(WARNING) << "Dropping packet: unknown address"; + return; + } + + // 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_, PROTO_UDP, packet_time); + } else { + LOG(WARNING) << "Dropping packet: entry not locked"; + } + return; + } + + rtc::ByteBuffer buf(data, size); + RelayMessage 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 (current_connection_->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; + } + + rtc::SocketAddress remote_addr2(addr_attr->ipaddr(), 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, + PROTO_UDP, packet_time); +} + +void RelayEntry::OnReadyToSend(rtc::AsyncPacketSocket* socket) { + if (connected()) { + port_->OnReadyToSend(); + } +} + +int RelayEntry::SendPacket(const void* data, size_t size, + const rtc::PacketOptions& options) { + int sent = 0; + if (current_connection_) { + // We are connected, no need to send packets anywere else than to + // the current connection. + sent = current_connection_->Send(data, size, options); + } + return sent; +} + +AllocateRequest::AllocateRequest(RelayEntry* entry, + RelayConnection* connection) + : StunRequest(new RelayMessage()), + entry_(entry), + connection_(connection) { + start_time_ = rtc::Time(); +} + +void AllocateRequest::Prepare(StunMessage* request) { + request->SetType(STUN_ALLOCATE_REQUEST); + + StunByteStringAttribute* username_attr = + StunAttribute::CreateByteString(STUN_ATTR_USERNAME); + username_attr->CopyBytes( + entry_->port()->username_fragment().c_str(), + entry_->port()->username_fragment().size()); + VERIFY(request->AddAttribute(username_attr)); +} + +int AllocateRequest::GetNextDelay() { + int delay = 100 * rtc::_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 { + rtc::SocketAddress addr(addr_attr->ipaddr(), addr_attr->port()); + entry_->OnConnect(addr, connection_); + } + + // 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=" << attr->code() + << " reason='" << attr->reason() << "'"; + } + + if (rtc::TimeSince(start_time_) <= kRetryTimeout) + entry_->ScheduleKeepAlive(); +} + +void AllocateRequest::OnTimeout() { + LOG(INFO) << "Allocate request timed out"; + entry_->HandleConnectFailure(connection_->socket()); +} + +} // namespace cricket diff --git a/webrtc/p2p/base/relayport.h b/webrtc/p2p/base/relayport.h new file mode 100644 index 000000000..3d9538daa --- /dev/null +++ b/webrtc/p2p/base/relayport.h @@ -0,0 +1,101 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_RELAYPORT_H_ +#define WEBRTC_P2P_BASE_RELAYPORT_H_ + +#include +#include +#include +#include + +#include "webrtc/p2p/base/port.h" +#include "webrtc/p2p/base/stunrequest.h" + +namespace cricket { + +class RelayEntry; +class RelayConnection; + +// Communicates using an allocated port on the relay server. For each +// remote candidate that we try to send data to a RelayEntry instance +// is created. The RelayEntry will try to reach the remote destination +// by connecting to all available server addresses in a pre defined +// order with a small delay in between. When a connection is +// successful all other connection attemts are aborted. +class RelayPort : public Port { + public: + typedef std::pair OptionValue; + + // RelayPort doesn't yet do anything fancy in the ctor. + static RelayPort* Create( + rtc::Thread* thread, rtc::PacketSocketFactory* factory, + rtc::Network* network, const rtc::IPAddress& ip, + int min_port, int max_port, const std::string& username, + const std::string& password) { + return new RelayPort(thread, factory, network, ip, min_port, max_port, + username, password); + } + virtual ~RelayPort(); + + void AddServerAddress(const ProtocolAddress& addr); + void AddExternalAddress(const ProtocolAddress& addr); + + const std::vector& options() const { return options_; } + bool HasMagicCookie(const char* data, size_t size); + + virtual void PrepareAddress(); + virtual Connection* CreateConnection(const Candidate& address, + CandidateOrigin origin); + virtual int SetOption(rtc::Socket::Option opt, int value); + virtual int GetOption(rtc::Socket::Option opt, int* value); + virtual int GetError(); + + const ProtocolAddress * ServerAddress(size_t index) const; + bool IsReady() { return ready_; } + + // Used for testing. + sigslot::signal1 SignalConnectFailure; + sigslot::signal1 SignalSoftTimeout; + + protected: + RelayPort(rtc::Thread* thread, rtc::PacketSocketFactory* factory, + rtc::Network*, const rtc::IPAddress& ip, + int min_port, int max_port, const std::string& username, + const std::string& password); + bool Init(); + + void SetReady(); + + virtual int SendTo(const void* data, size_t size, + const rtc::SocketAddress& addr, + const rtc::PacketOptions& options, + bool payload); + + // Dispatches the given packet to the port or connection as appropriate. + void OnReadPacket(const char* data, size_t size, + const rtc::SocketAddress& remote_addr, + ProtocolType proto, + const rtc::PacketTime& packet_time); + + private: + friend class RelayEntry; + + std::deque server_addr_; + std::vector external_addr_; + bool ready_; + std::vector entries_; + std::vector options_; + int error_; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_RELAYPORT_H_ diff --git a/webrtc/p2p/base/relayport_unittest.cc b/webrtc/p2p/base/relayport_unittest.cc new file mode 100644 index 000000000..d644d67c4 --- /dev/null +++ b/webrtc/p2p/base/relayport_unittest.cc @@ -0,0 +1,272 @@ +/* + * Copyright 2009 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/relayport.h" +#include "webrtc/p2p/base/relayserver.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/socketadapters.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/ssladapter.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/virtualsocketserver.h" + +using rtc::SocketAddress; + +static const SocketAddress kLocalAddress = SocketAddress("192.168.1.2", 0); +static const SocketAddress kRelayUdpAddr = SocketAddress("99.99.99.1", 5000); +static const SocketAddress kRelayTcpAddr = SocketAddress("99.99.99.2", 5001); +static const SocketAddress kRelaySslAddr = SocketAddress("99.99.99.3", 443); +static const SocketAddress kRelayExtAddr = SocketAddress("99.99.99.3", 5002); + +static const int kTimeoutMs = 1000; +static const int kMaxTimeoutMs = 5000; + +// Tests connecting a RelayPort to a fake relay server +// (cricket::RelayServer) using all currently available protocols. The +// network layer is faked out by using a VirtualSocketServer for +// creating sockets. The test will monitor the current state of the +// RelayPort and created sockets by listening for signals such as, +// SignalConnectFailure, SignalConnectTimeout, SignalSocketClosed and +// SignalReadPacket. +class RelayPortTest : public testing::Test, + public sigslot::has_slots<> { + public: + RelayPortTest() + : main_(rtc::Thread::Current()), + physical_socket_server_(new rtc::PhysicalSocketServer), + virtual_socket_server_(new rtc::VirtualSocketServer( + physical_socket_server_.get())), + ss_scope_(virtual_socket_server_.get()), + network_("unittest", "unittest", rtc::IPAddress(INADDR_ANY), 32), + socket_factory_(rtc::Thread::Current()), + username_(rtc::CreateRandomString(16)), + password_(rtc::CreateRandomString(16)), + relay_port_(cricket::RelayPort::Create(main_, &socket_factory_, + &network_, + kLocalAddress.ipaddr(), + 0, 0, username_, password_)), + relay_server_(new cricket::RelayServer(main_)) { + } + + void OnReadPacket(rtc::AsyncPacketSocket* socket, + const char* data, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time) { + received_packet_count_[socket]++; + } + + void OnConnectFailure(const cricket::ProtocolAddress* addr) { + failed_connections_.push_back(*addr); + } + + void OnSoftTimeout(const cricket::ProtocolAddress* addr) { + soft_timedout_connections_.push_back(*addr); + } + + protected: + virtual void SetUp() { + // The relay server needs an external socket to work properly. + rtc::AsyncUDPSocket* ext_socket = + CreateAsyncUdpSocket(kRelayExtAddr); + relay_server_->AddExternalSocket(ext_socket); + + // Listen for failures. + relay_port_->SignalConnectFailure. + connect(this, &RelayPortTest::OnConnectFailure); + + // Listen for soft timeouts. + relay_port_->SignalSoftTimeout. + connect(this, &RelayPortTest::OnSoftTimeout); + } + + // Udp has the highest 'goodness' value of the three different + // protocols used for connecting to the relay server. As soon as + // PrepareAddress is called, the RelayPort will start trying to + // connect to the given UDP address. As soon as a response to the + // sent STUN allocate request message has been received, the + // RelayPort will consider the connection to be complete and will + // abort any other connection attempts. + void TestConnectUdp() { + // Add a UDP socket to the relay server. + rtc::AsyncUDPSocket* internal_udp_socket = + CreateAsyncUdpSocket(kRelayUdpAddr); + rtc::AsyncSocket* server_socket = CreateServerSocket(kRelayTcpAddr); + + relay_server_->AddInternalSocket(internal_udp_socket); + relay_server_->AddInternalServerSocket(server_socket, cricket::PROTO_TCP); + + // Now add our relay addresses to the relay port and let it start. + relay_port_->AddServerAddress( + cricket::ProtocolAddress(kRelayUdpAddr, cricket::PROTO_UDP)); + relay_port_->AddServerAddress( + cricket::ProtocolAddress(kRelayTcpAddr, cricket::PROTO_TCP)); + relay_port_->PrepareAddress(); + + // Should be connected. + EXPECT_TRUE_WAIT(relay_port_->IsReady(), kTimeoutMs); + + // Make sure that we are happy with UDP, ie. not continuing with + // TCP, SSLTCP, etc. + WAIT(relay_server_->HasConnection(kRelayTcpAddr), kTimeoutMs); + + // Should have only one connection. + EXPECT_EQ(1, relay_server_->GetConnectionCount()); + + // Should be the UDP address. + EXPECT_TRUE(relay_server_->HasConnection(kRelayUdpAddr)); + } + + // TCP has the second best 'goodness' value, and as soon as UDP + // connection has failed, the RelayPort will attempt to connect via + // TCP. Here we add a fake UDP address together with a real TCP + // address to simulate an UDP failure. As soon as UDP has failed the + // RelayPort will try the TCP adress and succed. + void TestConnectTcp() { + // Create a fake UDP address for relay port to simulate a failure. + cricket::ProtocolAddress fake_protocol_address = + cricket::ProtocolAddress(kRelayUdpAddr, cricket::PROTO_UDP); + + // Create a server socket for the RelayServer. + rtc::AsyncSocket* server_socket = CreateServerSocket(kRelayTcpAddr); + relay_server_->AddInternalServerSocket(server_socket, cricket::PROTO_TCP); + + // Add server addresses to the relay port and let it start. + relay_port_->AddServerAddress( + cricket::ProtocolAddress(fake_protocol_address)); + relay_port_->AddServerAddress( + cricket::ProtocolAddress(kRelayTcpAddr, cricket::PROTO_TCP)); + relay_port_->PrepareAddress(); + + EXPECT_FALSE(relay_port_->IsReady()); + + // Should have timed out in 200 + 200 + 400 + 800 + 1600 ms. + EXPECT_TRUE_WAIT(HasFailed(&fake_protocol_address), 3600); + + // Wait until relayport is ready. + EXPECT_TRUE_WAIT(relay_port_->IsReady(), kMaxTimeoutMs); + + // Should have only one connection. + EXPECT_EQ(1, relay_server_->GetConnectionCount()); + + // Should be the TCP address. + EXPECT_TRUE(relay_server_->HasConnection(kRelayTcpAddr)); + } + + void TestConnectSslTcp() { + // Create a fake TCP address for relay port to simulate a failure. + // We skip UDP here since transition from UDP to TCP has been + // tested above. + cricket::ProtocolAddress fake_protocol_address = + cricket::ProtocolAddress(kRelayTcpAddr, cricket::PROTO_TCP); + + // Create a ssl server socket for the RelayServer. + rtc::AsyncSocket* ssl_server_socket = + CreateServerSocket(kRelaySslAddr); + relay_server_->AddInternalServerSocket(ssl_server_socket, + cricket::PROTO_SSLTCP); + + // Create a tcp server socket that listens on the fake address so + // the relay port can attempt to connect to it. + rtc::scoped_ptr tcp_server_socket( + CreateServerSocket(kRelayTcpAddr)); + + // Add server addresses to the relay port and let it start. + relay_port_->AddServerAddress(fake_protocol_address); + relay_port_->AddServerAddress( + cricket::ProtocolAddress(kRelaySslAddr, cricket::PROTO_SSLTCP)); + relay_port_->PrepareAddress(); + EXPECT_FALSE(relay_port_->IsReady()); + + // Should have timed out in 3000 ms(relayport.cc, kSoftConnectTimeoutMs). + EXPECT_TRUE_WAIT_MARGIN(HasTimedOut(&fake_protocol_address), 3000, 100); + + // Wait until relayport is ready. + EXPECT_TRUE_WAIT(relay_port_->IsReady(), kMaxTimeoutMs); + + // Should have only one connection. + EXPECT_EQ(1, relay_server_->GetConnectionCount()); + + // Should be the SSLTCP address. + EXPECT_TRUE(relay_server_->HasConnection(kRelaySslAddr)); + } + + private: + rtc::AsyncUDPSocket* CreateAsyncUdpSocket(const SocketAddress addr) { + rtc::AsyncSocket* socket = + virtual_socket_server_->CreateAsyncSocket(SOCK_DGRAM); + rtc::AsyncUDPSocket* packet_socket = + rtc::AsyncUDPSocket::Create(socket, addr); + EXPECT_TRUE(packet_socket != NULL); + packet_socket->SignalReadPacket.connect(this, &RelayPortTest::OnReadPacket); + return packet_socket; + } + + rtc::AsyncSocket* CreateServerSocket(const SocketAddress addr) { + rtc::AsyncSocket* socket = + virtual_socket_server_->CreateAsyncSocket(SOCK_STREAM); + EXPECT_GE(socket->Bind(addr), 0); + EXPECT_GE(socket->Listen(5), 0); + return socket; + } + + bool HasFailed(cricket::ProtocolAddress* addr) { + for (size_t i = 0; i < failed_connections_.size(); i++) { + if (failed_connections_[i].address == addr->address && + failed_connections_[i].proto == addr->proto) { + return true; + } + } + return false; + } + + bool HasTimedOut(cricket::ProtocolAddress* addr) { + for (size_t i = 0; i < soft_timedout_connections_.size(); i++) { + if (soft_timedout_connections_[i].address == addr->address && + soft_timedout_connections_[i].proto == addr->proto) { + return true; + } + } + return false; + } + + typedef std::map PacketMap; + + rtc::Thread* main_; + rtc::scoped_ptr + physical_socket_server_; + rtc::scoped_ptr virtual_socket_server_; + rtc::SocketServerScope ss_scope_; + rtc::Network network_; + rtc::BasicPacketSocketFactory socket_factory_; + std::string username_; + std::string password_; + rtc::scoped_ptr relay_port_; + rtc::scoped_ptr relay_server_; + std::vector failed_connections_; + std::vector soft_timedout_connections_; + PacketMap received_packet_count_; +}; + +TEST_F(RelayPortTest, ConnectUdp) { + TestConnectUdp(); +} + +TEST_F(RelayPortTest, ConnectTcp) { + TestConnectTcp(); +} + +TEST_F(RelayPortTest, ConnectSslTcp) { + TestConnectSslTcp(); +} diff --git a/webrtc/p2p/base/relayserver.cc b/webrtc/p2p/base/relayserver.cc new file mode 100644 index 000000000..e37a1680f --- /dev/null +++ b/webrtc/p2p/base/relayserver.cc @@ -0,0 +1,746 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/relayserver.h" + +#ifdef WEBRTC_POSIX +#include +#endif // WEBRTC_POSIX + +#include + +#include "webrtc/base/asynctcpsocket.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/socketadapters.h" + +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(rtc::AsyncPacketSocket* socket, const char* bytes, size_t size, + const rtc::SocketAddress& addr) { + rtc::PacketOptions options; + int result = socket->SendTo(bytes, size, addr, options); + if (result < static_cast(size)) { + LOG(LS_ERROR) << "SendTo wrote only " << result << " of " << size + << " bytes"; + } else if (result < 0) { + LOG_ERR(LS_ERROR) << "SendTo"; + } +} + +// Sends the given STUN message on the given socket. +void SendStun(const StunMessage& msg, + rtc::AsyncPacketSocket* socket, + const rtc::SocketAddress& addr) { + rtc::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, rtc::AsyncPacketSocket* socket, + const rtc::SocketAddress& remote_addr, int error_code, + const char* error_desc, const std::string& magic_cookie) { + RelayMessage 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::TURN_MAGIC_COOKIE_VALUE, + sizeof(cricket::TURN_MAGIC_COOKIE_VALUE)); + } 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->SetClass(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(rtc::Thread* thread) + : thread_(thread), log_bindings_(true) { +} + +RelayServer::~RelayServer() { + // Deleting the binding will cause it to be removed from the map. + while (!bindings_.empty()) + delete bindings_.begin()->second; + for (size_t i = 0; i < internal_sockets_.size(); ++i) + delete internal_sockets_[i]; + for (size_t i = 0; i < external_sockets_.size(); ++i) + delete external_sockets_[i]; + for (size_t i = 0; i < removed_sockets_.size(); ++i) + delete removed_sockets_[i]; + while (!server_sockets_.empty()) { + rtc::AsyncSocket* socket = server_sockets_.begin()->first; + server_sockets_.erase(server_sockets_.begin()->first); + delete socket; + } +} + +void RelayServer::AddInternalSocket(rtc::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(rtc::AsyncPacketSocket* socket) { + SocketList::iterator iter = + std::find(internal_sockets_.begin(), internal_sockets_.end(), socket); + ASSERT(iter != internal_sockets_.end()); + internal_sockets_.erase(iter); + removed_sockets_.push_back(socket); + socket->SignalReadPacket.disconnect(this); +} + +void RelayServer::AddExternalSocket(rtc::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(rtc::AsyncPacketSocket* socket) { + SocketList::iterator iter = + std::find(external_sockets_.begin(), external_sockets_.end(), socket); + ASSERT(iter != external_sockets_.end()); + external_sockets_.erase(iter); + removed_sockets_.push_back(socket); + socket->SignalReadPacket.disconnect(this); +} + +void RelayServer::AddInternalServerSocket(rtc::AsyncSocket* socket, + cricket::ProtocolType proto) { + ASSERT(server_sockets_.end() == + server_sockets_.find(socket)); + server_sockets_[socket] = proto; + socket->SignalReadEvent.connect(this, &RelayServer::OnReadEvent); +} + +void RelayServer::RemoveInternalServerSocket( + rtc::AsyncSocket* socket) { + ServerSocketMap::iterator iter = server_sockets_.find(socket); + ASSERT(iter != server_sockets_.end()); + server_sockets_.erase(iter); + socket->SignalReadEvent.disconnect(this); +} + +int RelayServer::GetConnectionCount() const { + return static_cast(connections_.size()); +} + +rtc::SocketAddressPair RelayServer::GetConnection(int connection) const { + int i = 0; + for (ConnectionMap::const_iterator it = connections_.begin(); + it != connections_.end(); ++it) { + if (i == connection) { + return it->second->addr_pair(); + } + ++i; + } + return rtc::SocketAddressPair(); +} + +bool RelayServer::HasConnection(const rtc::SocketAddress& address) const { + for (ConnectionMap::const_iterator it = connections_.begin(); + it != connections_.end(); ++it) { + if (it->second->addr_pair().destination() == address) { + return true; + } + } + return false; +} + +void RelayServer::OnReadEvent(rtc::AsyncSocket* socket) { + ASSERT(server_sockets_.find(socket) != server_sockets_.end()); + AcceptConnection(socket); +} + +void RelayServer::OnInternalPacket( + rtc::AsyncPacketSocket* socket, const char* bytes, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time) { + + // Get the address of the connection we just received on. + rtc::SocketAddressPair ap(remote_addr, socket->GetLocalAddress()); + ASSERT(!ap.destination().IsNil()); + + // 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()) { + LOG(LS_WARNING) << "Dropping packet: connection not locked"; + return; + } + + // Forward this to the destination address into the connection. + RelayServerConnection* ext_conn = int_conn->binding()->GetExternalConnection( + int_conn->default_destination()); + if (ext_conn && ext_conn->locked()) { + // TODO: Check the HMAC. + ext_conn->Send(bytes, size); + } else { + // This happens very often and is not an error. + LOG(LS_INFO) << "Dropping packet: no external connection"; + } +} + +void RelayServer::OnExternalPacket( + rtc::AsyncPacketSocket* socket, const char* bytes, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time) { + + // Get the address of the connection we just received on. + rtc::SocketAddressPair ap(remote_addr, socket->GetLocalAddress()); + ASSERT(!ap.destination().IsNil()); + + // 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 != NULL); + int_conn->Send(bytes, size, ext_conn->addr_pair().source()); + ext_conn->Lock(); // allow outgoing packets + return; + } + + // The first packet should always be a STUN / TURN packet. If it isn't, then + // we should just ignore this packet. + RelayMessage msg; + rtc::ByteBuffer buf(bytes, size); + if (!msg.Read(&buf)) { + LOG(LS_WARNING) << "Dropping packet: first packet not STUN"; + return; + } + + // The initial packet should have a username (which identifies the binding). + const StunByteStringAttribute* username_attr = + msg.GetByteString(STUN_ATTR_USERNAME); + if (!username_attr) { + LOG(LS_WARNING) << "Dropping packet: no username"; + return; + } + + uint32 length = rtc::_min(static_cast(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()) { + LOG(LS_WARNING) << "Dropping packet: no binding with username"; + 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 != NULL); + int_conn->Send(bytes, size, ext_conn->addr_pair().source()); +} + +bool RelayServer::HandleStun( + const char* bytes, size_t size, const rtc::SocketAddress& remote_addr, + rtc::AsyncPacketSocket* socket, std::string* username, + StunMessage* msg) { + + // Parse this into a stun message. Eat the message if this fails. + rtc::ByteBuffer buf(bytes, size); + if (!msg->Read(&buf)) { + 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 rtc::SocketAddressPair& ap, + rtc::AsyncPacketSocket* socket) { + + // Make sure this is a valid STUN request. + RelayMessage 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 = rtc::_min(lifetime, lifetime_attr->value() * 1000); + + binding = new RelayServerBinding(this, username, "0", lifetime); + binding->SignalTimeout.connect(this, &RelayServer::OnTimeout); + bindings_[username] = binding; + + if (log_bindings_) { + LOG(LS_INFO) << "Added new binding " << username << ", " + << bindings_.size() << " total"; + } + } + + // 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. + RelayMessage 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. + + RelayMessage 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(); + rtc::SocketAddress ext_addr = + external_sockets_[index]->GetLocalAddress(); + + StunAddressAttribute* addr_attr = + StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS); + addr_attr->SetIP(ext_addr.ipaddr()); + 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; + } + + rtc::SocketAddress ext_addr(addr_attr->ipaddr(), addr_attr->port()); + RelayServerConnection* ext_conn = + int_conn->binding()->GetExternalConnection(ext_addr); + if (!ext_conn) { + // Create a new connection to establish the relationship with this binding. + ASSERT(external_sockets_.size() == 1); + rtc::AsyncPacketSocket* socket = external_sockets_[0]; + rtc::SocketAddressPair ap(ext_addr, socket->GetLocalAddress()); + ext_conn = new RelayServerConnection(int_conn->binding(), ap, socket); + ext_conn->binding()->AddExternalConnection(ext_conn); + AddConnection(ext_conn); + } + + // If this connection has pinged us, then allow outgoing traffic. + if (ext_conn->locked()) + 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)) { + int_conn->set_default_destination(ext_addr); + int_conn->Lock(); + + RelayMessage 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); + + if (log_bindings_) { + LOG(LS_INFO) << "Removed binding " << binding->username() << ", " + << bindings_.size() << " remaining"; + } +} + +void RelayServer::OnMessage(rtc::Message *pmsg) { +#if ENABLE_DEBUG + static const uint32 kMessageAcceptConnection = 1; + ASSERT(pmsg->message_id == kMessageAcceptConnection); +#endif + rtc::MessageData* data = pmsg->pdata; + rtc::AsyncSocket* socket = + static_cast *> + (data)->data(); + AcceptConnection(socket); + delete data; +} + +void RelayServer::OnTimeout(RelayServerBinding* binding) { + // This call will result in all of the necessary clean-up. We can't call + // delete here, because you can't delete an object that is signaling you. + thread_->Dispose(binding); +} + +void RelayServer::AcceptConnection(rtc::AsyncSocket* server_socket) { + // Check if someone is trying to connect to us. + rtc::SocketAddress accept_addr; + rtc::AsyncSocket* accepted_socket = + server_socket->Accept(&accept_addr); + if (accepted_socket != NULL) { + // We had someone trying to connect, now check which protocol to + // use and create a packet socket. + ASSERT(server_sockets_[server_socket] == cricket::PROTO_TCP || + server_sockets_[server_socket] == cricket::PROTO_SSLTCP); + if (server_sockets_[server_socket] == cricket::PROTO_SSLTCP) { + accepted_socket = new rtc::AsyncSSLServerSocket(accepted_socket); + } + rtc::AsyncTCPSocket* tcp_socket = + new rtc::AsyncTCPSocket(accepted_socket, false); + + // Finally add the socket so it can start communicating with the client. + AddInternalSocket(tcp_socket); + } +} + +RelayServerConnection::RelayServerConnection( + RelayServerBinding* binding, const rtc::SocketAddressPair& addrs, + rtc::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 rtc::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. + + RelayMessage msg; + msg.SetType(STUN_DATA_INDICATION); + + 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->SetIP(from_addr.ipaddr()); + 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(TURN_MAGIC_COOKIE_VALUE), + sizeof(TURN_MAGIC_COOKIE_VALUE)); + + // 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_ = rtc::Time(); +} + +bool RelayServerBinding::HasMagicCookie(const char* bytes, size_t size) const { + if (size < 24 + magic_cookie_.size()) { + return false; + } else { + return memcmp(bytes + 24, magic_cookie_.c_str(), magic_cookie_.size()) == 0; + } +} + +RelayServerConnection* RelayServerBinding::GetInternalConnection( + const rtc::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 rtc::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(rtc::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 (rtc::Time() >= last_used_ + lifetime_) { + LOG(LS_INFO) << "Expiring binding " << username_; + SignalTimeout(this); + } else { + server_->thread()->PostDelayed(lifetime_, this, MSG_LIFETIME_TIMER); + } + + } else { + ASSERT(false); + } +} + +} // namespace cricket diff --git a/webrtc/p2p/base/relayserver.h b/webrtc/p2p/base/relayserver.h new file mode 100644 index 000000000..e0e45d525 --- /dev/null +++ b/webrtc/p2p/base/relayserver.h @@ -0,0 +1,235 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_RELAYSERVER_H_ +#define WEBRTC_P2P_BASE_RELAYSERVER_H_ + +#include +#include +#include + +#include "webrtc/p2p/base/port.h" +#include "webrtc/p2p/base/stun.h" +#include "webrtc/base/asyncudpsocket.h" +#include "webrtc/base/socketaddresspair.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" + +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 rtc::MessageHandler, + public sigslot::has_slots<> { + public: + // Creates a server, which will use this thread to post messages to itself. + explicit RelayServer(rtc::Thread* thread); + ~RelayServer(); + + rtc::Thread* thread() { return thread_; } + + // Indicates whether we will print updates of the number of bindings. + bool log_bindings() const { return log_bindings_; } + void set_log_bindings(bool log_bindings) { log_bindings_ = log_bindings; } + + // Updates the set of sockets that the server uses to talk to "internal" + // clients. These are clients that do the "port allocations". + void AddInternalSocket(rtc::AsyncPacketSocket* socket); + void RemoveInternalSocket(rtc::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(rtc::AsyncPacketSocket* socket); + void RemoveExternalSocket(rtc::AsyncPacketSocket* socket); + + // Starts listening for connections on this sockets. When someone + // tries to connect, the connection will be accepted and a new + // internal socket will be added. + void AddInternalServerSocket(rtc::AsyncSocket* socket, + cricket::ProtocolType proto); + + // Removes this server socket from the list. + void RemoveInternalServerSocket(rtc::AsyncSocket* socket); + + // Methods for testing and debuging. + int GetConnectionCount() const; + rtc::SocketAddressPair GetConnection(int connection) const; + bool HasConnection(const rtc::SocketAddress& address) const; + + private: + typedef std::vector SocketList; + typedef std::map ServerSocketMap; + typedef std::map BindingMap; + typedef std::map ConnectionMap; + + rtc::Thread* thread_; + bool log_bindings_; + SocketList internal_sockets_; + SocketList external_sockets_; + SocketList removed_sockets_; + ServerSocketMap server_sockets_; + BindingMap bindings_; + ConnectionMap connections_; + + // Called when a packet is received by the server on one of its sockets. + void OnInternalPacket(rtc::AsyncPacketSocket* socket, + const char* bytes, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time); + void OnExternalPacket(rtc::AsyncPacketSocket* socket, + const char* bytes, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time); + + void OnReadEvent(rtc::AsyncSocket* socket); + + // Processes the relevant STUN request types from the client. + bool HandleStun(const char* bytes, size_t size, + const rtc::SocketAddress& remote_addr, + rtc::AsyncPacketSocket* socket, + std::string* username, StunMessage* msg); + void HandleStunAllocate(const char* bytes, size_t size, + const rtc::SocketAddressPair& ap, + rtc::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); + + // Handle messages in our worker thread. + void OnMessage(rtc::Message *pmsg); + + // Called when the timer for checking lifetime times out. + void OnTimeout(RelayServerBinding* binding); + + // Accept connections on this server socket. + void AcceptConnection(rtc::AsyncSocket* server_socket); + + 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 rtc::SocketAddressPair& addrs, + rtc::AsyncPacketSocket* socket); + ~RelayServerConnection(); + + RelayServerBinding* binding() { return binding_; } + rtc::AsyncPacketSocket* socket() { return socket_; } + + // Returns a pair where the source is the remote address and the destination + // is the local address. + const rtc::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 rtc::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 rtc::SocketAddress& default_destination() const { + return default_dest_; + } + void set_default_destination(const rtc::SocketAddress& addr) { + default_dest_ = addr; + } + + private: + RelayServerBinding* binding_; + rtc::SocketAddressPair addr_pair_; + rtc::AsyncPacketSocket* socket_; + bool locked_; + rtc::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 rtc::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 rtc::SocketAddress& ext_addr); + RelayServerConnection* GetExternalConnection( + const rtc::SocketAddress& ext_addr); + + // MessageHandler: + void OnMessage(rtc::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 // WEBRTC_P2P_BASE_RELAYSERVER_H_ diff --git a/webrtc/p2p/base/relayserver_unittest.cc b/webrtc/p2p/base/relayserver_unittest.cc new file mode 100644 index 000000000..3349a1730 --- /dev/null +++ b/webrtc/p2p/base/relayserver_unittest.cc @@ -0,0 +1,519 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/p2p/base/relayserver.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/ssladapter.h" +#include "webrtc/base/testclient.h" +#include "webrtc/base/thread.h" + +using rtc::SocketAddress; +using namespace cricket; + +static const uint32 LIFETIME = 4; // seconds +static const SocketAddress server_int_addr("127.0.0.1", 5000); +static const SocketAddress server_ext_addr("127.0.0.1", 5001); +static const SocketAddress client1_addr("127.0.0.1", 6000 + (rand() % 1000)); +static const SocketAddress client2_addr("127.0.0.1", 7000 + (rand() % 1000)); +static const char* bad = "this is a completely nonsensical message whose only " + "purpose is to make the parser go 'ack'. it doesn't " + "look anything like a normal stun message"; +static const char* msg1 = "spamspamspamspamspamspamspambakedbeansspam"; +static const char* msg2 = "Lobster Thermidor a Crevette with a mornay sauce..."; + +class RelayServerTest : public testing::Test { + public: + RelayServerTest() + : main_(rtc::Thread::Current()), ss_(main_->socketserver()), + username_(rtc::CreateRandomString(12)), + password_(rtc::CreateRandomString(12)) { + } + protected: + virtual void SetUp() { + server_.reset(new RelayServer(main_)); + + server_->AddInternalSocket( + rtc::AsyncUDPSocket::Create(ss_, server_int_addr)); + server_->AddExternalSocket( + rtc::AsyncUDPSocket::Create(ss_, server_ext_addr)); + + client1_.reset(new rtc::TestClient( + rtc::AsyncUDPSocket::Create(ss_, client1_addr))); + client2_.reset(new rtc::TestClient( + rtc::AsyncUDPSocket::Create(ss_, client2_addr))); + } + + void Allocate() { + rtc::scoped_ptr req( + CreateStunMessage(STUN_ALLOCATE_REQUEST)); + AddUsernameAttr(req.get(), username_); + AddLifetimeAttr(req.get(), LIFETIME); + Send1(req.get()); + delete Receive1(); + } + void Bind() { + rtc::scoped_ptr req( + CreateStunMessage(STUN_BINDING_REQUEST)); + AddUsernameAttr(req.get(), username_); + Send2(req.get()); + delete Receive1(); + } + + void Send1(const StunMessage* msg) { + rtc::ByteBuffer buf; + msg->Write(&buf); + SendRaw1(buf.Data(), static_cast(buf.Length())); + } + void Send2(const StunMessage* msg) { + rtc::ByteBuffer buf; + msg->Write(&buf); + SendRaw2(buf.Data(), static_cast(buf.Length())); + } + void SendRaw1(const char* data, int len) { + return Send(client1_.get(), data, len, server_int_addr); + } + void SendRaw2(const char* data, int len) { + return Send(client2_.get(), data, len, server_ext_addr); + } + void Send(rtc::TestClient* client, const char* data, + int len, const SocketAddress& addr) { + client->SendTo(data, len, addr); + } + + StunMessage* Receive1() { + return Receive(client1_.get()); + } + StunMessage* Receive2() { + return Receive(client2_.get()); + } + std::string ReceiveRaw1() { + return ReceiveRaw(client1_.get()); + } + std::string ReceiveRaw2() { + return ReceiveRaw(client2_.get()); + } + StunMessage* Receive(rtc::TestClient* client) { + StunMessage* msg = NULL; + rtc::TestClient::Packet* packet = client->NextPacket(); + if (packet) { + rtc::ByteBuffer buf(packet->buf, packet->size); + msg = new RelayMessage(); + msg->Read(&buf); + delete packet; + } + return msg; + } + std::string ReceiveRaw(rtc::TestClient* client) { + std::string raw; + rtc::TestClient::Packet* packet = client->NextPacket(); + if (packet) { + raw = std::string(packet->buf, packet->size); + delete packet; + } + return raw; + } + + static StunMessage* CreateStunMessage(int type) { + StunMessage* msg = new RelayMessage(); + msg->SetType(type); + msg->SetTransactionID( + rtc::CreateRandomString(kStunTransactionIdLength)); + return msg; + } + static void AddMagicCookieAttr(StunMessage* msg) { + StunByteStringAttribute* attr = + StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE); + attr->CopyBytes(TURN_MAGIC_COOKIE_VALUE, sizeof(TURN_MAGIC_COOKIE_VALUE)); + msg->AddAttribute(attr); + } + static void AddUsernameAttr(StunMessage* msg, const std::string& val) { + StunByteStringAttribute* attr = + StunAttribute::CreateByteString(STUN_ATTR_USERNAME); + attr->CopyBytes(val.c_str(), val.size()); + msg->AddAttribute(attr); + } + static void AddLifetimeAttr(StunMessage* msg, int val) { + StunUInt32Attribute* attr = + StunAttribute::CreateUInt32(STUN_ATTR_LIFETIME); + attr->SetValue(val); + msg->AddAttribute(attr); + } + static void AddDestinationAttr(StunMessage* msg, const SocketAddress& addr) { + StunAddressAttribute* attr = + StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS); + attr->SetIP(addr.ipaddr()); + attr->SetPort(addr.port()); + msg->AddAttribute(attr); + } + + rtc::Thread* main_; + rtc::SocketServer* ss_; + rtc::scoped_ptr server_; + rtc::scoped_ptr client1_; + rtc::scoped_ptr client2_; + std::string username_; + std::string password_; +}; + +// Send a complete nonsense message and verify that it is eaten. +TEST_F(RelayServerTest, TestBadRequest) { + rtc::scoped_ptr res; + + SendRaw1(bad, static_cast(strlen(bad))); + res.reset(Receive1()); + + ASSERT_TRUE(!res); +} + +// Send an allocate request without a username and verify it is rejected. +TEST_F(RelayServerTest, TestAllocateNoUsername) { + rtc::scoped_ptr req( + CreateStunMessage(STUN_ALLOCATE_REQUEST)), res; + + Send1(req.get()); + res.reset(Receive1()); + + ASSERT_TRUE(res); + EXPECT_EQ(STUN_ALLOCATE_ERROR_RESPONSE, res->type()); + EXPECT_EQ(req->transaction_id(), res->transaction_id()); + + const StunErrorCodeAttribute* err = res->GetErrorCode(); + ASSERT_TRUE(err != NULL); + EXPECT_EQ(4, err->eclass()); + EXPECT_EQ(32, err->number()); + EXPECT_EQ("Missing Username", err->reason()); +} + +// Send a binding request and verify that it is rejected. +TEST_F(RelayServerTest, TestBindingRequest) { + rtc::scoped_ptr req( + CreateStunMessage(STUN_BINDING_REQUEST)), res; + AddUsernameAttr(req.get(), username_); + + Send1(req.get()); + res.reset(Receive1()); + + ASSERT_TRUE(res); + EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, res->type()); + EXPECT_EQ(req->transaction_id(), res->transaction_id()); + + const StunErrorCodeAttribute* err = res->GetErrorCode(); + ASSERT_TRUE(err != NULL); + EXPECT_EQ(6, err->eclass()); + EXPECT_EQ(0, err->number()); + EXPECT_EQ("Operation Not Supported", err->reason()); +} + +// Send an allocate request and verify that it is accepted. +TEST_F(RelayServerTest, TestAllocate) { + rtc::scoped_ptr req( + CreateStunMessage(STUN_ALLOCATE_REQUEST)), res; + AddUsernameAttr(req.get(), username_); + AddLifetimeAttr(req.get(), LIFETIME); + + Send1(req.get()); + res.reset(Receive1()); + + ASSERT_TRUE(res); + EXPECT_EQ(STUN_ALLOCATE_RESPONSE, res->type()); + EXPECT_EQ(req->transaction_id(), res->transaction_id()); + + const StunAddressAttribute* mapped_addr = + res->GetAddress(STUN_ATTR_MAPPED_ADDRESS); + ASSERT_TRUE(mapped_addr != NULL); + EXPECT_EQ(1, mapped_addr->family()); + EXPECT_EQ(server_ext_addr.port(), mapped_addr->port()); + EXPECT_EQ(server_ext_addr.ipaddr(), mapped_addr->ipaddr()); + + const StunUInt32Attribute* res_lifetime_attr = + res->GetUInt32(STUN_ATTR_LIFETIME); + ASSERT_TRUE(res_lifetime_attr != NULL); + EXPECT_EQ(LIFETIME, res_lifetime_attr->value()); +} + +// Send a second allocate request and verify that it is also accepted, though +// the lifetime should be ignored. +TEST_F(RelayServerTest, TestReallocate) { + Allocate(); + + rtc::scoped_ptr req( + CreateStunMessage(STUN_ALLOCATE_REQUEST)), res; + AddMagicCookieAttr(req.get()); + AddUsernameAttr(req.get(), username_); + + Send1(req.get()); + res.reset(Receive1()); + + ASSERT_TRUE(res); + EXPECT_EQ(STUN_ALLOCATE_RESPONSE, res->type()); + EXPECT_EQ(req->transaction_id(), res->transaction_id()); + + const StunAddressAttribute* mapped_addr = + res->GetAddress(STUN_ATTR_MAPPED_ADDRESS); + ASSERT_TRUE(mapped_addr != NULL); + EXPECT_EQ(1, mapped_addr->family()); + EXPECT_EQ(server_ext_addr.port(), mapped_addr->port()); + EXPECT_EQ(server_ext_addr.ipaddr(), mapped_addr->ipaddr()); + + const StunUInt32Attribute* lifetime_attr = + res->GetUInt32(STUN_ATTR_LIFETIME); + ASSERT_TRUE(lifetime_attr != NULL); + EXPECT_EQ(LIFETIME, lifetime_attr->value()); +} + +// Send a request from another client and see that it arrives at the first +// client in the binding. +TEST_F(RelayServerTest, TestRemoteBind) { + Allocate(); + + rtc::scoped_ptr req( + CreateStunMessage(STUN_BINDING_REQUEST)), res; + AddUsernameAttr(req.get(), username_); + + Send2(req.get()); + res.reset(Receive1()); + + ASSERT_TRUE(res); + EXPECT_EQ(STUN_DATA_INDICATION, res->type()); + + const StunByteStringAttribute* recv_data = + res->GetByteString(STUN_ATTR_DATA); + ASSERT_TRUE(recv_data != NULL); + + rtc::ByteBuffer buf(recv_data->bytes(), recv_data->length()); + rtc::scoped_ptr res2(new StunMessage()); + EXPECT_TRUE(res2->Read(&buf)); + EXPECT_EQ(STUN_BINDING_REQUEST, res2->type()); + EXPECT_EQ(req->transaction_id(), res2->transaction_id()); + + const StunAddressAttribute* src_addr = + res->GetAddress(STUN_ATTR_SOURCE_ADDRESS2); + ASSERT_TRUE(src_addr != NULL); + EXPECT_EQ(1, src_addr->family()); + EXPECT_EQ(client2_addr.ipaddr(), src_addr->ipaddr()); + EXPECT_EQ(client2_addr.port(), src_addr->port()); + + EXPECT_TRUE(Receive2() == NULL); +} + +// Send a complete nonsense message to the established connection and verify +// that it is dropped by the server. +TEST_F(RelayServerTest, TestRemoteBadRequest) { + Allocate(); + Bind(); + + SendRaw1(bad, static_cast(strlen(bad))); + EXPECT_TRUE(Receive1() == NULL); + EXPECT_TRUE(Receive2() == NULL); +} + +// Send a send request without a username and verify it is rejected. +TEST_F(RelayServerTest, TestSendRequestMissingUsername) { + Allocate(); + Bind(); + + rtc::scoped_ptr req( + CreateStunMessage(STUN_SEND_REQUEST)), res; + AddMagicCookieAttr(req.get()); + + Send1(req.get()); + res.reset(Receive1()); + + ASSERT_TRUE(res); + EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type()); + EXPECT_EQ(req->transaction_id(), res->transaction_id()); + + const StunErrorCodeAttribute* err = res->GetErrorCode(); + ASSERT_TRUE(err != NULL); + EXPECT_EQ(4, err->eclass()); + EXPECT_EQ(32, err->number()); + EXPECT_EQ("Missing Username", err->reason()); +} + +// Send a send request with the wrong username and verify it is rejected. +TEST_F(RelayServerTest, TestSendRequestBadUsername) { + Allocate(); + Bind(); + + rtc::scoped_ptr req( + CreateStunMessage(STUN_SEND_REQUEST)), res; + AddMagicCookieAttr(req.get()); + AddUsernameAttr(req.get(), "foobarbizbaz"); + + Send1(req.get()); + res.reset(Receive1()); + + ASSERT_TRUE(res); + EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type()); + EXPECT_EQ(req->transaction_id(), res->transaction_id()); + + const StunErrorCodeAttribute* err = res->GetErrorCode(); + ASSERT_TRUE(err != NULL); + EXPECT_EQ(4, err->eclass()); + EXPECT_EQ(30, err->number()); + EXPECT_EQ("Stale Credentials", err->reason()); +} + +// Send a send request without a destination address and verify that it is +// rejected. +TEST_F(RelayServerTest, TestSendRequestNoDestinationAddress) { + Allocate(); + Bind(); + + rtc::scoped_ptr req( + CreateStunMessage(STUN_SEND_REQUEST)), res; + AddMagicCookieAttr(req.get()); + AddUsernameAttr(req.get(), username_); + + Send1(req.get()); + res.reset(Receive1()); + + ASSERT_TRUE(res); + EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type()); + EXPECT_EQ(req->transaction_id(), res->transaction_id()); + + const StunErrorCodeAttribute* err = res->GetErrorCode(); + ASSERT_TRUE(err != NULL); + EXPECT_EQ(4, err->eclass()); + EXPECT_EQ(0, err->number()); + EXPECT_EQ("Bad Request", err->reason()); +} + +// Send a send request without data and verify that it is rejected. +TEST_F(RelayServerTest, TestSendRequestNoData) { + Allocate(); + Bind(); + + rtc::scoped_ptr req( + CreateStunMessage(STUN_SEND_REQUEST)), res; + AddMagicCookieAttr(req.get()); + AddUsernameAttr(req.get(), username_); + AddDestinationAttr(req.get(), client2_addr); + + Send1(req.get()); + res.reset(Receive1()); + + ASSERT_TRUE(res); + EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type()); + EXPECT_EQ(req->transaction_id(), res->transaction_id()); + + const StunErrorCodeAttribute* err = res->GetErrorCode(); + ASSERT_TRUE(err != NULL); + EXPECT_EQ(4, err->eclass()); + EXPECT_EQ(00, err->number()); + EXPECT_EQ("Bad Request", err->reason()); +} + +// Send a binding request after an allocate and verify that it is rejected. +TEST_F(RelayServerTest, TestSendRequestWrongType) { + Allocate(); + Bind(); + + rtc::scoped_ptr req( + CreateStunMessage(STUN_BINDING_REQUEST)), res; + AddMagicCookieAttr(req.get()); + AddUsernameAttr(req.get(), username_); + + Send1(req.get()); + res.reset(Receive1()); + + ASSERT_TRUE(res); + EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, res->type()); + EXPECT_EQ(req->transaction_id(), res->transaction_id()); + + const StunErrorCodeAttribute* err = res->GetErrorCode(); + ASSERT_TRUE(err != NULL); + EXPECT_EQ(6, err->eclass()); + EXPECT_EQ(0, err->number()); + EXPECT_EQ("Operation Not Supported", err->reason()); +} + +// Verify that we can send traffic back and forth between the clients after a +// successful allocate and bind. +TEST_F(RelayServerTest, TestSendRaw) { + Allocate(); + Bind(); + + for (int i = 0; i < 10; i++) { + rtc::scoped_ptr req( + CreateStunMessage(STUN_SEND_REQUEST)), res; + AddMagicCookieAttr(req.get()); + AddUsernameAttr(req.get(), username_); + AddDestinationAttr(req.get(), client2_addr); + + StunByteStringAttribute* send_data = + StunAttribute::CreateByteString(STUN_ATTR_DATA); + send_data->CopyBytes(msg1); + req->AddAttribute(send_data); + + Send1(req.get()); + EXPECT_EQ(msg1, ReceiveRaw2()); + SendRaw2(msg2, static_cast(strlen(msg2))); + res.reset(Receive1()); + + ASSERT_TRUE(res); + EXPECT_EQ(STUN_DATA_INDICATION, res->type()); + + const StunAddressAttribute* src_addr = + res->GetAddress(STUN_ATTR_SOURCE_ADDRESS2); + ASSERT_TRUE(src_addr != NULL); + EXPECT_EQ(1, src_addr->family()); + EXPECT_EQ(client2_addr.ipaddr(), src_addr->ipaddr()); + EXPECT_EQ(client2_addr.port(), src_addr->port()); + + const StunByteStringAttribute* recv_data = + res->GetByteString(STUN_ATTR_DATA); + ASSERT_TRUE(recv_data != NULL); + EXPECT_EQ(strlen(msg2), recv_data->length()); + EXPECT_EQ(0, memcmp(msg2, recv_data->bytes(), recv_data->length())); + } +} + +// Verify that a binding expires properly, and rejects send requests. +TEST_F(RelayServerTest, TestExpiration) { + Allocate(); + Bind(); + + // Wait twice the lifetime to make sure the server has expired the binding. + rtc::Thread::Current()->ProcessMessages((LIFETIME * 2) * 1000); + + rtc::scoped_ptr req( + CreateStunMessage(STUN_SEND_REQUEST)), res; + AddMagicCookieAttr(req.get()); + AddUsernameAttr(req.get(), username_); + AddDestinationAttr(req.get(), client2_addr); + + StunByteStringAttribute* data_attr = + StunAttribute::CreateByteString(STUN_ATTR_DATA); + data_attr->CopyBytes(msg1); + req->AddAttribute(data_attr); + + Send1(req.get()); + res.reset(Receive1()); + + ASSERT_TRUE(res.get() != NULL); + EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type()); + + const StunErrorCodeAttribute* err = res->GetErrorCode(); + ASSERT_TRUE(err != NULL); + EXPECT_EQ(6, err->eclass()); + EXPECT_EQ(0, err->number()); + EXPECT_EQ("Operation Not Supported", err->reason()); + + // Also verify that traffic from the external client is ignored. + SendRaw2(msg2, static_cast(strlen(msg2))); + EXPECT_TRUE(ReceiveRaw1().empty()); +} diff --git a/webrtc/p2p/base/session.cc b/webrtc/p2p/base/session.cc new file mode 100644 index 000000000..9749b14ef --- /dev/null +++ b/webrtc/p2p/base/session.cc @@ -0,0 +1,1760 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/session.h" + +#include "webrtc/p2p/base/dtlstransport.h" +#include "webrtc/p2p/base/p2ptransport.h" +#include "webrtc/p2p/base/sessionclient.h" +#include "webrtc/p2p/base/transport.h" +#include "webrtc/p2p/base/transportchannelproxy.h" +#include "webrtc/p2p/base/transportinfo.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/base/bind.h" +#include "webrtc/base/common.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sslstreamadapter.h" + +#include "webrtc/p2p/base/constants.h" + +namespace cricket { + +using rtc::Bind; + +bool BadMessage(const buzz::QName type, + const std::string& text, + MessageError* err) { + err->SetType(type); + err->SetText(text); + return false; +} + +TransportProxy::~TransportProxy() { + for (ChannelMap::iterator iter = channels_.begin(); + iter != channels_.end(); ++iter) { + iter->second->SignalDestroyed(iter->second); + delete iter->second; + } +} + +const std::string& TransportProxy::type() const { + return transport_->get()->type(); +} + +TransportChannel* TransportProxy::GetChannel(int component) { + ASSERT(rtc::Thread::Current() == worker_thread_); + return GetChannelProxy(component); +} + +TransportChannel* TransportProxy::CreateChannel( + const std::string& name, int component) { + ASSERT(rtc::Thread::Current() == worker_thread_); + ASSERT(GetChannel(component) == NULL); + ASSERT(!transport_->get()->HasChannel(component)); + + // We always create a proxy in case we need to change out the transport later. + TransportChannelProxy* channel = + new TransportChannelProxy(content_name(), name, component); + channels_[component] = channel; + + // If we're already negotiated, create an impl and hook it up to the proxy + // channel. If we're connecting, create an impl but don't hook it up yet. + if (negotiated_) { + SetupChannelProxy_w(component, channel); + } else if (connecting_) { + GetOrCreateChannelProxyImpl_w(component); + } + return channel; +} + +bool TransportProxy::HasChannel(int component) { + return transport_->get()->HasChannel(component); +} + +void TransportProxy::DestroyChannel(int component) { + ASSERT(rtc::Thread::Current() == worker_thread_); + TransportChannel* channel = GetChannel(component); + if (channel) { + // If the state of TransportProxy is not NEGOTIATED + // then TransportChannelProxy and its impl are not + // connected. Both must be connected before + // deletion. + if (!negotiated_) { + SetupChannelProxy_w(component, GetChannelProxy(component)); + } + + channels_.erase(component); + channel->SignalDestroyed(channel); + delete channel; + } +} + +void TransportProxy::ConnectChannels() { + if (!connecting_) { + if (!negotiated_) { + for (ChannelMap::iterator iter = channels_.begin(); + iter != channels_.end(); ++iter) { + GetOrCreateChannelProxyImpl(iter->first); + } + } + connecting_ = true; + } + // TODO(juberti): Right now Transport::ConnectChannels doesn't work if we + // don't have any channels yet, so we need to allow this method to be called + // multiple times. Once we fix Transport, we can move this call inside the + // if (!connecting_) block. + transport_->get()->ConnectChannels(); +} + +void TransportProxy::CompleteNegotiation() { + if (!negotiated_) { + for (ChannelMap::iterator iter = channels_.begin(); + iter != channels_.end(); ++iter) { + SetupChannelProxy(iter->first, iter->second); + } + negotiated_ = true; + } +} + +void TransportProxy::AddSentCandidates(const Candidates& candidates) { + for (Candidates::const_iterator cand = candidates.begin(); + cand != candidates.end(); ++cand) { + sent_candidates_.push_back(*cand); + } +} + +void TransportProxy::AddUnsentCandidates(const Candidates& candidates) { + for (Candidates::const_iterator cand = candidates.begin(); + cand != candidates.end(); ++cand) { + unsent_candidates_.push_back(*cand); + } +} + +bool TransportProxy::GetChannelNameFromComponent( + int component, std::string* channel_name) const { + const TransportChannelProxy* channel = GetChannelProxy(component); + if (channel == NULL) { + return false; + } + + *channel_name = channel->name(); + return true; +} + +bool TransportProxy::GetComponentFromChannelName( + const std::string& channel_name, int* component) const { + const TransportChannelProxy* channel = GetChannelProxyByName(channel_name); + if (channel == NULL) { + return false; + } + + *component = channel->component(); + return true; +} + +TransportChannelProxy* TransportProxy::GetChannelProxy(int component) const { + ChannelMap::const_iterator iter = channels_.find(component); + return (iter != channels_.end()) ? iter->second : NULL; +} + +TransportChannelProxy* TransportProxy::GetChannelProxyByName( + const std::string& name) const { + for (ChannelMap::const_iterator iter = channels_.begin(); + iter != channels_.end(); + ++iter) { + if (iter->second->name() == name) { + return iter->second; + } + } + return NULL; +} + +TransportChannelImpl* TransportProxy::GetOrCreateChannelProxyImpl( + int component) { + return worker_thread_->Invoke(Bind( + &TransportProxy::GetOrCreateChannelProxyImpl_w, this, component)); +} + +TransportChannelImpl* TransportProxy::GetOrCreateChannelProxyImpl_w( + int component) { + ASSERT(rtc::Thread::Current() == worker_thread_); + TransportChannelImpl* impl = transport_->get()->GetChannel(component); + if (impl == NULL) { + impl = transport_->get()->CreateChannel(component); + } + return impl; +} + +void TransportProxy::SetupChannelProxy( + int component, TransportChannelProxy* transproxy) { + worker_thread_->Invoke(Bind( + &TransportProxy::SetupChannelProxy_w, this, component, transproxy)); +} + +void TransportProxy::SetupChannelProxy_w( + int component, TransportChannelProxy* transproxy) { + ASSERT(rtc::Thread::Current() == worker_thread_); + TransportChannelImpl* impl = GetOrCreateChannelProxyImpl(component); + ASSERT(impl != NULL); + transproxy->SetImplementation(impl); +} + +void TransportProxy::ReplaceChannelProxyImpl(TransportChannelProxy* proxy, + TransportChannelImpl* impl) { + worker_thread_->Invoke(Bind( + &TransportProxy::ReplaceChannelProxyImpl_w, this, proxy, impl)); +} + +void TransportProxy::ReplaceChannelProxyImpl_w(TransportChannelProxy* proxy, + TransportChannelImpl* impl) { + ASSERT(rtc::Thread::Current() == worker_thread_); + ASSERT(proxy != NULL); + proxy->SetImplementation(impl); +} + +// This function muxes |this| onto |target| by repointing |this| at +// |target|'s transport and setting our TransportChannelProxies +// to point to |target|'s underlying implementations. +bool TransportProxy::SetupMux(TransportProxy* target) { + // Bail out if there's nothing to do. + if (transport_ == target->transport_) { + return true; + } + + // Run through all channels and remove any non-rtp transport channels before + // setting target transport channels. + for (ChannelMap::const_iterator iter = channels_.begin(); + iter != channels_.end(); ++iter) { + if (!target->transport_->get()->HasChannel(iter->first)) { + // Remove if channel doesn't exist in |transport_|. + ReplaceChannelProxyImpl(iter->second, NULL); + } else { + // Replace the impl for all the TransportProxyChannels with the channels + // from |target|'s transport. Fail if there's not an exact match. + ReplaceChannelProxyImpl( + iter->second, target->transport_->get()->CreateChannel(iter->first)); + } + } + + // Now replace our transport. Must happen afterwards because + // it deletes all impls as a side effect. + transport_ = target->transport_; + transport_->get()->SignalCandidatesReady.connect( + this, &TransportProxy::OnTransportCandidatesReady); + set_candidates_allocated(target->candidates_allocated()); + return true; +} + +void TransportProxy::SetIceRole(IceRole role) { + transport_->get()->SetIceRole(role); +} + +bool TransportProxy::SetLocalTransportDescription( + const TransportDescription& description, + ContentAction action, + std::string* error_desc) { + // If this is an answer, finalize the negotiation. + if (action == CA_ANSWER) { + CompleteNegotiation(); + } + bool result = transport_->get()->SetLocalTransportDescription(description, + action, + error_desc); + if (result) + local_description_set_ = true; + return result; +} + +bool TransportProxy::SetRemoteTransportDescription( + const TransportDescription& description, + ContentAction action, + std::string* error_desc) { + // If this is an answer, finalize the negotiation. + if (action == CA_ANSWER) { + CompleteNegotiation(); + } + bool result = transport_->get()->SetRemoteTransportDescription(description, + action, + error_desc); + if (result) + remote_description_set_ = true; + return result; +} + +void TransportProxy::OnSignalingReady() { + // If we're starting a new allocation sequence, reset our state. + set_candidates_allocated(false); + transport_->get()->OnSignalingReady(); +} + +bool TransportProxy::OnRemoteCandidates(const Candidates& candidates, + std::string* error) { + // Ensure the transport is negotiated before handling candidates. + // TODO(juberti): Remove this once everybody calls SetLocalTD. + CompleteNegotiation(); + + // Verify each candidate before passing down to transport layer. + for (Candidates::const_iterator cand = candidates.begin(); + cand != candidates.end(); ++cand) { + if (!transport_->get()->VerifyCandidate(*cand, error)) + return false; + if (!HasChannel(cand->component())) { + *error = "Candidate has unknown component: " + cand->ToString() + + " for content: " + content_name_; + return false; + } + } + transport_->get()->OnRemoteCandidates(candidates); + return true; +} + +void TransportProxy::SetIdentity( + rtc::SSLIdentity* identity) { + transport_->get()->SetIdentity(identity); +} + +std::string BaseSession::StateToString(State state) { + switch (state) { + case Session::STATE_INIT: + return "STATE_INIT"; + case Session::STATE_SENTINITIATE: + return "STATE_SENTINITIATE"; + case Session::STATE_RECEIVEDINITIATE: + return "STATE_RECEIVEDINITIATE"; + case Session::STATE_SENTPRACCEPT: + return "STATE_SENTPRACCEPT"; + case Session::STATE_SENTACCEPT: + return "STATE_SENTACCEPT"; + case Session::STATE_RECEIVEDPRACCEPT: + return "STATE_RECEIVEDPRACCEPT"; + case Session::STATE_RECEIVEDACCEPT: + return "STATE_RECEIVEDACCEPT"; + case Session::STATE_SENTMODIFY: + return "STATE_SENTMODIFY"; + case Session::STATE_RECEIVEDMODIFY: + return "STATE_RECEIVEDMODIFY"; + case Session::STATE_SENTREJECT: + return "STATE_SENTREJECT"; + case Session::STATE_RECEIVEDREJECT: + return "STATE_RECEIVEDREJECT"; + case Session::STATE_SENTREDIRECT: + return "STATE_SENTREDIRECT"; + case Session::STATE_SENTTERMINATE: + return "STATE_SENTTERMINATE"; + case Session::STATE_RECEIVEDTERMINATE: + return "STATE_RECEIVEDTERMINATE"; + case Session::STATE_INPROGRESS: + return "STATE_INPROGRESS"; + case Session::STATE_DEINIT: + return "STATE_DEINIT"; + default: + break; + } + return "STATE_" + rtc::ToString(state); +} + +BaseSession::BaseSession(rtc::Thread* signaling_thread, + rtc::Thread* worker_thread, + PortAllocator* port_allocator, + const std::string& sid, + const std::string& content_type, + bool initiator) + : state_(STATE_INIT), + error_(ERROR_NONE), + signaling_thread_(signaling_thread), + worker_thread_(worker_thread), + port_allocator_(port_allocator), + sid_(sid), + content_type_(content_type), + transport_type_(NS_GINGLE_P2P), + initiator_(initiator), + identity_(NULL), + ice_tiebreaker_(rtc::CreateRandomId64()), + role_switch_(false) { + ASSERT(signaling_thread->IsCurrent()); +} + +BaseSession::~BaseSession() { + ASSERT(signaling_thread()->IsCurrent()); + + ASSERT(state_ != STATE_DEINIT); + LogState(state_, STATE_DEINIT); + state_ = STATE_DEINIT; + SignalState(this, state_); + + for (TransportMap::iterator iter = transports_.begin(); + iter != transports_.end(); ++iter) { + delete iter->second; + } +} + +const SessionDescription* BaseSession::local_description() const { + // TODO(tommi): Assert on thread correctness. + return local_description_.get(); +} + +const SessionDescription* BaseSession::remote_description() const { + // TODO(tommi): Assert on thread correctness. + return remote_description_.get(); +} + +SessionDescription* BaseSession::remote_description() { + // TODO(tommi): Assert on thread correctness. + return remote_description_.get(); +} + +void BaseSession::set_local_description(const SessionDescription* sdesc) { + // TODO(tommi): Assert on thread correctness. + if (sdesc != local_description_.get()) + local_description_.reset(sdesc); +} + +void BaseSession::set_remote_description(SessionDescription* sdesc) { + // TODO(tommi): Assert on thread correctness. + if (sdesc != remote_description_) + remote_description_.reset(sdesc); +} + +const SessionDescription* BaseSession::initiator_description() const { + // TODO(tommi): Assert on thread correctness. + return initiator_ ? local_description_.get() : remote_description_.get(); +} + +bool BaseSession::SetIdentity(rtc::SSLIdentity* identity) { + if (identity_) + return false; + identity_ = identity; + for (TransportMap::iterator iter = transports_.begin(); + iter != transports_.end(); ++iter) { + iter->second->SetIdentity(identity_); + } + return true; +} + +bool BaseSession::PushdownTransportDescription(ContentSource source, + ContentAction action, + std::string* error_desc) { + if (source == CS_LOCAL) { + return PushdownLocalTransportDescription(local_description(), + action, + error_desc); + } + return PushdownRemoteTransportDescription(remote_description(), + action, + error_desc); +} + +bool BaseSession::PushdownLocalTransportDescription( + const SessionDescription* sdesc, + ContentAction action, + std::string* error_desc) { + // Update the Transports with the right information, and trigger them to + // start connecting. + for (TransportMap::iterator iter = transports_.begin(); + iter != transports_.end(); ++iter) { + // If no transport info was in this session description, ret == false + // and we just skip this one. + TransportDescription tdesc; + bool ret = GetTransportDescription( + sdesc, iter->second->content_name(), &tdesc); + if (ret) { + if (!iter->second->SetLocalTransportDescription(tdesc, action, + error_desc)) { + return false; + } + + iter->second->ConnectChannels(); + } + } + + return true; +} + +bool BaseSession::PushdownRemoteTransportDescription( + const SessionDescription* sdesc, + ContentAction action, + std::string* error_desc) { + // Update the Transports with the right information. + for (TransportMap::iterator iter = transports_.begin(); + iter != transports_.end(); ++iter) { + TransportDescription tdesc; + + // If no transport info was in this session description, ret == false + // and we just skip this one. + bool ret = GetTransportDescription( + sdesc, iter->second->content_name(), &tdesc); + if (ret) { + if (!iter->second->SetRemoteTransportDescription(tdesc, action, + error_desc)) { + return false; + } + } + } + + return true; +} + +TransportChannel* BaseSession::CreateChannel(const std::string& content_name, + const std::string& channel_name, + int component) { + // We create the proxy "on demand" here because we need to support + // creating channels at any time, even before we send or receive + // initiate messages, which is before we create the transports. + TransportProxy* transproxy = GetOrCreateTransportProxy(content_name); + return transproxy->CreateChannel(channel_name, component); +} + +TransportChannel* BaseSession::GetChannel(const std::string& content_name, + int component) { + TransportProxy* transproxy = GetTransportProxy(content_name); + if (transproxy == NULL) + return NULL; + + return transproxy->GetChannel(component); +} + +void BaseSession::DestroyChannel(const std::string& content_name, + int component) { + TransportProxy* transproxy = GetTransportProxy(content_name); + ASSERT(transproxy != NULL); + transproxy->DestroyChannel(component); +} + +TransportProxy* BaseSession::GetOrCreateTransportProxy( + const std::string& content_name) { + TransportProxy* transproxy = GetTransportProxy(content_name); + if (transproxy) + return transproxy; + + Transport* transport = CreateTransport(content_name); + transport->SetIceRole(initiator_ ? ICEROLE_CONTROLLING : ICEROLE_CONTROLLED); + transport->SetIceTiebreaker(ice_tiebreaker_); + // TODO: Connect all the Transport signals to TransportProxy + // then to the BaseSession. + transport->SignalConnecting.connect( + this, &BaseSession::OnTransportConnecting); + transport->SignalWritableState.connect( + this, &BaseSession::OnTransportWritable); + transport->SignalRequestSignaling.connect( + this, &BaseSession::OnTransportRequestSignaling); + transport->SignalTransportError.connect( + this, &BaseSession::OnTransportSendError); + transport->SignalRouteChange.connect( + this, &BaseSession::OnTransportRouteChange); + transport->SignalCandidatesAllocationDone.connect( + this, &BaseSession::OnTransportCandidatesAllocationDone); + transport->SignalRoleConflict.connect( + this, &BaseSession::OnRoleConflict); + transport->SignalCompleted.connect( + this, &BaseSession::OnTransportCompleted); + transport->SignalFailed.connect( + this, &BaseSession::OnTransportFailed); + + transproxy = new TransportProxy(worker_thread_, sid_, content_name, + new TransportWrapper(transport)); + transproxy->SignalCandidatesReady.connect( + this, &BaseSession::OnTransportProxyCandidatesReady); + if (identity_) + transproxy->SetIdentity(identity_); + transports_[content_name] = transproxy; + + return transproxy; +} + +Transport* BaseSession::GetTransport(const std::string& content_name) { + TransportProxy* transproxy = GetTransportProxy(content_name); + if (transproxy == NULL) + return NULL; + return transproxy->impl(); +} + +TransportProxy* BaseSession::GetTransportProxy( + const std::string& content_name) { + TransportMap::iterator iter = transports_.find(content_name); + return (iter != transports_.end()) ? iter->second : NULL; +} + +TransportProxy* BaseSession::GetTransportProxy(const Transport* transport) { + for (TransportMap::iterator iter = transports_.begin(); + iter != transports_.end(); ++iter) { + TransportProxy* transproxy = iter->second; + if (transproxy->impl() == transport) { + return transproxy; + } + } + return NULL; +} + +TransportProxy* BaseSession::GetFirstTransportProxy() { + if (transports_.empty()) + return NULL; + return transports_.begin()->second; +} + +void BaseSession::DestroyTransportProxy( + const std::string& content_name) { + TransportMap::iterator iter = transports_.find(content_name); + if (iter != transports_.end()) { + delete iter->second; + transports_.erase(content_name); + } +} + +cricket::Transport* BaseSession::CreateTransport( + const std::string& content_name) { + ASSERT(transport_type_ == NS_GINGLE_P2P); + return new cricket::DtlsTransport( + signaling_thread(), worker_thread(), content_name, + port_allocator(), identity_); +} + +bool BaseSession::GetStats(SessionStats* stats) { + for (TransportMap::iterator iter = transports_.begin(); + iter != transports_.end(); ++iter) { + std::string proxy_id = iter->second->content_name(); + // We are ignoring not-yet-instantiated transports. + if (iter->second->impl()) { + std::string transport_id = iter->second->impl()->content_name(); + stats->proxy_to_transport[proxy_id] = transport_id; + if (stats->transport_stats.find(transport_id) + == stats->transport_stats.end()) { + TransportStats subinfos; + if (!iter->second->impl()->GetStats(&subinfos)) { + return false; + } + stats->transport_stats[transport_id] = subinfos; + } + } + } + return true; +} + +void BaseSession::SetState(State state) { + ASSERT(signaling_thread_->IsCurrent()); + if (state != state_) { + LogState(state_, state); + state_ = state; + SignalState(this, state_); + signaling_thread_->Post(this, MSG_STATE); + } + SignalNewDescription(); +} + +void BaseSession::SetError(Error error, const std::string& error_desc) { + ASSERT(signaling_thread_->IsCurrent()); + if (error != error_) { + error_ = error; + error_desc_ = error_desc; + SignalError(this, error); + } +} + +void BaseSession::OnSignalingReady() { + ASSERT(signaling_thread()->IsCurrent()); + for (TransportMap::iterator iter = transports_.begin(); + iter != transports_.end(); ++iter) { + iter->second->OnSignalingReady(); + } +} + +// TODO(juberti): Since PushdownLocalTD now triggers the connection process to +// start, remove this method once everyone calls PushdownLocalTD. +void BaseSession::SpeculativelyConnectAllTransportChannels() { + // Put all transports into the connecting state. + for (TransportMap::iterator iter = transports_.begin(); + iter != transports_.end(); ++iter) { + iter->second->ConnectChannels(); + } +} + +bool BaseSession::OnRemoteCandidates(const std::string& content_name, + const Candidates& candidates, + std::string* error) { + // Give candidates to the appropriate transport, and tell that transport + // to start connecting, if it's not already doing so. + TransportProxy* transproxy = GetTransportProxy(content_name); + if (!transproxy) { + *error = "Unknown content name " + content_name; + return false; + } + if (!transproxy->OnRemoteCandidates(candidates, error)) { + return false; + } + // TODO(juberti): Remove this call once we can be sure that we always have + // a local transport description (which will trigger the connection). + transproxy->ConnectChannels(); + return true; +} + +bool BaseSession::MaybeEnableMuxingSupport() { + // We need both a local and remote description to decide if we should mux. + if ((state_ == STATE_SENTINITIATE || + state_ == STATE_RECEIVEDINITIATE) && + ((local_description_ == NULL) || + (remote_description_ == NULL))) { + return false; + } + + // In order to perform the multiplexing, we need all proxies to be in the + // negotiated state, i.e. to have implementations underneath. + // Ensure that this is the case, regardless of whether we are going to mux. + for (TransportMap::iterator iter = transports_.begin(); + iter != transports_.end(); ++iter) { + ASSERT(iter->second->negotiated()); + if (!iter->second->negotiated()) + return false; + } + + // If both sides agree to BUNDLE, mux all the specified contents onto the + // transport belonging to the first content name in the BUNDLE group. + // If the contents are already muxed, this will be a no-op. + // TODO(juberti): Should this check that local and remote have configured + // BUNDLE the same way? + bool candidates_allocated = IsCandidateAllocationDone(); + const ContentGroup* local_bundle_group = + local_description()->GetGroupByName(GROUP_TYPE_BUNDLE); + const ContentGroup* remote_bundle_group = + remote_description()->GetGroupByName(GROUP_TYPE_BUNDLE); + if (local_bundle_group && remote_bundle_group && + local_bundle_group->FirstContentName()) { + const std::string* content_name = local_bundle_group->FirstContentName(); + const ContentInfo* content = + local_description_->GetContentByName(*content_name); + ASSERT(content != NULL); + if (!SetSelectedProxy(content->name, local_bundle_group)) { + LOG(LS_WARNING) << "Failed to set up BUNDLE"; + return false; + } + + // If we weren't done gathering before, we might be done now, as a result + // of enabling mux. + LOG(LS_INFO) << "Enabling BUNDLE, bundling onto transport: " + << *content_name; + if (!candidates_allocated) { + MaybeCandidateAllocationDone(); + } + } else { + LOG(LS_INFO) << "No BUNDLE information, not bundling."; + } + return true; +} + +bool BaseSession::SetSelectedProxy(const std::string& content_name, + const ContentGroup* muxed_group) { + TransportProxy* selected_proxy = GetTransportProxy(content_name); + if (!selected_proxy) { + return false; + } + + ASSERT(selected_proxy->negotiated()); + for (TransportMap::iterator iter = transports_.begin(); + iter != transports_.end(); ++iter) { + // If content is part of the mux group, then repoint its proxy at the + // transport object that we have chosen to mux onto. If the proxy + // is already pointing at the right object, it will be a no-op. + if (muxed_group->HasContentName(iter->first) && + !iter->second->SetupMux(selected_proxy)) { + return false; + } + } + return true; +} + +void BaseSession::OnTransportCandidatesAllocationDone(Transport* transport) { + // TODO(juberti): This is a clunky way of processing the done signal. Instead, + // TransportProxy should receive the done signal directly, set its allocated + // flag internally, and then reissue the done signal to Session. + // Overall we should make TransportProxy receive *all* the signals from + // Transport, since this removes the need to manually iterate over all + // the transports, as is needed to make sure signals are handled properly + // when BUNDLEing. + // TODO(juberti): Per b/7998978, devs and QA are hitting this assert in ways + // that make it prohibitively difficult to run dbg builds. Disabled for now. + //ASSERT(!IsCandidateAllocationDone()); + for (TransportMap::iterator iter = transports_.begin(); + iter != transports_.end(); ++iter) { + if (iter->second->impl() == transport) { + iter->second->set_candidates_allocated(true); + } + } + MaybeCandidateAllocationDone(); +} + +bool BaseSession::IsCandidateAllocationDone() const { + for (TransportMap::const_iterator iter = transports_.begin(); + iter != transports_.end(); ++iter) { + if (!iter->second->candidates_allocated()) + return false; + } + return true; +} + +void BaseSession::MaybeCandidateAllocationDone() { + if (IsCandidateAllocationDone()) { + LOG(LS_INFO) << "Candidate gathering is complete."; + OnCandidatesAllocationDone(); + } +} + +void BaseSession::OnRoleConflict() { + if (role_switch_) { + LOG(LS_WARNING) << "Repeat of role conflict signal from Transport."; + return; + } + + role_switch_ = true; + for (TransportMap::iterator iter = transports_.begin(); + iter != transports_.end(); ++iter) { + // Role will be reverse of initial role setting. + IceRole role = initiator_ ? ICEROLE_CONTROLLED : ICEROLE_CONTROLLING; + iter->second->SetIceRole(role); + } +} + +void BaseSession::LogState(State old_state, State new_state) { + LOG(LS_INFO) << "Session:" << id() + << " Old state:" << StateToString(old_state) + << " New state:" << StateToString(new_state) + << " Type:" << content_type() + << " Transport:" << transport_type(); +} + +// static +bool BaseSession::GetTransportDescription(const SessionDescription* description, + const std::string& content_name, + TransportDescription* tdesc) { + if (!description || !tdesc) { + return false; + } + const TransportInfo* transport_info = + description->GetTransportInfoByName(content_name); + if (!transport_info) { + return false; + } + *tdesc = transport_info->description; + return true; +} + +void BaseSession::SignalNewDescription() { + ContentAction action; + ContentSource source; + if (!GetContentAction(&action, &source)) { + return; + } + if (source == CS_LOCAL) { + SignalNewLocalDescription(this, action); + } else { + SignalNewRemoteDescription(this, action); + } +} + +bool BaseSession::GetContentAction(ContentAction* action, + ContentSource* source) { + switch (state_) { + // new local description + case STATE_SENTINITIATE: + *action = CA_OFFER; + *source = CS_LOCAL; + break; + case STATE_SENTPRACCEPT: + *action = CA_PRANSWER; + *source = CS_LOCAL; + break; + case STATE_SENTACCEPT: + *action = CA_ANSWER; + *source = CS_LOCAL; + break; + // new remote description + case STATE_RECEIVEDINITIATE: + *action = CA_OFFER; + *source = CS_REMOTE; + break; + case STATE_RECEIVEDPRACCEPT: + *action = CA_PRANSWER; + *source = CS_REMOTE; + break; + case STATE_RECEIVEDACCEPT: + *action = CA_ANSWER; + *source = CS_REMOTE; + break; + default: + return false; + } + return true; +} + +void BaseSession::OnMessage(rtc::Message *pmsg) { + switch (pmsg->message_id) { + case MSG_TIMEOUT: + // Session timeout has occured. + SetError(ERROR_TIME, "Session timeout has occured."); + break; + + case MSG_STATE: + switch (state_) { + case STATE_SENTACCEPT: + case STATE_RECEIVEDACCEPT: + SetState(STATE_INPROGRESS); + break; + + default: + // Explicitly ignoring some states here. + break; + } + break; + } +} + +Session::Session(SessionManager* session_manager, + const std::string& local_name, + const std::string& initiator_name, + const std::string& sid, + const std::string& content_type, + SessionClient* client) + : BaseSession(session_manager->signaling_thread(), + session_manager->worker_thread(), + session_manager->port_allocator(), + sid, content_type, initiator_name == local_name) { + ASSERT(client != NULL); + session_manager_ = session_manager; + local_name_ = local_name; + initiator_name_ = initiator_name; + transport_parser_ = new P2PTransportParser(); + client_ = client; + initiate_acked_ = false; + current_protocol_ = PROTOCOL_HYBRID; +} + +Session::~Session() { + delete transport_parser_; +} + +bool Session::Initiate(const std::string& to, + const SessionDescription* sdesc) { + ASSERT(signaling_thread()->IsCurrent()); + SessionError error; + + // Only from STATE_INIT + if (state() != STATE_INIT) + return false; + + // Setup for signaling. + set_remote_name(to); + set_local_description(sdesc); + if (!CreateTransportProxies(GetEmptyTransportInfos(sdesc->contents()), + &error)) { + LOG(LS_ERROR) << "Could not create transports: " << error.text; + return false; + } + + if (!SendInitiateMessage(sdesc, &error)) { + LOG(LS_ERROR) << "Could not send initiate message: " << error.text; + return false; + } + + // We need to connect transport proxy and impl here so that we can process + // the TransportDescriptions. + SpeculativelyConnectAllTransportChannels(); + + PushdownTransportDescription(CS_LOCAL, CA_OFFER, NULL); + SetState(Session::STATE_SENTINITIATE); + return true; +} + +bool Session::Accept(const SessionDescription* sdesc) { + ASSERT(signaling_thread()->IsCurrent()); + + // Only if just received initiate + if (state() != STATE_RECEIVEDINITIATE) + return false; + + // Setup for signaling. + set_local_description(sdesc); + + SessionError error; + if (!SendAcceptMessage(sdesc, &error)) { + LOG(LS_ERROR) << "Could not send accept message: " << error.text; + return false; + } + // TODO(juberti): Add BUNDLE support to transport-info messages. + PushdownTransportDescription(CS_LOCAL, CA_ANSWER, NULL); + MaybeEnableMuxingSupport(); // Enable transport channel mux if supported. + SetState(Session::STATE_SENTACCEPT); + return true; +} + +bool Session::Reject(const std::string& reason) { + ASSERT(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; + + SessionError error; + if (!SendRejectMessage(reason, &error)) { + LOG(LS_ERROR) << "Could not send reject message: " << error.text; + return false; + } + + SetState(STATE_SENTREJECT); + return true; +} + +bool Session::TerminateWithReason(const std::string& reason) { + ASSERT(signaling_thread()->IsCurrent()); + + // Either side can terminate, at any time. + switch (state()) { + case STATE_SENTTERMINATE: + case STATE_RECEIVEDTERMINATE: + return false; + + case STATE_SENTREJECT: + case STATE_RECEIVEDREJECT: + // We don't need to send terminate if we sent or received a reject... + // it's implicit. + break; + + default: + SessionError error; + if (!SendTerminateMessage(reason, &error)) { + LOG(LS_ERROR) << "Could not send terminate message: " << error.text; + return false; + } + break; + } + + SetState(STATE_SENTTERMINATE); + return true; +} + +bool Session::SendInfoMessage(const XmlElements& elems, + const std::string& remote_name) { + ASSERT(signaling_thread()->IsCurrent()); + SessionError error; + if (!SendMessage(ACTION_SESSION_INFO, elems, remote_name, &error)) { + LOG(LS_ERROR) << "Could not send info message " << error.text; + return false; + } + return true; +} + +bool Session::SendDescriptionInfoMessage(const ContentInfos& contents) { + XmlElements elems; + WriteError write_error; + if (!WriteDescriptionInfo(current_protocol_, + contents, + GetContentParsers(), + &elems, &write_error)) { + LOG(LS_ERROR) << "Could not write description info message: " + << write_error.text; + return false; + } + SessionError error; + if (!SendMessage(ACTION_DESCRIPTION_INFO, elems, &error)) { + LOG(LS_ERROR) << "Could not send description info message: " + << error.text; + return false; + } + return true; +} + +TransportInfos Session::GetEmptyTransportInfos( + const ContentInfos& contents) const { + TransportInfos tinfos; + for (ContentInfos::const_iterator content = contents.begin(); + content != contents.end(); ++content) { + tinfos.push_back(TransportInfo(content->name, + TransportDescription(transport_type(), + std::string(), + std::string()))); + } + return tinfos; +} + +bool Session::OnRemoteCandidates( + const TransportInfos& tinfos, ParseError* error) { + for (TransportInfos::const_iterator tinfo = tinfos.begin(); + tinfo != tinfos.end(); ++tinfo) { + std::string str_error; + if (!BaseSession::OnRemoteCandidates( + tinfo->content_name, tinfo->description.candidates, &str_error)) { + return BadParse(str_error, error); + } + } + return true; +} + +bool Session::CreateTransportProxies(const TransportInfos& tinfos, + SessionError* error) { + for (TransportInfos::const_iterator tinfo = tinfos.begin(); + tinfo != tinfos.end(); ++tinfo) { + if (tinfo->description.transport_type != transport_type()) { + error->SetText("No supported transport in offer."); + return false; + } + + GetOrCreateTransportProxy(tinfo->content_name); + } + return true; +} + +TransportParserMap Session::GetTransportParsers() { + TransportParserMap parsers; + parsers[transport_type()] = transport_parser_; + return parsers; +} + +CandidateTranslatorMap Session::GetCandidateTranslators() { + CandidateTranslatorMap translators; + // NOTE: This technique makes it impossible to parse G-ICE + // candidates in session-initiate messages because the channels + // aren't yet created at that point. Since we don't use candidates + // in session-initiate messages, we should be OK. Once we switch to + // ICE, this translation shouldn't be necessary. + for (TransportMap::const_iterator iter = transport_proxies().begin(); + iter != transport_proxies().end(); ++iter) { + translators[iter->first] = iter->second; + } + return translators; +} + +ContentParserMap Session::GetContentParsers() { + ContentParserMap parsers; + parsers[content_type()] = client_; + // We need to be able parse both RTP-based and SCTP-based Jingle + // with the same client. + if (content_type() == NS_JINGLE_RTP) { + parsers[NS_JINGLE_DRAFT_SCTP] = client_; + } + return parsers; +} + +void Session::OnTransportRequestSignaling(Transport* transport) { + ASSERT(signaling_thread()->IsCurrent()); + TransportProxy* transproxy = GetTransportProxy(transport); + ASSERT(transproxy != NULL); + if (transproxy) { + // Reset candidate allocation status for the transport proxy. + transproxy->set_candidates_allocated(false); + } + SignalRequestSignaling(this); +} + +void Session::OnTransportConnecting(Transport* transport) { + // This is an indication that we should begin watching the writability + // state of the transport. + OnTransportWritable(transport); +} + +void Session::OnTransportWritable(Transport* transport) { + ASSERT(signaling_thread()->IsCurrent()); + + // If the transport is not writable, start a timer to make sure that it + // becomes writable within a reasonable amount of time. If it does not, we + // terminate since we can't actually send data. If the transport is writable, + // cancel the timer. Note that writability transitions may occur repeatedly + // during the lifetime of the session. + signaling_thread()->Clear(this, MSG_TIMEOUT); + if (transport->HasChannels() && !transport->writable()) { + signaling_thread()->PostDelayed( + session_manager_->session_timeout() * 1000, this, MSG_TIMEOUT); + } +} + +void Session::OnTransportProxyCandidatesReady(TransportProxy* transproxy, + const Candidates& candidates) { + ASSERT(signaling_thread()->IsCurrent()); + if (transproxy != NULL) { + if (initiator() && !initiate_acked_) { + // TODO: This is to work around server re-ordering + // messages. We send the candidates once the session-initiate + // is acked. Once we have fixed the server to guarantee message + // order, we can remove this case. + transproxy->AddUnsentCandidates(candidates); + } else { + if (!transproxy->negotiated()) { + transproxy->AddSentCandidates(candidates); + } + SessionError error; + if (!SendTransportInfoMessage(transproxy, candidates, &error)) { + LOG(LS_ERROR) << "Could not send transport info message: " + << error.text; + return; + } + } + } +} + +void Session::OnTransportSendError(Transport* transport, + const buzz::XmlElement* stanza, + const buzz::QName& name, + const std::string& type, + const std::string& text, + const buzz::XmlElement* extra_info) { + ASSERT(signaling_thread()->IsCurrent()); + SignalErrorMessage(this, stanza, name, type, text, extra_info); +} + +void Session::OnIncomingMessage(const SessionMessage& msg) { + ASSERT(signaling_thread()->IsCurrent()); + ASSERT(state() == STATE_INIT || msg.from == remote_name()); + + if (current_protocol_== PROTOCOL_HYBRID) { + if (msg.protocol == PROTOCOL_GINGLE) { + current_protocol_ = PROTOCOL_GINGLE; + } else { + current_protocol_ = PROTOCOL_JINGLE; + } + } + + bool valid = false; + MessageError error; + switch (msg.type) { + case ACTION_SESSION_INITIATE: + valid = OnInitiateMessage(msg, &error); + break; + case ACTION_SESSION_INFO: + valid = OnInfoMessage(msg); + break; + case ACTION_SESSION_ACCEPT: + valid = OnAcceptMessage(msg, &error); + break; + case ACTION_SESSION_REJECT: + valid = OnRejectMessage(msg, &error); + break; + case ACTION_SESSION_TERMINATE: + valid = OnTerminateMessage(msg, &error); + break; + case ACTION_TRANSPORT_INFO: + valid = OnTransportInfoMessage(msg, &error); + break; + case ACTION_TRANSPORT_ACCEPT: + valid = OnTransportAcceptMessage(msg, &error); + break; + case ACTION_DESCRIPTION_INFO: + valid = OnDescriptionInfoMessage(msg, &error); + break; + default: + valid = BadMessage(buzz::QN_STANZA_BAD_REQUEST, + "unknown session message type", + &error); + } + + if (valid) { + SendAcknowledgementMessage(msg.stanza); + } else { + SignalErrorMessage(this, msg.stanza, error.type, + "modify", error.text, NULL); + } +} + +void Session::OnIncomingResponse(const buzz::XmlElement* orig_stanza, + const buzz::XmlElement* response_stanza, + const SessionMessage& msg) { + ASSERT(signaling_thread()->IsCurrent()); + + if (msg.type == ACTION_SESSION_INITIATE) { + OnInitiateAcked(); + } +} + +void Session::OnInitiateAcked() { + // TODO: This is to work around server re-ordering + // messages. We send the candidates once the session-initiate + // is acked. Once we have fixed the server to guarantee message + // order, we can remove this case. + if (!initiate_acked_) { + initiate_acked_ = true; + SessionError error; + SendAllUnsentTransportInfoMessages(&error); + } +} + +void Session::OnFailedSend(const buzz::XmlElement* orig_stanza, + const buzz::XmlElement* error_stanza) { + ASSERT(signaling_thread()->IsCurrent()); + + SessionMessage msg; + ParseError parse_error; + if (!ParseSessionMessage(orig_stanza, &msg, &parse_error)) { + LOG(LS_ERROR) << "Error parsing failed send: " << parse_error.text + << ":" << orig_stanza; + return; + } + + // If the error is a session redirect, call OnRedirectError, which will + // continue the session with a new remote JID. + SessionRedirect redirect; + if (FindSessionRedirect(error_stanza, &redirect)) { + SessionError error; + if (!OnRedirectError(redirect, &error)) { + // TODO: Should we send a message back? The standard + // says nothing about it. + std::ostringstream desc; + desc << "Failed to redirect: " << error.text; + LOG(LS_ERROR) << desc.str(); + SetError(ERROR_RESPONSE, desc.str()); + } + return; + } + + std::string error_type = "cancel"; + + const buzz::XmlElement* error = error_stanza->FirstNamed(buzz::QN_ERROR); + if (error) { + error_type = error->Attr(buzz::QN_TYPE); + + LOG(LS_ERROR) << "Session error:\n" << error->Str() << "\n" + << "in response to:\n" << orig_stanza->Str(); + } else { + // don't crash if is missing + LOG(LS_ERROR) << "Session error without element, ignoring"; + return; + } + + if (msg.type == ACTION_TRANSPORT_INFO) { + // Transport messages frequently generate errors because they are sent right + // when we detect a network failure. For that reason, we ignore such + // errors, because if we do not establish writability again, we will + // terminate anyway. The exceptions are transport-specific error tags, + // which we pass on to the respective transport. + } else if ((error_type != "continue") && (error_type != "wait")) { + // We do not set an error if the other side said it is okay to continue + // (possibly after waiting). These errors can be ignored. + SetError(ERROR_RESPONSE, ""); + } +} + +bool Session::OnInitiateMessage(const SessionMessage& msg, + MessageError* error) { + if (!CheckState(STATE_INIT, error)) + return false; + + SessionInitiate init; + if (!ParseSessionInitiate(msg.protocol, msg.action_elem, + GetContentParsers(), GetTransportParsers(), + GetCandidateTranslators(), + &init, error)) + return false; + + SessionError session_error; + if (!CreateTransportProxies(init.transports, &session_error)) { + return BadMessage(buzz::QN_STANZA_NOT_ACCEPTABLE, + session_error.text, error); + } + + set_remote_name(msg.from); + set_initiator_name(msg.initiator); + set_remote_description(new SessionDescription(init.ClearContents(), + init.transports, + init.groups)); + // Updating transport with TransportDescription. + PushdownTransportDescription(CS_REMOTE, CA_OFFER, NULL); + SetState(STATE_RECEIVEDINITIATE); + + // Users of Session may listen to state change and call Reject(). + if (state() != STATE_SENTREJECT) { + if (!OnRemoteCandidates(init.transports, error)) + return false; + + // TODO(juberti): Auto-generate and push down the local transport answer. + // This is necessary for trickling to work with RFC 5245 ICE. + } + return true; +} + +bool Session::OnAcceptMessage(const SessionMessage& msg, MessageError* error) { + if (!CheckState(STATE_SENTINITIATE, error)) + return false; + + SessionAccept accept; + if (!ParseSessionAccept(msg.protocol, msg.action_elem, + GetContentParsers(), GetTransportParsers(), + GetCandidateTranslators(), + &accept, error)) { + return false; + } + + // If we get an accept, we can assume the initiate has been + // received, even if we haven't gotten an IQ response. + OnInitiateAcked(); + + set_remote_description(new SessionDescription(accept.ClearContents(), + accept.transports, + accept.groups)); + // Updating transport with TransportDescription. + PushdownTransportDescription(CS_REMOTE, CA_ANSWER, NULL); + MaybeEnableMuxingSupport(); // Enable transport channel mux if supported. + SetState(STATE_RECEIVEDACCEPT); + + if (!OnRemoteCandidates(accept.transports, error)) + return false; + + return true; +} + +bool Session::OnRejectMessage(const SessionMessage& msg, MessageError* error) { + if (!CheckState(STATE_SENTINITIATE, error)) + return false; + + SetState(STATE_RECEIVEDREJECT); + return true; +} + +bool Session::OnInfoMessage(const SessionMessage& msg) { + SignalInfoMessage(this, msg.action_elem); + return true; +} + +bool Session::OnTerminateMessage(const SessionMessage& msg, + MessageError* error) { + SessionTerminate term; + if (!ParseSessionTerminate(msg.protocol, msg.action_elem, &term, error)) + return false; + + SignalReceivedTerminateReason(this, term.reason); + if (term.debug_reason != buzz::STR_EMPTY) { + LOG(LS_VERBOSE) << "Received error on call: " << term.debug_reason; + } + + SetState(STATE_RECEIVEDTERMINATE); + return true; +} + +bool Session::OnTransportInfoMessage(const SessionMessage& msg, + MessageError* error) { + TransportInfos tinfos; + if (!ParseTransportInfos(msg.protocol, msg.action_elem, + initiator_description()->contents(), + GetTransportParsers(), GetCandidateTranslators(), + &tinfos, error)) + return false; + + if (!OnRemoteCandidates(tinfos, error)) + return false; + + return true; +} + +bool Session::OnTransportAcceptMessage(const SessionMessage& msg, + MessageError* error) { + // TODO: Currently here only for compatibility with + // Gingle 1.1 clients (notably, Google Voice). + return true; +} + +bool Session::OnDescriptionInfoMessage(const SessionMessage& msg, + MessageError* error) { + if (!CheckState(STATE_INPROGRESS, error)) + return false; + + DescriptionInfo description_info; + if (!ParseDescriptionInfo(msg.protocol, msg.action_elem, + GetContentParsers(), GetTransportParsers(), + GetCandidateTranslators(), + &description_info, error)) { + return false; + } + + ContentInfos& updated_contents = description_info.contents; + + // TODO: Currently, reflector sends back + // video stream updates even for an audio-only call, which causes + // this to fail. Put this back once reflector is fixed. + // + // ContentInfos::iterator it; + // First, ensure all updates are valid before modifying remote_description_. + // for (it = updated_contents.begin(); it != updated_contents.end(); ++it) { + // if (remote_description()->GetContentByName(it->name) == NULL) { + // return false; + // } + // } + + // TODO: We used to replace contents from an update, but + // that no longer works with partial updates. We need to figure out + // a way to merge patial updates into contents. For now, users of + // Session should listen to SignalRemoteDescriptionUpdate and handle + // updates. They should not expect remote_description to be the + // latest value. + // + // for (it = updated_contents.begin(); it != updated_contents.end(); ++it) { + // remote_description()->RemoveContentByName(it->name); + // remote_description()->AddContent(it->name, it->type, it->description); + // } + // } + + SignalRemoteDescriptionUpdate(this, updated_contents); + return true; +} + +bool BareJidsEqual(const std::string& name1, + const std::string& name2) { + buzz::Jid jid1(name1); + buzz::Jid jid2(name2); + + return jid1.IsValid() && jid2.IsValid() && jid1.BareEquals(jid2); +} + +bool Session::OnRedirectError(const SessionRedirect& redirect, + SessionError* error) { + MessageError message_error; + if (!CheckState(STATE_SENTINITIATE, &message_error)) { + return BadWrite(message_error.text, error); + } + + if (!BareJidsEqual(remote_name(), redirect.target)) + return BadWrite("Redirection not allowed: must be the same bare jid.", + error); + + // When we receive a redirect, we point the session at the new JID + // and resend the candidates. + set_remote_name(redirect.target); + return (SendInitiateMessage(local_description(), error) && + ResendAllTransportInfoMessages(error)); +} + +bool Session::CheckState(State expected, MessageError* error) { + if (state() != expected) { + // The server can deliver messages out of order/repeated for various + // reasons. For example, if the server does not recive our iq response, + // it could assume that the iq it sent was lost, and will then send + // it again. Ideally, we should implement reliable messaging with + // duplicate elimination. + return BadMessage(buzz::QN_STANZA_NOT_ALLOWED, + "message not allowed in current state", + error); + } + return true; +} + +void Session::SetError(Error error, const std::string& error_desc) { + BaseSession::SetError(error, error_desc); + if (error != ERROR_NONE) + signaling_thread()->Post(this, MSG_ERROR); +} + +void Session::OnMessage(rtc::Message* pmsg) { + // preserve this because BaseSession::OnMessage may modify it + State orig_state = state(); + + BaseSession::OnMessage(pmsg); + + switch (pmsg->message_id) { + case MSG_ERROR: + TerminateWithReason(STR_TERMINATE_ERROR); + break; + + case MSG_STATE: + switch (orig_state) { + case STATE_SENTREJECT: + case STATE_RECEIVEDREJECT: + // Assume clean termination. + Terminate(); + break; + + case STATE_SENTTERMINATE: + case STATE_RECEIVEDTERMINATE: + session_manager_->DestroySession(this); + break; + + default: + // Explicitly ignoring some states here. + break; + } + break; + } +} + +bool Session::SendInitiateMessage(const SessionDescription* sdesc, + SessionError* error) { + SessionInitiate init; + init.contents = sdesc->contents(); + init.transports = GetEmptyTransportInfos(init.contents); + init.groups = sdesc->groups(); + return SendMessage(ACTION_SESSION_INITIATE, init, error); +} + +bool Session::WriteSessionAction( + SignalingProtocol protocol, const SessionInitiate& init, + XmlElements* elems, WriteError* error) { + return WriteSessionInitiate(protocol, init.contents, init.transports, + GetContentParsers(), GetTransportParsers(), + GetCandidateTranslators(), init.groups, + elems, error); +} + +bool Session::SendAcceptMessage(const SessionDescription* sdesc, + SessionError* error) { + XmlElements elems; + if (!WriteSessionAccept(current_protocol_, + sdesc->contents(), + GetEmptyTransportInfos(sdesc->contents()), + GetContentParsers(), GetTransportParsers(), + GetCandidateTranslators(), sdesc->groups(), + &elems, error)) { + return false; + } + return SendMessage(ACTION_SESSION_ACCEPT, elems, error); +} + +bool Session::SendRejectMessage(const std::string& reason, + SessionError* error) { + SessionTerminate term(reason); + return SendMessage(ACTION_SESSION_REJECT, term, error); +} + +bool Session::SendTerminateMessage(const std::string& reason, + SessionError* error) { + SessionTerminate term(reason); + return SendMessage(ACTION_SESSION_TERMINATE, term, error); +} + +bool Session::WriteSessionAction(SignalingProtocol protocol, + const SessionTerminate& term, + XmlElements* elems, WriteError* error) { + WriteSessionTerminate(protocol, term, elems); + return true; +} + +bool Session::SendTransportInfoMessage(const TransportInfo& tinfo, + SessionError* error) { + return SendMessage(ACTION_TRANSPORT_INFO, tinfo, error); +} + +bool Session::SendTransportInfoMessage(const TransportProxy* transproxy, + const Candidates& candidates, + SessionError* error) { + return SendTransportInfoMessage(TransportInfo(transproxy->content_name(), + TransportDescription(transproxy->type(), std::vector(), + std::string(), std::string(), ICEMODE_FULL, + CONNECTIONROLE_NONE, NULL, candidates)), error); +} + +bool Session::WriteSessionAction(SignalingProtocol protocol, + const TransportInfo& tinfo, + XmlElements* elems, WriteError* error) { + TransportInfos tinfos; + tinfos.push_back(tinfo); + return WriteTransportInfos(protocol, tinfos, + GetTransportParsers(), GetCandidateTranslators(), + elems, error); +} + +bool Session::ResendAllTransportInfoMessages(SessionError* error) { + for (TransportMap::const_iterator iter = transport_proxies().begin(); + iter != transport_proxies().end(); ++iter) { + TransportProxy* transproxy = iter->second; + if (transproxy->sent_candidates().size() > 0) { + if (!SendTransportInfoMessage( + transproxy, transproxy->sent_candidates(), error)) { + LOG(LS_ERROR) << "Could not resend transport info messages: " + << error->text; + return false; + } + transproxy->ClearSentCandidates(); + } + } + return true; +} + +bool Session::SendAllUnsentTransportInfoMessages(SessionError* error) { + for (TransportMap::const_iterator iter = transport_proxies().begin(); + iter != transport_proxies().end(); ++iter) { + TransportProxy* transproxy = iter->second; + if (transproxy->unsent_candidates().size() > 0) { + if (!SendTransportInfoMessage( + transproxy, transproxy->unsent_candidates(), error)) { + LOG(LS_ERROR) << "Could not send unsent transport info messages: " + << error->text; + return false; + } + transproxy->ClearUnsentCandidates(); + } + } + return true; +} + +bool Session::SendMessage(ActionType type, const XmlElements& action_elems, + SessionError* error) { + return SendMessage(type, action_elems, remote_name(), error); +} + +bool Session::SendMessage(ActionType type, const XmlElements& action_elems, + const std::string& remote_name, SessionError* error) { + rtc::scoped_ptr stanza( + new buzz::XmlElement(buzz::QN_IQ)); + + SessionMessage msg(current_protocol_, type, id(), initiator_name()); + msg.to = remote_name; + WriteSessionMessage(msg, action_elems, stanza.get()); + + SignalOutgoingMessage(this, stanza.get()); + return true; +} + +template +bool Session::SendMessage(ActionType type, const Action& action, + SessionError* error) { + rtc::scoped_ptr stanza( + new buzz::XmlElement(buzz::QN_IQ)); + if (!WriteActionMessage(type, action, stanza.get(), error)) + return false; + + SignalOutgoingMessage(this, stanza.get()); + return true; +} + +template +bool Session::WriteActionMessage(ActionType type, const Action& action, + buzz::XmlElement* stanza, + WriteError* error) { + if (current_protocol_ == PROTOCOL_HYBRID) { + if (!WriteActionMessage(PROTOCOL_JINGLE, type, action, stanza, error)) + return false; + if (!WriteActionMessage(PROTOCOL_GINGLE, type, action, stanza, error)) + return false; + } else { + if (!WriteActionMessage(current_protocol_, type, action, stanza, error)) + return false; + } + return true; +} + +template +bool Session::WriteActionMessage(SignalingProtocol protocol, + ActionType type, const Action& action, + buzz::XmlElement* stanza, WriteError* error) { + XmlElements action_elems; + if (!WriteSessionAction(protocol, action, &action_elems, error)) + return false; + + SessionMessage msg(protocol, type, id(), initiator_name()); + msg.to = remote_name(); + + WriteSessionMessage(msg, action_elems, stanza); + return true; +} + +void Session::SendAcknowledgementMessage(const buzz::XmlElement* stanza) { + rtc::scoped_ptr ack( + new buzz::XmlElement(buzz::QN_IQ)); + ack->SetAttr(buzz::QN_TO, remote_name()); + ack->SetAttr(buzz::QN_ID, stanza->Attr(buzz::QN_ID)); + ack->SetAttr(buzz::QN_TYPE, "result"); + + SignalOutgoingMessage(this, ack.get()); +} + +} // namespace cricket diff --git a/webrtc/p2p/base/session.h b/webrtc/p2p/base/session.h new file mode 100644 index 000000000..f5eaf413a --- /dev/null +++ b/webrtc/p2p/base/session.h @@ -0,0 +1,730 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_SESSION_H_ +#define WEBRTC_P2P_BASE_SESSION_H_ + +#include +#include +#include +#include + +#include "webrtc/p2p/base/parsing.h" +#include "webrtc/p2p/base/port.h" +#include "webrtc/p2p/base/sessionclient.h" +#include "webrtc/p2p/base/sessionmanager.h" +#include "webrtc/p2p/base/sessionmessages.h" +#include "webrtc/p2p/base/transport.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/base/refcount.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/scoped_ref_ptr.h" +#include "webrtc/base/socketaddress.h" + +namespace cricket { + +class BaseSession; +class P2PTransportChannel; +class Transport; +class TransportChannel; +class TransportChannelProxy; +class TransportChannelImpl; + +typedef rtc::RefCountedObject > +TransportWrapper; + +// Used for errors that will send back a specific error message to the +// remote peer. We add "type" to the errors because it's needed for +// SignalErrorMessage. +struct MessageError : ParseError { + buzz::QName type; + + // if unset, assume type is a parse error + MessageError() : ParseError(), type(buzz::QN_STANZA_BAD_REQUEST) {} + + void SetType(const buzz::QName type) { + this->type = type; + } +}; + +// Used for errors that may be returned by public session methods that +// can fail. +// TODO: Use this error in Session::Initiate and +// Session::Accept. +struct SessionError : WriteError { +}; + +// Bundles a Transport and ChannelMap together. ChannelMap is used to +// create transport channels before receiving or sending a session +// initiate, and for speculatively connecting channels. Previously, a +// session had one ChannelMap and transport. Now, with multiple +// transports per session, we need multiple ChannelMaps as well. + +typedef std::map ChannelMap; + +class TransportProxy : public sigslot::has_slots<>, + public CandidateTranslator { + public: + TransportProxy( + rtc::Thread* worker_thread, + const std::string& sid, + const std::string& content_name, + TransportWrapper* transport) + : worker_thread_(worker_thread), + sid_(sid), + content_name_(content_name), + transport_(transport), + connecting_(false), + negotiated_(false), + sent_candidates_(false), + candidates_allocated_(false), + local_description_set_(false), + remote_description_set_(false) { + transport_->get()->SignalCandidatesReady.connect( + this, &TransportProxy::OnTransportCandidatesReady); + } + ~TransportProxy(); + + const std::string& content_name() const { return content_name_; } + // TODO(juberti): It's not good form to expose the object you're wrapping, + // since callers can mutate it. Can we make this return a const Transport*? + Transport* impl() const { return transport_->get(); } + + const std::string& type() const; + bool negotiated() const { return negotiated_; } + const Candidates& sent_candidates() const { return sent_candidates_; } + const Candidates& unsent_candidates() const { return unsent_candidates_; } + bool candidates_allocated() const { return candidates_allocated_; } + void set_candidates_allocated(bool allocated) { + candidates_allocated_ = allocated; + } + + TransportChannel* GetChannel(int component); + TransportChannel* CreateChannel(const std::string& channel_name, + int component); + bool HasChannel(int component); + void DestroyChannel(int component); + + void AddSentCandidates(const Candidates& candidates); + void AddUnsentCandidates(const Candidates& candidates); + void ClearSentCandidates() { sent_candidates_.clear(); } + void ClearUnsentCandidates() { unsent_candidates_.clear(); } + + // Start the connection process for any channels, creating impls if needed. + void ConnectChannels(); + // Hook up impls to the proxy channels. Doesn't change connect state. + void CompleteNegotiation(); + + // Mux this proxy onto the specified proxy's transport. + bool SetupMux(TransportProxy* proxy); + + // Simple functions that thunk down to the same functions on Transport. + void SetIceRole(IceRole role); + void SetIdentity(rtc::SSLIdentity* identity); + bool SetLocalTransportDescription(const TransportDescription& description, + ContentAction action, + std::string* error_desc); + bool SetRemoteTransportDescription(const TransportDescription& description, + ContentAction action, + std::string* error_desc); + void OnSignalingReady(); + bool OnRemoteCandidates(const Candidates& candidates, std::string* error); + + // CandidateTranslator methods. + virtual bool GetChannelNameFromComponent( + int component, std::string* channel_name) const; + virtual bool GetComponentFromChannelName( + const std::string& channel_name, int* component) const; + + // Called when a transport signals that it has new candidates. + void OnTransportCandidatesReady(cricket::Transport* transport, + const Candidates& candidates) { + SignalCandidatesReady(this, candidates); + } + + bool local_description_set() const { + return local_description_set_; + } + bool remote_description_set() const { + return remote_description_set_; + } + + // Handles sending of ready candidates and receiving of remote candidates. + sigslot::signal2&> SignalCandidatesReady; + + private: + TransportChannelProxy* GetChannelProxy(int component) const; + TransportChannelProxy* GetChannelProxyByName(const std::string& name) const; + + TransportChannelImpl* GetOrCreateChannelProxyImpl(int component); + TransportChannelImpl* GetOrCreateChannelProxyImpl_w(int component); + + // Manipulators of transportchannelimpl in channel proxy. + void SetupChannelProxy(int component, + TransportChannelProxy* proxy); + void SetupChannelProxy_w(int component, + TransportChannelProxy* proxy); + void ReplaceChannelProxyImpl(TransportChannelProxy* proxy, + TransportChannelImpl* impl); + void ReplaceChannelProxyImpl_w(TransportChannelProxy* proxy, + TransportChannelImpl* impl); + + rtc::Thread* const worker_thread_; + const std::string sid_; + const std::string content_name_; + rtc::scoped_refptr transport_; + bool connecting_; + bool negotiated_; + ChannelMap channels_; + Candidates sent_candidates_; + Candidates unsent_candidates_; + bool candidates_allocated_; + bool local_description_set_; + bool remote_description_set_; +}; + +typedef std::map TransportMap; + +// Statistics for all the transports of this session. +typedef std::map TransportStatsMap; +typedef std::map ProxyTransportMap; + +struct SessionStats { + ProxyTransportMap proxy_to_transport; + TransportStatsMap transport_stats; +}; + +// A BaseSession manages general session state. This includes negotiation +// of both the application-level and network-level protocols: the former +// defines what will be sent and the latter defines how it will be sent. Each +// network-level protocol is represented by a Transport object. Each Transport +// participates in the network-level negotiation. The individual streams of +// packets are represented by TransportChannels. The application-level protocol +// is represented by SessionDecription objects. +class BaseSession : public sigslot::has_slots<>, + public rtc::MessageHandler { + public: + enum { + MSG_TIMEOUT = 0, + MSG_ERROR, + MSG_STATE, + }; + + enum State { + STATE_INIT = 0, + STATE_SENTINITIATE, // sent initiate, waiting for Accept or Reject + STATE_RECEIVEDINITIATE, // received an initiate. Call Accept or Reject + STATE_SENTPRACCEPT, // sent provisional Accept + STATE_SENTACCEPT, // sent accept. begin connecting transport + STATE_RECEIVEDPRACCEPT, // received provisional Accept, waiting for Accept + STATE_RECEIVEDACCEPT, // received accept. begin connecting transport + 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 + STATE_DEINIT, // session is being destroyed + }; + + enum Error { + ERROR_NONE = 0, // no error + ERROR_TIME = 1, // no response to signaling + ERROR_RESPONSE = 2, // error during signaling + ERROR_NETWORK = 3, // network error, could not allocate network resources + ERROR_CONTENT = 4, // channel errors in SetLocalContent/SetRemoteContent + ERROR_TRANSPORT = 5, // transport error of some kind + }; + + // Convert State to a readable string. + static std::string StateToString(State state); + + BaseSession(rtc::Thread* signaling_thread, + rtc::Thread* worker_thread, + PortAllocator* port_allocator, + const std::string& sid, + const std::string& content_type, + bool initiator); + virtual ~BaseSession(); + + // These are const to allow them to be called from const methods. + rtc::Thread* signaling_thread() const { return signaling_thread_; } + rtc::Thread* worker_thread() const { return worker_thread_; } + PortAllocator* port_allocator() const { return port_allocator_; } + + // The ID of this session. + const std::string& id() const { return sid_; } + + // TODO(juberti): This data is largely redundant, as it can now be obtained + // from local/remote_description(). Remove these functions and members. + // Returns the XML namespace identifying the type of this session. + const std::string& content_type() const { return content_type_; } + // Returns the XML namespace identifying the transport used for this session. + const std::string& transport_type() const { return transport_type_; } + + // Indicates whether we initiated this session. + bool initiator() const { return initiator_; } + + // Returns the application-level description given by our client. + // If we are the recipient, this will be NULL until we send an accept. + const SessionDescription* local_description() const; + + // Returns the application-level description given by the other client. + // If we are the initiator, this will be NULL until we receive an accept. + const SessionDescription* remote_description() const; + + SessionDescription* remote_description(); + + // Takes ownership of SessionDescription* + void set_local_description(const SessionDescription* sdesc); + + // Takes ownership of SessionDescription* + void set_remote_description(SessionDescription* sdesc); + + const SessionDescription* initiator_description() const; + + // Returns the current state of the session. See the enum above for details. + // Each time the state changes, we will fire this signal. + State state() const { return state_; } + sigslot::signal2 SignalState; + + // Returns the last error in the session. See the enum above for details. + // Each time the an error occurs, we will fire this signal. + Error error() const { return error_; } + const std::string& error_desc() const { return error_desc_; } + sigslot::signal2 SignalError; + + // Updates the state, signaling if necessary. + virtual void SetState(State state); + + // Updates the error state, signaling if necessary. + // TODO(ronghuawu): remove the SetError method that doesn't take |error_desc|. + virtual void SetError(Error error, const std::string& error_desc); + + // Fired when the remote description is updated, with the updated + // contents. + sigslot::signal2 + SignalRemoteDescriptionUpdate; + + // Fired when SetState is called (regardless if there's a state change), which + // indicates the session description might have be updated. + sigslot::signal2 SignalNewLocalDescription; + + // Fired when SetState is called (regardless if there's a state change), which + // indicates the session description might have be updated. + sigslot::signal2 SignalNewRemoteDescription; + + // Returns the transport that has been negotiated or NULL if + // negotiation is still in progress. + virtual Transport* GetTransport(const std::string& content_name); + + // Creates a new channel with the given names. This method may be called + // immediately after creating the session. However, the actual + // implementation may not be fixed until transport negotiation completes. + // This will usually be called from the worker thread, but that + // shouldn't be an issue since the main thread will be blocked in + // Send when doing so. + virtual TransportChannel* CreateChannel(const std::string& content_name, + const std::string& channel_name, + int component); + + // Returns the channel with the given names. + virtual TransportChannel* GetChannel(const std::string& content_name, + int component); + + // Destroys the channel with the given names. + // This will usually be called from the worker thread, but that + // shouldn't be an issue since the main thread will be blocked in + // Send when doing so. + virtual void DestroyChannel(const std::string& content_name, + int component); + + // Returns stats for all channels of all transports. + // This avoids exposing the internal structures used to track them. + virtual bool GetStats(SessionStats* stats); + + rtc::SSLIdentity* identity() { return identity_; } + + protected: + // Specifies the identity to use in this session. + bool SetIdentity(rtc::SSLIdentity* identity); + + bool PushdownTransportDescription(ContentSource source, + ContentAction action, + std::string* error_desc); + void set_initiator(bool initiator) { initiator_ = initiator; } + + const TransportMap& transport_proxies() const { return transports_; } + // Get a TransportProxy by content_name or transport. NULL if not found. + TransportProxy* GetTransportProxy(const std::string& content_name); + TransportProxy* GetTransportProxy(const Transport* transport); + TransportProxy* GetFirstTransportProxy(); + void DestroyTransportProxy(const std::string& content_name); + // TransportProxy is owned by session. Return proxy just for convenience. + TransportProxy* GetOrCreateTransportProxy(const std::string& content_name); + // Creates the actual transport object. Overridable for testing. + virtual Transport* CreateTransport(const std::string& content_name); + + void OnSignalingReady(); + void SpeculativelyConnectAllTransportChannels(); + // Helper method to provide remote candidates to the transport. + bool OnRemoteCandidates(const std::string& content_name, + const Candidates& candidates, + std::string* error); + + // This method will mux transport channels by content_name. + // First content is used for muxing. + bool MaybeEnableMuxingSupport(); + + // Called when a transport requests signaling. + virtual void OnTransportRequestSignaling(Transport* transport) { + } + + // Called when the first channel of a transport begins connecting. We use + // this to start a timer, to make sure that the connection completes in a + // reasonable amount of time. + virtual void OnTransportConnecting(Transport* transport) { + } + + // Called when a transport changes its writable state. We track this to make + // sure that the transport becomes writable within a reasonable amount of + // time. If this does not occur, we signal an error. + virtual void OnTransportWritable(Transport* transport) { + } + virtual void OnTransportReadable(Transport* transport) { + } + + // Called when a transport has found its steady-state connections. + virtual void OnTransportCompleted(Transport* transport) { + } + + // Called when a transport has failed permanently. + virtual void OnTransportFailed(Transport* transport) { + } + + // Called when a transport signals that it has new candidates. + virtual void OnTransportProxyCandidatesReady(TransportProxy* proxy, + const Candidates& candidates) { + } + + // Called when a transport signals that it found an error in an incoming + // message. + virtual void OnTransportSendError(Transport* transport, + const buzz::XmlElement* stanza, + const buzz::QName& name, + const std::string& type, + const std::string& text, + const buzz::XmlElement* extra_info) { + } + + virtual void OnTransportRouteChange( + Transport* transport, + int component, + const cricket::Candidate& remote_candidate) { + } + + virtual void OnTransportCandidatesAllocationDone(Transport* transport); + + // Called when all transport channels allocated required candidates. + // This method should be used as an indication of candidates gathering process + // is completed and application can now send local candidates list to remote. + virtual void OnCandidatesAllocationDone() { + } + + // Handles the ice role change callback from Transport. This must be + // propagated to all the transports. + virtual void OnRoleConflict(); + + // Handles messages posted to us. + virtual void OnMessage(rtc::Message *pmsg); + + protected: + State state_; + Error error_; + std::string error_desc_; + + private: + // Helper methods to push local and remote transport descriptions. + bool PushdownLocalTransportDescription( + const SessionDescription* sdesc, ContentAction action, + std::string* error_desc); + bool PushdownRemoteTransportDescription( + const SessionDescription* sdesc, ContentAction action, + std::string* error_desc); + + bool IsCandidateAllocationDone() const; + void MaybeCandidateAllocationDone(); + + // This method will delete the Transport and TransportChannelImpls and + // replace those with the selected Transport objects. Selection is done + // based on the content_name and in this case first MediaContent information + // is used for mux. + bool SetSelectedProxy(const std::string& content_name, + const ContentGroup* muxed_group); + // Log session state. + void LogState(State old_state, State new_state); + + // Returns true and the TransportInfo of the given |content_name| + // from |description|. Returns false if it's not available. + static bool GetTransportDescription(const SessionDescription* description, + const std::string& content_name, + TransportDescription* info); + + // Fires the new description signal according to the current state. + void SignalNewDescription(); + + // Gets the ContentAction and ContentSource according to the session state. + bool GetContentAction(ContentAction* action, ContentSource* source); + + rtc::Thread* const signaling_thread_; + rtc::Thread* const worker_thread_; + PortAllocator* const port_allocator_; + const std::string sid_; + const std::string content_type_; + const std::string transport_type_; + bool initiator_; + rtc::SSLIdentity* identity_; + rtc::scoped_ptr local_description_; + rtc::scoped_ptr remote_description_; + uint64 ice_tiebreaker_; + // This flag will be set to true after the first role switch. This flag + // will enable us to stop any role switch during the call. + bool role_switch_; + TransportMap transports_; +}; + +// A specific Session created by the SessionManager, using XMPP for protocol. +class Session : public BaseSession { + public: + // Returns the manager that created and owns this session. + SessionManager* session_manager() const { return session_manager_; } + + // Returns the client that is handling the application data of this session. + SessionClient* client() const { return client_; } + + // Returns the JID of this client. + const std::string& local_name() const { return local_name_; } + + // Returns the JID of the other peer in this session. + const std::string& remote_name() const { return remote_name_; } + + // Set the JID of the other peer in this session. + // Typically the remote_name_ is set when the session is initiated. + // However, sometimes (e.g when a proxy is used) the peer name is + // known after the BaseSession has been initiated and it must be updated + // explicitly. + void set_remote_name(const std::string& name) { remote_name_ = name; } + + // Set the JID of the initiator of this session. Allows for the overriding + // of the initiator to be a third-party, eg. the MUC JID when creating p2p + // sessions. + void set_initiator_name(const std::string& name) { initiator_name_ = name; } + + // Indicates the JID of the entity who initiated this session. + // In special cases, may be different than both local_name and remote_name. + const std::string& initiator_name() const { return initiator_name_; } + + SignalingProtocol current_protocol() const { return current_protocol_; } + + void set_current_protocol(SignalingProtocol protocol) { + current_protocol_ = protocol; + } + + // Updates the error state, signaling if necessary. + virtual void SetError(Error error, const std::string& error_desc); + + // When the session needs to send signaling messages, it beings by requesting + // signaling. The client should handle this by calling OnSignalingReady once + // it is ready to send the messages. + // (These are called only by SessionManager.) + sigslot::signal1 SignalRequestSignaling; + void OnSignalingReady() { BaseSession::OnSignalingReady(); } + + // Takes ownership of session description. + // TODO: Add an error argument to pass back to the caller. + bool Initiate(const std::string& to, + const SessionDescription* sdesc); + + // When we receive an initiate, we create a session in the + // RECEIVEDINITIATE state and respond by accepting or rejecting. + // Takes ownership of session description. + // TODO: Add an error argument to pass back to the caller. + bool Accept(const SessionDescription* sdesc); + bool Reject(const std::string& reason); + bool Terminate() { + return TerminateWithReason(STR_TERMINATE_SUCCESS); + } + bool TerminateWithReason(const std::string& reason); + // Fired whenever we receive a terminate message along with a reason + sigslot::signal2 SignalReceivedTerminateReason; + + // The two clients in the session may also send one another + // arbitrary XML messages, which are called "info" messages. Sending + // takes ownership of the given elements. The signal does not; the + // parent element will be deleted after the signal. + bool SendInfoMessage(const XmlElements& elems, + const std::string& remote_name); + bool SendDescriptionInfoMessage(const ContentInfos& contents); + sigslot::signal2 SignalInfoMessage; + + private: + // Creates or destroys a session. (These are called only SessionManager.) + Session(SessionManager *session_manager, + const std::string& local_name, const std::string& initiator_name, + const std::string& sid, const std::string& content_type, + SessionClient* client); + ~Session(); + // For each transport info, create a transport proxy. Can fail for + // incompatible transport types. + bool CreateTransportProxies(const TransportInfos& tinfos, + SessionError* error); + bool OnRemoteCandidates(const TransportInfos& tinfos, + ParseError* error); + // Returns a TransportInfo without candidates for each content name. + // Uses the transport_type_ of the session. + TransportInfos GetEmptyTransportInfos(const ContentInfos& contents) const; + + // Maps passed to serialization functions. + TransportParserMap GetTransportParsers(); + ContentParserMap GetContentParsers(); + CandidateTranslatorMap GetCandidateTranslators(); + + virtual void OnTransportRequestSignaling(Transport* transport); + virtual void OnTransportConnecting(Transport* transport); + virtual void OnTransportWritable(Transport* transport); + virtual void OnTransportProxyCandidatesReady(TransportProxy* proxy, + const Candidates& candidates); + virtual void OnTransportSendError(Transport* transport, + const buzz::XmlElement* stanza, + const buzz::QName& name, + const std::string& type, + const std::string& text, + const buzz::XmlElement* extra_info); + virtual void OnMessage(rtc::Message *pmsg); + + // Send various kinds of session messages. + bool SendInitiateMessage(const SessionDescription* sdesc, + SessionError* error); + bool SendAcceptMessage(const SessionDescription* sdesc, SessionError* error); + bool SendRejectMessage(const std::string& reason, SessionError* error); + bool SendTerminateMessage(const std::string& reason, SessionError* error); + bool SendTransportInfoMessage(const TransportInfo& tinfo, + SessionError* error); + bool SendTransportInfoMessage(const TransportProxy* transproxy, + const Candidates& candidates, + SessionError* error); + + bool ResendAllTransportInfoMessages(SessionError* error); + bool SendAllUnsentTransportInfoMessages(SessionError* error); + + // All versions of SendMessage send a message of the given type to + // the other client. Can pass either a set of elements or an + // "action", which must have a WriteSessionAction method to go along + // with it. Sending with an action supports sending a "hybrid" + // message. Sending with elements must be sent as Jingle or Gingle. + + // When passing elems, must be either Jingle or Gingle protocol. + // Takes ownership of action_elems. + bool SendMessage(ActionType type, const XmlElements& action_elems, + SessionError* error); + // Sends a messge, but overrides the remote name. + bool SendMessage(ActionType type, const XmlElements& action_elems, + const std::string& remote_name, + SessionError* error); + // When passing an action, may be Hybrid protocol. + template + bool SendMessage(ActionType type, const Action& action, + SessionError* error); + + // Helper methods to write the session message stanza. + template + bool WriteActionMessage(ActionType type, const Action& action, + buzz::XmlElement* stanza, WriteError* error); + template + bool WriteActionMessage(SignalingProtocol protocol, + ActionType type, const Action& action, + buzz::XmlElement* stanza, WriteError* error); + + // Sending messages in hybrid form requires being able to write them + // on a per-protocol basis with a common method signature, which all + // of these have. + bool WriteSessionAction(SignalingProtocol protocol, + const SessionInitiate& init, + XmlElements* elems, WriteError* error); + bool WriteSessionAction(SignalingProtocol protocol, + const TransportInfo& tinfo, + XmlElements* elems, WriteError* error); + bool WriteSessionAction(SignalingProtocol protocol, + const SessionTerminate& term, + XmlElements* elems, WriteError* error); + + // Sends a message back to the other client indicating that we have received + // and accepted their message. + void SendAcknowledgementMessage(const buzz::XmlElement* stanza); + + // Once signaling is ready, the session will use this signal to request the + // sending of each message. When messages are received by the other client, + // they should be handed to OnIncomingMessage. + // (These are called only by SessionManager.) + sigslot::signal2 SignalOutgoingMessage; + void OnIncomingMessage(const SessionMessage& msg); + + void OnIncomingResponse(const buzz::XmlElement* orig_stanza, + const buzz::XmlElement* response_stanza, + const SessionMessage& msg); + void OnInitiateAcked(); + void OnFailedSend(const buzz::XmlElement* orig_stanza, + const buzz::XmlElement* error_stanza); + + // Invoked when an error is found in an incoming message. This is translated + // into the appropriate XMPP response by SessionManager. + sigslot::signal6 SignalErrorMessage; + + // Handlers for the various types of messages. These functions may take + // pointers to the whole stanza or to just the session element. + bool OnInitiateMessage(const SessionMessage& msg, MessageError* error); + bool OnAcceptMessage(const SessionMessage& msg, MessageError* error); + bool OnRejectMessage(const SessionMessage& msg, MessageError* error); + bool OnInfoMessage(const SessionMessage& msg); + bool OnTerminateMessage(const SessionMessage& msg, MessageError* error); + bool OnTransportInfoMessage(const SessionMessage& msg, MessageError* error); + bool OnTransportAcceptMessage(const SessionMessage& msg, MessageError* error); + bool OnDescriptionInfoMessage(const SessionMessage& msg, MessageError* error); + bool OnRedirectError(const SessionRedirect& redirect, SessionError* error); + + // Verifies that we are in the appropriate state to receive this message. + bool CheckState(State state, MessageError* error); + + SessionManager* session_manager_; + bool initiate_acked_; + std::string local_name_; + std::string initiator_name_; + std::string remote_name_; + SessionClient* client_; + TransportParser* transport_parser_; + // Keeps track of what protocol we are speaking. + SignalingProtocol current_protocol_; + + friend class SessionManager; // For access to constructor, destructor, + // and signaling related methods. +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_SESSION_H_ diff --git a/webrtc/p2p/base/session_unittest.cc b/webrtc/p2p/base/session_unittest.cc new file mode 100644 index 000000000..d6f94b29d --- /dev/null +++ b/webrtc/p2p/base/session_unittest.cc @@ -0,0 +1,2430 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include +#include +#include + +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/p2ptransport.h" +#include "webrtc/p2p/base/parsing.h" +#include "webrtc/p2p/base/portallocator.h" +#include "webrtc/p2p/base/relayport.h" +#include "webrtc/p2p/base/relayserver.h" +#include "webrtc/p2p/base/session.h" +#include "webrtc/p2p/base/sessionclient.h" +#include "webrtc/p2p/base/sessionmanager.h" +#include "webrtc/p2p/base/stunport.h" +#include "webrtc/p2p/base/stunserver.h" +#include "webrtc/p2p/base/transportchannel.h" +#include "webrtc/p2p/base/transportchannelproxy.h" +#include "webrtc/p2p/base/udpport.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/base/base64.h" +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/natserver.h" +#include "webrtc/base/natsocketfactory.h" +#include "webrtc/base/stringencode.h" + +using cricket::SignalingProtocol; +using cricket::PROTOCOL_HYBRID; +using cricket::PROTOCOL_JINGLE; +using cricket::PROTOCOL_GINGLE; + +static const std::string kInitiator = "init@init.com"; +static const std::string kResponder = "resp@resp.com"; +// Expected from test random number generator. +static const std::string kSessionId = "9254631414740579489"; +// TODO: When we need to test more than one transport type, +// allow this to be injected like the content types are. +static const std::string kTransportType = "http://www.google.com/transport/p2p"; + +// Controls how long we wait for a session to send messages that we +// expect, in milliseconds. We put it high to avoid flaky tests. +static const int kEventTimeout = 5000; + +static const int kNumPorts = 2; +static const int kPort0 = 28653; +static const int kPortStep = 5; + +int GetPort(int port_index) { + return kPort0 + (port_index * kPortStep); +} + +std::string GetPortString(int port_index) { + return rtc::ToString(GetPort(port_index)); +} + +// Only works for port_index < 10, which is fine for our purposes. +std::string GetUsername(int port_index) { + return "username" + std::string(8, rtc::ToString(port_index)[0]); +} + +// Only works for port_index < 10, which is fine for our purposes. +std::string GetPassword(int port_index) { + return "password" + std::string(8, rtc::ToString(port_index)[0]); +} + +std::string IqAck(const std::string& id, + const std::string& from, + const std::string& to) { + return ""; +} + +std::string IqSet(const std::string& id, + const std::string& from, + const std::string& to, + const std::string& content) { + return "" + + content + + ""; +} + +std::string IqError(const std::string& id, + const std::string& from, + const std::string& to, + const std::string& content) { + return "" + + content + + ""; +} + +std::string GingleSessionXml(const std::string& type, + const std::string& content) { + return "" + + content + + ""; +} + +std::string GingleDescriptionXml(const std::string& content_type) { + return ""; +} + +std::string P2pCandidateXml(const std::string& name, int port_index) { + // Port will update the rtcp username by +1 on the last character. So we need + // to compensate here. See Port::username_fragment() for detail. + std::string username = GetUsername(port_index); + // TODO: Use the component id instead of the channel name to + // determinte if we need to covert the username here. + if (name == "rtcp" || name == "video_rtcp" || name == "chanb") { + char next_ch = username[username.size() - 1]; + ASSERT(username.size() > 0); + rtc::Base64::GetNextBase64Char(next_ch, &next_ch); + username[username.size() - 1] = next_ch; + } + return ""; +} + +std::string JingleActionXml(const std::string& action, + const std::string& content) { + return "" + + content + + ""; +} + +std::string JingleInitiateActionXml(const std::string& content) { + return "" + + content + + ""; +} + +std::string JingleGroupInfoXml(const std::string& content_name_a, + const std::string& content_name_b) { + std::string group_info = ""; + if (!content_name_a.empty()) + group_info += ""; + if (!content_name_b.empty()) + group_info += ""; + group_info += ""; + return group_info; +} + + +std::string JingleEmptyContentXml(const std::string& content_name, + const std::string& content_type, + const std::string& transport_type) { + return "" + "" + "" + ""; +} + +std::string JingleContentXml(const std::string& content_name, + const std::string& content_type, + const std::string& transport_type, + const std::string& transport_main) { + std::string transport = transport_type.empty() ? "" : + "" + + transport_main + + ""; + + return"" + "" + + transport + + ""; +} + +std::string JingleTransportContentXml(const std::string& content_name, + const std::string& transport_type, + const std::string& content) { + return "" + "" + + content + + "" + ""; +} + +std::string GingleInitiateXml(const std::string& content_type) { + return GingleSessionXml( + "initiate", + GingleDescriptionXml(content_type)); +} + +std::string JingleInitiateXml(const std::string& content_name_a, + const std::string& content_type_a, + const std::string& content_name_b, + const std::string& content_type_b, + bool bundle = false) { + std::string content_xml; + if (content_name_b.empty()) { + content_xml = JingleEmptyContentXml( + content_name_a, content_type_a, kTransportType); + } else { + content_xml = JingleEmptyContentXml( + content_name_a, content_type_a, kTransportType) + + JingleEmptyContentXml( + content_name_b, content_type_b, kTransportType); + if (bundle) { + content_xml += JingleGroupInfoXml(content_name_a, content_name_b); + } + } + return JingleInitiateActionXml(content_xml); +} + +std::string GingleAcceptXml(const std::string& content_type) { + return GingleSessionXml( + "accept", + GingleDescriptionXml(content_type)); +} + +std::string JingleAcceptXml(const std::string& content_name_a, + const std::string& content_type_a, + const std::string& content_name_b, + const std::string& content_type_b, + bool bundle = false) { + std::string content_xml; + if (content_name_b.empty()) { + content_xml = JingleEmptyContentXml( + content_name_a, content_type_a, kTransportType); + } else { + content_xml = JingleEmptyContentXml( + content_name_a, content_type_a, kTransportType) + + JingleEmptyContentXml( + content_name_b, content_type_b, kTransportType); + } + if (bundle) { + content_xml += JingleGroupInfoXml(content_name_a, content_name_b); + } + + return JingleActionXml("session-accept", content_xml); +} + +std::string Gingle2CandidatesXml(const std::string& channel_name, + int port_index0, + int port_index1) { + return GingleSessionXml( + "candidates", + P2pCandidateXml(channel_name, port_index0) + + P2pCandidateXml(channel_name, port_index1)); +} + +std::string Gingle4CandidatesXml(const std::string& channel_name_a, + int port_index0, + int port_index1, + const std::string& channel_name_b, + int port_index2, + int port_index3) { + return GingleSessionXml( + "candidates", + P2pCandidateXml(channel_name_a, port_index0) + + P2pCandidateXml(channel_name_a, port_index1) + + P2pCandidateXml(channel_name_b, port_index2) + + P2pCandidateXml(channel_name_b, port_index3)); +} + +std::string Jingle2TransportInfoXml(const std::string& content_name, + const std::string& channel_name, + int port_index0, + int port_index1) { + return JingleActionXml( + "transport-info", + JingleTransportContentXml( + content_name, kTransportType, + P2pCandidateXml(channel_name, port_index0) + + P2pCandidateXml(channel_name, port_index1))); +} + +std::string Jingle4TransportInfoXml(const std::string& content_name, + const std::string& channel_name_a, + int port_index0, + int port_index1, + const std::string& channel_name_b, + int port_index2, + int port_index3) { + return JingleActionXml( + "transport-info", + JingleTransportContentXml( + content_name, kTransportType, + P2pCandidateXml(channel_name_a, port_index0) + + P2pCandidateXml(channel_name_a, port_index1) + + P2pCandidateXml(channel_name_b, port_index2) + + P2pCandidateXml(channel_name_b, port_index3))); +} + +std::string JingleDescriptionInfoXml(const std::string& content_name, + const std::string& content_type) { + return JingleActionXml( + "description-info", + JingleContentXml(content_name, content_type, "", "")); +} + +std::string GingleRejectXml(const std::string& reason) { + return GingleSessionXml( + "reject", + "<" + reason + "/>"); +} + +std::string JingleTerminateXml(const std::string& reason) { + return JingleActionXml( + "session-terminate", + "<" + reason + "/>"); +} + +std::string GingleTerminateXml(const std::string& reason) { + return GingleSessionXml( + "terminate", + "<" + reason + "/>"); +} + +std::string GingleRedirectXml(const std::string& intitiate, + const std::string& target) { + return intitiate + + "" + "" + "xmpp:" + target + + "" + ""; +} + +std::string JingleRedirectXml(const std::string& intitiate, + const std::string& target) { + return intitiate + + "" + "" + "xmpp:" + target + + "" + ""; +} + +std::string InitiateXml(SignalingProtocol protocol, + const std::string& gingle_content_type, + const std::string& content_name_a, + const std::string& content_type_a, + const std::string& content_name_b, + const std::string& content_type_b, + bool bundle = false) { + switch (protocol) { + case PROTOCOL_JINGLE: + return JingleInitiateXml(content_name_a, content_type_a, + content_name_b, content_type_b, + bundle); + case PROTOCOL_GINGLE: + return GingleInitiateXml(gingle_content_type); + case PROTOCOL_HYBRID: + return JingleInitiateXml(content_name_a, content_type_a, + content_name_b, content_type_b) + + GingleInitiateXml(gingle_content_type); + } + return ""; +} + +std::string InitiateXml(SignalingProtocol protocol, + const std::string& content_name, + const std::string& content_type) { + return InitiateXml(protocol, + content_type, + content_name, content_type, + "", ""); +} + +std::string AcceptXml(SignalingProtocol protocol, + const std::string& gingle_content_type, + const std::string& content_name_a, + const std::string& content_type_a, + const std::string& content_name_b, + const std::string& content_type_b, + bool bundle = false) { + switch (protocol) { + case PROTOCOL_JINGLE: + return JingleAcceptXml(content_name_a, content_type_a, + content_name_b, content_type_b, bundle); + case PROTOCOL_GINGLE: + return GingleAcceptXml(gingle_content_type); + case PROTOCOL_HYBRID: + return + JingleAcceptXml(content_name_a, content_type_a, + content_name_b, content_type_b) + + GingleAcceptXml(gingle_content_type); + } + return ""; +} + + +std::string AcceptXml(SignalingProtocol protocol, + const std::string& content_name, + const std::string& content_type, + bool bundle = false) { + return AcceptXml(protocol, + content_type, + content_name, content_type, + "", ""); +} + +std::string TransportInfo2Xml(SignalingProtocol protocol, + const std::string& content_name, + const std::string& channel_name, + int port_index0, + int port_index1) { + switch (protocol) { + case PROTOCOL_JINGLE: + return Jingle2TransportInfoXml( + content_name, + channel_name, port_index0, port_index1); + case PROTOCOL_GINGLE: + return Gingle2CandidatesXml( + channel_name, port_index0, port_index1); + case PROTOCOL_HYBRID: + return + Jingle2TransportInfoXml( + content_name, + channel_name, port_index0, port_index1) + + Gingle2CandidatesXml( + channel_name, port_index0, port_index1); + } + return ""; +} + +std::string TransportInfo4Xml(SignalingProtocol protocol, + const std::string& content_name, + const std::string& channel_name_a, + int port_index0, + int port_index1, + const std::string& channel_name_b, + int port_index2, + int port_index3) { + switch (protocol) { + case PROTOCOL_JINGLE: + return Jingle4TransportInfoXml( + content_name, + channel_name_a, port_index0, port_index1, + channel_name_b, port_index2, port_index3); + case PROTOCOL_GINGLE: + return Gingle4CandidatesXml( + channel_name_a, port_index0, port_index1, + channel_name_b, port_index2, port_index3); + case PROTOCOL_HYBRID: + return + Jingle4TransportInfoXml( + content_name, + channel_name_a, port_index0, port_index1, + channel_name_b, port_index2, port_index3) + + Gingle4CandidatesXml( + channel_name_a, port_index0, port_index1, + channel_name_b, port_index2, port_index3); + } + return ""; +} + +std::string RejectXml(SignalingProtocol protocol, + const std::string& reason) { + switch (protocol) { + case PROTOCOL_JINGLE: + return JingleTerminateXml(reason); + case PROTOCOL_GINGLE: + return GingleRejectXml(reason); + case PROTOCOL_HYBRID: + return JingleTerminateXml(reason) + + GingleRejectXml(reason); + } + return ""; +} + +std::string TerminateXml(SignalingProtocol protocol, + const std::string& reason) { + switch (protocol) { + case PROTOCOL_JINGLE: + return JingleTerminateXml(reason); + case PROTOCOL_GINGLE: + return GingleTerminateXml(reason); + case PROTOCOL_HYBRID: + return JingleTerminateXml(reason) + + GingleTerminateXml(reason); + } + return ""; +} + +std::string RedirectXml(SignalingProtocol protocol, + const std::string& initiate, + const std::string& target) { + switch (protocol) { + case PROTOCOL_JINGLE: + return JingleRedirectXml(initiate, target); + case PROTOCOL_GINGLE: + return GingleRedirectXml(initiate, target); + default: + break; + } + return ""; +} + +// TODO: Break out and join with fakeportallocator.h +class TestPortAllocatorSession : public cricket::PortAllocatorSession { + public: + TestPortAllocatorSession(const std::string& content_name, + int component, + const std::string& ice_ufrag, + const std::string& ice_pwd, + const int port_offset) + : PortAllocatorSession(content_name, component, ice_ufrag, ice_pwd, 0), + port_offset_(port_offset), + ports_(kNumPorts), + address_("127.0.0.1", 0), + network_("network", "unittest", + rtc::IPAddress(INADDR_LOOPBACK), 8), + socket_factory_(rtc::Thread::Current()), + running_(false) { + network_.AddIP(address_.ipaddr()); + } + + ~TestPortAllocatorSession() { + for (size_t i = 0; i < ports_.size(); i++) + delete ports_[i]; + } + + virtual void StartGettingPorts() { + for (int i = 0; i < kNumPorts; i++) { + int index = port_offset_ + i; + ports_[i] = cricket::UDPPort::Create( + rtc::Thread::Current(), &socket_factory_, + &network_, address_.ipaddr(), GetPort(index), GetPort(index), + GetUsername(index), GetPassword(index)); + AddPort(ports_[i]); + } + running_ = true; + } + + virtual void StopGettingPorts() { running_ = false; } + virtual bool IsGettingPorts() { return running_; } + + void AddPort(cricket::Port* port) { + port->set_component(component_); + port->set_generation(0); + port->SignalDestroyed.connect( + this, &TestPortAllocatorSession::OnPortDestroyed); + port->SignalPortComplete.connect( + this, &TestPortAllocatorSession::OnPortComplete); + port->PrepareAddress(); + SignalPortReady(this, port); + } + + void OnPortDestroyed(cricket::PortInterface* port) { + for (size_t i = 0; i < ports_.size(); i++) { + if (ports_[i] == port) + ports_[i] = NULL; + } + } + + void OnPortComplete(cricket::Port* port) { + SignalCandidatesReady(this, port->Candidates()); + } + + private: + int port_offset_; + std::vector ports_; + rtc::SocketAddress address_; + rtc::Network network_; + rtc::BasicPacketSocketFactory socket_factory_; + bool running_; +}; + +class TestPortAllocator : public cricket::PortAllocator { + public: + TestPortAllocator() : port_offset_(0) {} + + virtual cricket::PortAllocatorSession* + CreateSessionInternal( + const std::string& content_name, + int component, + const std::string& ice_ufrag, + const std::string& ice_pwd) { + port_offset_ += 2; + return new TestPortAllocatorSession(content_name, component, + ice_ufrag, ice_pwd, port_offset_ - 2); + } + + int port_offset_; +}; + +class TestContentDescription : public cricket::ContentDescription { + public: + explicit TestContentDescription(const std::string& gingle_content_type, + const std::string& content_type) + : gingle_content_type(gingle_content_type), + content_type(content_type) { + } + virtual ContentDescription* Copy() const { + return new TestContentDescription(*this); + } + + std::string gingle_content_type; + std::string content_type; +}; + +cricket::SessionDescription* NewTestSessionDescription( + const std::string gingle_content_type, + const std::string& content_name_a, const std::string& content_type_a, + const std::string& content_name_b, const std::string& content_type_b) { + + cricket::SessionDescription* offer = new cricket::SessionDescription(); + offer->AddContent(content_name_a, content_type_a, + new TestContentDescription(gingle_content_type, + content_type_a)); + cricket::TransportDescription desc(cricket::NS_GINGLE_P2P, + std::string(), std::string()); + offer->AddTransportInfo(cricket::TransportInfo(content_name_a, desc)); + + if (content_name_a != content_name_b) { + offer->AddContent(content_name_b, content_type_b, + new TestContentDescription(gingle_content_type, + content_type_b)); + offer->AddTransportInfo(cricket::TransportInfo(content_name_b, desc)); + } + return offer; +} + +cricket::SessionDescription* NewTestSessionDescription( + const std::string& content_name, const std::string& content_type) { + + cricket::SessionDescription* offer = new cricket::SessionDescription(); + offer->AddContent(content_name, content_type, + new TestContentDescription(content_type, + content_type)); + offer->AddTransportInfo(cricket::TransportInfo + (content_name, cricket::TransportDescription( + cricket::NS_GINGLE_P2P, + std::string(), std::string()))); + return offer; +} + +struct TestSessionClient: public cricket::SessionClient, + public sigslot::has_slots<> { + public: + TestSessionClient() { + } + + ~TestSessionClient() { + } + + virtual bool ParseContent(SignalingProtocol protocol, + const buzz::XmlElement* elem, + cricket::ContentDescription** content, + cricket::ParseError* error) { + std::string content_type; + std::string gingle_content_type; + if (protocol == PROTOCOL_GINGLE) { + gingle_content_type = elem->Name().Namespace(); + } else { + content_type = elem->Name().Namespace(); + } + + *content = new TestContentDescription(gingle_content_type, content_type); + return true; + } + + virtual bool WriteContent(SignalingProtocol protocol, + const cricket::ContentDescription* untyped_content, + buzz::XmlElement** elem, + cricket::WriteError* error) { + const TestContentDescription* content = + static_cast(untyped_content); + std::string content_type = (protocol == PROTOCOL_GINGLE ? + content->gingle_content_type : + content->content_type); + *elem = new buzz::XmlElement( + buzz::QName(content_type, "description"), true); + return true; + } + + void OnSessionCreate(cricket::Session* session, bool initiate) { + } + + void OnSessionDestroy(cricket::Session* session) { + } +}; + +struct ChannelHandler : sigslot::has_slots<> { + explicit ChannelHandler(cricket::TransportChannel* p, const std::string& name) + : channel(p), last_readable(false), last_writable(false), data_count(0), + last_size(0), name(name) { + p->SignalReadableState.connect(this, &ChannelHandler::OnReadableState); + p->SignalWritableState.connect(this, &ChannelHandler::OnWritableState); + p->SignalReadPacket.connect(this, &ChannelHandler::OnReadPacket); + } + + bool writable() const { + return last_writable && channel->writable(); + } + + bool readable() const { + return last_readable && channel->readable(); + } + + void OnReadableState(cricket::TransportChannel* p) { + EXPECT_EQ(channel, p); + last_readable = channel->readable(); + } + + void OnWritableState(cricket::TransportChannel* p) { + EXPECT_EQ(channel, p); + last_writable = channel->writable(); + } + + void OnReadPacket(cricket::TransportChannel* p, const char* buf, + size_t size, const rtc::PacketTime& time, int flags) { + if (memcmp(buf, name.c_str(), name.size()) != 0) + return; // drop packet if packet doesn't belong to this channel. This + // can happen when transport channels are muxed together. + buf += name.size(); // Remove channel name from the message. + size -= name.size(); // Decrement size by channel name string size. + EXPECT_EQ(channel, p); + EXPECT_LE(size, sizeof(last_data)); + data_count += 1; + last_size = size; + memcpy(last_data, buf, size); + } + + void Send(const char* data, size_t size) { + rtc::PacketOptions options; + std::string data_with_id(name); + data_with_id += data; + int result = channel->SendPacket(data_with_id.c_str(), data_with_id.size(), + options, 0); + EXPECT_EQ(static_cast(data_with_id.size()), result); + } + + cricket::TransportChannel* channel; + bool last_readable, last_writable; + int data_count; + char last_data[4096]; + size_t last_size; + std::string name; +}; + +void PrintStanza(const std::string& message, + const buzz::XmlElement* stanza) { + printf("%s: %s\n", message.c_str(), stanza->Str().c_str()); +} + +class TestClient : public sigslot::has_slots<> { + public: + // TODO: Add channel_component_a/b as inputs to the ctor. + TestClient(cricket::PortAllocator* port_allocator, + int* next_message_id, + const std::string& local_name, + SignalingProtocol start_protocol, + const std::string& content_type, + const std::string& content_name_a, + const std::string& channel_name_a, + const std::string& content_name_b, + const std::string& channel_name_b) { + Construct(port_allocator, next_message_id, local_name, start_protocol, + content_type, content_name_a, channel_name_a, + content_name_b, channel_name_b); + } + + ~TestClient() { + if (session) { + session_manager->DestroySession(session); + EXPECT_EQ(1U, session_destroyed_count); + } + delete session_manager; + delete client; + for (std::deque::iterator it = sent_stanzas.begin(); + it != sent_stanzas.end(); ++it) { + delete *it; + } + } + + void Construct(cricket::PortAllocator* pa, + int* message_id, + const std::string& lname, + SignalingProtocol protocol, + const std::string& cont_type, + const std::string& cont_name_a, + const std::string& chan_name_a, + const std::string& cont_name_b, + const std::string& chan_name_b) { + port_allocator_ = pa; + next_message_id = message_id; + local_name = lname; + start_protocol = protocol; + content_type = cont_type; + content_name_a = cont_name_a; + channel_name_a = chan_name_a; + content_name_b = cont_name_b; + channel_name_b = chan_name_b; + session_created_count = 0; + session_destroyed_count = 0; + session_remote_description_update_count = 0; + new_local_description = false; + new_remote_description = false; + last_content_action = cricket::CA_OFFER; + last_content_source = cricket::CS_LOCAL; + session = NULL; + last_session_state = cricket::BaseSession::STATE_INIT; + blow_up_on_error = true; + error_count = 0; + + session_manager = new cricket::SessionManager(port_allocator_); + session_manager->SignalSessionCreate.connect( + this, &TestClient::OnSessionCreate); + session_manager->SignalSessionDestroy.connect( + this, &TestClient::OnSessionDestroy); + session_manager->SignalOutgoingMessage.connect( + this, &TestClient::OnOutgoingMessage); + + client = new TestSessionClient(); + session_manager->AddClient(content_type, client); + EXPECT_EQ(client, session_manager->GetClient(content_type)); + } + + uint32 sent_stanza_count() const { + return static_cast(sent_stanzas.size()); + } + + const buzz::XmlElement* stanza() const { + return last_expected_sent_stanza.get(); + } + + cricket::BaseSession::State session_state() const { + EXPECT_EQ(last_session_state, session->state()); + return session->state(); + } + + void SetSessionState(cricket::BaseSession::State state) { + session->SetState(state); + EXPECT_EQ_WAIT(last_session_state, session->state(), kEventTimeout); + } + + void CreateSession() { + session_manager->CreateSession(local_name, content_type); + } + + void DeliverStanza(const buzz::XmlElement* stanza) { + session_manager->OnIncomingMessage(stanza); + } + + void DeliverStanza(const std::string& str) { + buzz::XmlElement* stanza = buzz::XmlElement::ForStr(str); + session_manager->OnIncomingMessage(stanza); + delete stanza; + } + + void DeliverAckToLastStanza() { + const buzz::XmlElement* orig_stanza = stanza(); + const buzz::XmlElement* response_stanza = + buzz::XmlElement::ForStr(IqAck(orig_stanza->Attr(buzz::QN_IQ), "", "")); + session_manager->OnIncomingResponse(orig_stanza, response_stanza); + delete response_stanza; + } + + void ExpectSentStanza(const std::string& expected) { + EXPECT_TRUE(!sent_stanzas.empty()) << + "Found no stanza when expected " << expected; + + last_expected_sent_stanza.reset(sent_stanzas.front()); + sent_stanzas.pop_front(); + + std::string actual = last_expected_sent_stanza->Str(); + EXPECT_EQ(expected, actual); + } + + void SkipUnsentStanza() { + GetNextOutgoingMessageID(); + } + + bool HasTransport(const std::string& content_name) const { + ASSERT(session != NULL); + const cricket::Transport* transport = session->GetTransport(content_name); + return transport != NULL && (kTransportType == transport->type()); + } + + bool HasChannel(const std::string& content_name, + int component) const { + ASSERT(session != NULL); + const cricket::TransportChannel* channel = + session->GetChannel(content_name, component); + return channel != NULL && (component == channel->component()); + } + + cricket::TransportChannel* GetChannel(const std::string& content_name, + int component) const { + ASSERT(session != NULL); + return session->GetChannel(content_name, component); + } + + void OnSessionCreate(cricket::Session* created_session, bool initiate) { + session_created_count += 1; + + session = created_session; + session->set_current_protocol(start_protocol); + session->SignalState.connect(this, &TestClient::OnSessionState); + session->SignalError.connect(this, &TestClient::OnSessionError); + session->SignalRemoteDescriptionUpdate.connect( + this, &TestClient::OnSessionRemoteDescriptionUpdate); + session->SignalNewLocalDescription.connect( + this, &TestClient::OnNewLocalDescription); + session->SignalNewRemoteDescription.connect( + this, &TestClient::OnNewRemoteDescription); + + CreateChannels(); + } + + void OnSessionDestroy(cricket::Session *session) { + session_destroyed_count += 1; + } + + void OnSessionState(cricket::BaseSession* session, + cricket::BaseSession::State state) { + // EXPECT_EQ does not allow use of this, hence the tmp variable. + cricket::BaseSession* tmp = this->session; + EXPECT_EQ(tmp, session); + last_session_state = state; + } + + void OnSessionError(cricket::BaseSession* session, + cricket::BaseSession::Error error) { + // EXPECT_EQ does not allow use of this, hence the tmp variable. + cricket::BaseSession* tmp = this->session; + EXPECT_EQ(tmp, session); + if (blow_up_on_error) { + EXPECT_TRUE(false); + } else { + error_count++; + } + } + + void OnSessionRemoteDescriptionUpdate(cricket::BaseSession* session, + const cricket::ContentInfos& contents) { + session_remote_description_update_count++; + } + + void OnNewLocalDescription(cricket::BaseSession* session, + cricket::ContentAction action) { + new_local_description = true; + last_content_action = action; + last_content_source = cricket::CS_LOCAL; + } + + void OnNewRemoteDescription(cricket::BaseSession* session, + cricket::ContentAction action) { + new_remote_description = true; + last_content_action = action; + last_content_source = cricket::CS_REMOTE; + } + + void PrepareCandidates() { + session_manager->OnSignalingReady(); + } + + void OnOutgoingMessage(cricket::SessionManager* manager, + const buzz::XmlElement* stanza) { + buzz::XmlElement* elem = new buzz::XmlElement(*stanza); + EXPECT_TRUE(elem->Name() == buzz::QN_IQ); + EXPECT_TRUE(elem->HasAttr(buzz::QN_TO)); + EXPECT_FALSE(elem->HasAttr(buzz::QN_FROM)); + EXPECT_TRUE(elem->HasAttr(buzz::QN_TYPE)); + EXPECT_TRUE((elem->Attr(buzz::QN_TYPE) == "set") || + (elem->Attr(buzz::QN_TYPE) == "result") || + (elem->Attr(buzz::QN_TYPE) == "error")); + + elem->SetAttr(buzz::QN_FROM, local_name); + if (elem->Attr(buzz::QN_TYPE) == "set") { + EXPECT_FALSE(elem->HasAttr(buzz::QN_ID)); + elem->SetAttr(buzz::QN_ID, GetNextOutgoingMessageID()); + } + + // Uncommenting this is useful for debugging. + // PrintStanza("OutgoingMessage", elem); + sent_stanzas.push_back(elem); + } + + std::string GetNextOutgoingMessageID() { + int message_id = (*next_message_id)++; + std::ostringstream ost; + ost << message_id; + return ost.str(); + } + + void CreateChannels() { + ASSERT(session != NULL); + // We either have a single content with multiple components (RTP/RTCP), or + // multiple contents with single components, but not both. + int component_a = 1; + int component_b = (content_name_a == content_name_b) ? 2 : 1; + chan_a.reset(new ChannelHandler( + session->CreateChannel(content_name_a, channel_name_a, component_a), + channel_name_a)); + chan_b.reset(new ChannelHandler( + session->CreateChannel(content_name_b, channel_name_b, component_b), + channel_name_b)); + } + + int* next_message_id; + std::string local_name; + SignalingProtocol start_protocol; + std::string content_type; + std::string content_name_a; + std::string channel_name_a; + std::string content_name_b; + std::string channel_name_b; + + uint32 session_created_count; + uint32 session_destroyed_count; + uint32 session_remote_description_update_count; + bool new_local_description; + bool new_remote_description; + cricket::ContentAction last_content_action; + cricket::ContentSource last_content_source; + std::deque sent_stanzas; + rtc::scoped_ptr last_expected_sent_stanza; + + cricket::SessionManager* session_manager; + TestSessionClient* client; + cricket::PortAllocator* port_allocator_; + cricket::Session* session; + cricket::BaseSession::State last_session_state; + rtc::scoped_ptr chan_a; + rtc::scoped_ptr chan_b; + bool blow_up_on_error; + int error_count; +}; + +class SessionTest : public testing::Test { + protected: + virtual void SetUp() { + // Seed needed for each test to satisfy expectations. + rtc::SetRandomTestMode(true); + } + + virtual void TearDown() { + rtc::SetRandomTestMode(false); + } + + // Tests sending data between two clients, over two channels. + void TestSendRecv(ChannelHandler* chan1a, + ChannelHandler* chan1b, + ChannelHandler* chan2a, + ChannelHandler* chan2b) { + const char* dat1a = "spamspamspamspamspamspamspambakedbeansspam"; + const char* dat2a = "mapssnaebdekabmapsmapsmapsmapsmapsmapsmaps"; + const char* dat1b = "Lobster Thermidor a Crevette with a mornay sauce..."; + const char* dat2b = "...ecuas yanrom a htiw etteverC a rodimrehT retsboL"; + + for (int i = 0; i < 20; i++) { + chan1a->Send(dat1a, strlen(dat1a)); + chan1b->Send(dat1b, strlen(dat1b)); + chan2a->Send(dat2a, strlen(dat2a)); + chan2b->Send(dat2b, strlen(dat2b)); + + EXPECT_EQ_WAIT(i + 1, chan1a->data_count, kEventTimeout); + EXPECT_EQ_WAIT(i + 1, chan1b->data_count, kEventTimeout); + EXPECT_EQ_WAIT(i + 1, chan2a->data_count, kEventTimeout); + EXPECT_EQ_WAIT(i + 1, chan2b->data_count, kEventTimeout); + + EXPECT_EQ(strlen(dat2a), chan1a->last_size); + EXPECT_EQ(strlen(dat2b), chan1b->last_size); + EXPECT_EQ(strlen(dat1a), chan2a->last_size); + EXPECT_EQ(strlen(dat1b), chan2b->last_size); + + EXPECT_EQ(0, memcmp(chan1a->last_data, dat2a, strlen(dat2a))); + EXPECT_EQ(0, memcmp(chan1b->last_data, dat2b, strlen(dat2b))); + EXPECT_EQ(0, memcmp(chan2a->last_data, dat1a, strlen(dat1a))); + EXPECT_EQ(0, memcmp(chan2b->last_data, dat1b, strlen(dat1b))); + } + } + + // Test an initiate from one client to another, each with + // independent initial protocols. Checks for the correct initiates, + // candidates, and accept messages, and tests that working network + // channels are established. + void TestSession(SignalingProtocol initiator_protocol, + SignalingProtocol responder_protocol, + SignalingProtocol resulting_protocol, + const std::string& gingle_content_type, + const std::string& content_type, + const std::string& content_name_a, + const std::string& channel_name_a, + const std::string& content_name_b, + const std::string& channel_name_b, + const std::string& initiate_xml, + const std::string& transport_info_a_xml, + const std::string& transport_info_b_xml, + const std::string& transport_info_reply_a_xml, + const std::string& transport_info_reply_b_xml, + const std::string& accept_xml, + bool bundle = false) { + rtc::scoped_ptr allocator( + new TestPortAllocator()); + int next_message_id = 0; + + rtc::scoped_ptr initiator( + new TestClient(allocator.get(), &next_message_id, + kInitiator, initiator_protocol, + content_type, + content_name_a, channel_name_a, + content_name_b, channel_name_b)); + rtc::scoped_ptr responder( + new TestClient(allocator.get(), &next_message_id, + kResponder, responder_protocol, + content_type, + content_name_a, channel_name_a, + content_name_b, channel_name_b)); + + // Create Session and check channels and state. + initiator->CreateSession(); + EXPECT_EQ(1U, initiator->session_created_count); + EXPECT_EQ(kSessionId, initiator->session->id()); + EXPECT_EQ(initiator->session->local_name(), kInitiator); + EXPECT_EQ(cricket::BaseSession::STATE_INIT, + initiator->session_state()); + + // See comment in CreateChannels about how we choose component IDs. + int component_a = 1; + int component_b = (content_name_a == content_name_b) ? 2 : 1; + EXPECT_TRUE(initiator->HasTransport(content_name_a)); + EXPECT_TRUE(initiator->HasChannel(content_name_a, component_a)); + EXPECT_TRUE(initiator->HasTransport(content_name_b)); + EXPECT_TRUE(initiator->HasChannel(content_name_b, component_b)); + + // Initiate and expect initiate message sent. + cricket::SessionDescription* offer = NewTestSessionDescription( + gingle_content_type, + content_name_a, content_type, + content_name_b, content_type); + if (bundle) { + cricket::ContentGroup group(cricket::GROUP_TYPE_BUNDLE); + group.AddContentName(content_name_a); + group.AddContentName(content_name_b); + EXPECT_TRUE(group.HasContentName(content_name_a)); + EXPECT_TRUE(group.HasContentName(content_name_b)); + offer->AddGroup(group); + } + EXPECT_TRUE(initiator->session->Initiate(kResponder, offer)); + EXPECT_EQ(initiator->session->remote_name(), kResponder); + EXPECT_EQ(initiator->session->local_description(), offer); + + EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout); + EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE, + initiator->session_state()); + + initiator->ExpectSentStanza( + IqSet("0", kInitiator, kResponder, initiate_xml)); + + // Deliver the initiate. Expect ack and session created with + // transports. + responder->DeliverStanza(initiator->stanza()); + responder->ExpectSentStanza( + IqAck("0", kResponder, kInitiator)); + EXPECT_EQ(0U, responder->sent_stanza_count()); + + EXPECT_EQ(1U, responder->session_created_count); + EXPECT_EQ(kSessionId, responder->session->id()); + EXPECT_EQ(responder->session->local_name(), kResponder); + EXPECT_EQ(responder->session->remote_name(), kInitiator); + EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE, + responder->session_state()); + + EXPECT_TRUE(responder->HasTransport(content_name_a)); + EXPECT_TRUE(responder->HasChannel(content_name_a, component_a)); + EXPECT_TRUE(responder->HasTransport(content_name_b)); + EXPECT_TRUE(responder->HasChannel(content_name_b, component_b)); + + // Expect transport-info message from initiator. + // But don't send candidates until initiate ack is received. + initiator->PrepareCandidates(); + WAIT(initiator->sent_stanza_count() > 0, 100); + EXPECT_EQ(0U, initiator->sent_stanza_count()); + initiator->DeliverAckToLastStanza(); + EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout); + initiator->ExpectSentStanza( + IqSet("1", kInitiator, kResponder, transport_info_a_xml)); + + // Deliver transport-info and expect ack. + responder->DeliverStanza(initiator->stanza()); + responder->ExpectSentStanza( + IqAck("1", kResponder, kInitiator)); + + if (!transport_info_b_xml.empty()) { + // Expect second transport-info message from initiator. + EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout); + initiator->ExpectSentStanza( + IqSet("2", kInitiator, kResponder, transport_info_b_xml)); + EXPECT_EQ(0U, initiator->sent_stanza_count()); + + // Deliver second transport-info message and expect ack. + responder->DeliverStanza(initiator->stanza()); + responder->ExpectSentStanza( + IqAck("2", kResponder, kInitiator)); + } else { + EXPECT_EQ(0U, initiator->sent_stanza_count()); + EXPECT_EQ(0U, responder->sent_stanza_count()); + initiator->SkipUnsentStanza(); + } + + // Expect reply transport-info message from responder. + responder->PrepareCandidates(); + EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout); + responder->ExpectSentStanza( + IqSet("3", kResponder, kInitiator, transport_info_reply_a_xml)); + + // Deliver reply transport-info and expect ack. + initiator->DeliverStanza(responder->stanza()); + initiator->ExpectSentStanza( + IqAck("3", kInitiator, kResponder)); + + if (!transport_info_reply_b_xml.empty()) { + // Expect second reply transport-info message from responder. + EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout); + responder->ExpectSentStanza( + IqSet("4", kResponder, kInitiator, transport_info_reply_b_xml)); + EXPECT_EQ(0U, responder->sent_stanza_count()); + + // Deliver second reply transport-info message and expect ack. + initiator->DeliverStanza(responder->stanza()); + initiator->ExpectSentStanza( + IqAck("4", kInitiator, kResponder)); + EXPECT_EQ(0U, initiator->sent_stanza_count()); + } else { + EXPECT_EQ(0U, initiator->sent_stanza_count()); + EXPECT_EQ(0U, responder->sent_stanza_count()); + responder->SkipUnsentStanza(); + } + + // The channels should be able to become writable at this point. This + // requires pinging, so it may take a little while. + EXPECT_TRUE_WAIT(initiator->chan_a->writable() && + initiator->chan_a->readable(), kEventTimeout); + EXPECT_TRUE_WAIT(initiator->chan_b->writable() && + initiator->chan_b->readable(), kEventTimeout); + EXPECT_TRUE_WAIT(responder->chan_a->writable() && + responder->chan_a->readable(), kEventTimeout); + EXPECT_TRUE_WAIT(responder->chan_b->writable() && + responder->chan_b->readable(), kEventTimeout); + + // Accept the session and expect accept stanza. + cricket::SessionDescription* answer = NewTestSessionDescription( + gingle_content_type, + content_name_a, content_type, + content_name_b, content_type); + if (bundle) { + cricket::ContentGroup group(cricket::GROUP_TYPE_BUNDLE); + group.AddContentName(content_name_a); + group.AddContentName(content_name_b); + EXPECT_TRUE(group.HasContentName(content_name_a)); + EXPECT_TRUE(group.HasContentName(content_name_b)); + answer->AddGroup(group); + } + EXPECT_TRUE(responder->session->Accept(answer)); + EXPECT_EQ(responder->session->local_description(), answer); + + responder->ExpectSentStanza( + IqSet("5", kResponder, kInitiator, accept_xml)); + + EXPECT_EQ(0U, responder->sent_stanza_count()); + + // Deliver the accept message and expect an ack. + initiator->DeliverStanza(responder->stanza()); + EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout); + initiator->ExpectSentStanza( + IqAck("5", kInitiator, kResponder)); + EXPECT_EQ(0U, initiator->sent_stanza_count()); + + // Both sessions should be in progress and have functioning + // channels. + EXPECT_EQ(resulting_protocol, initiator->session->current_protocol()); + EXPECT_EQ(resulting_protocol, responder->session->current_protocol()); + EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS, + initiator->session_state(), kEventTimeout); + EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS, + responder->session_state(), kEventTimeout); + if (bundle) { + cricket::TransportChannel* initiator_chan_a = initiator->chan_a->channel; + cricket::TransportChannel* initiator_chan_b = initiator->chan_b->channel; + + // Since we know these are TransportChannelProxy, type cast it. + cricket::TransportChannelProxy* initiator_proxy_chan_a = + static_cast(initiator_chan_a); + cricket::TransportChannelProxy* initiator_proxy_chan_b = + static_cast(initiator_chan_b); + EXPECT_TRUE(initiator_proxy_chan_a->impl() != NULL); + EXPECT_TRUE(initiator_proxy_chan_b->impl() != NULL); + EXPECT_EQ(initiator_proxy_chan_a->impl(), initiator_proxy_chan_b->impl()); + + cricket::TransportChannel* responder_chan_a = responder->chan_a->channel; + cricket::TransportChannel* responder_chan_b = responder->chan_b->channel; + + // Since we know these are TransportChannelProxy, type cast it. + cricket::TransportChannelProxy* responder_proxy_chan_a = + static_cast(responder_chan_a); + cricket::TransportChannelProxy* responder_proxy_chan_b = + static_cast(responder_chan_b); + EXPECT_TRUE(responder_proxy_chan_a->impl() != NULL); + EXPECT_TRUE(responder_proxy_chan_b->impl() != NULL); + EXPECT_EQ(responder_proxy_chan_a->impl(), responder_proxy_chan_b->impl()); + } + TestSendRecv(initiator->chan_a.get(), initiator->chan_b.get(), + responder->chan_a.get(), responder->chan_b.get()); + + if (resulting_protocol == PROTOCOL_JINGLE) { + // Deliver a description-info message to the initiator and check if the + // content description changes. + EXPECT_EQ(0U, initiator->session_remote_description_update_count); + + const cricket::SessionDescription* old_session_desc = + initiator->session->remote_description(); + const cricket::ContentInfo* old_content_a = + old_session_desc->GetContentByName(content_name_a); + const cricket::ContentDescription* old_content_desc_a = + old_content_a->description; + const cricket::ContentInfo* old_content_b = + old_session_desc->GetContentByName(content_name_b); + const cricket::ContentDescription* old_content_desc_b = + old_content_b->description; + EXPECT_TRUE(old_content_desc_a != NULL); + EXPECT_TRUE(old_content_desc_b != NULL); + + LOG(LS_INFO) << "A " << old_content_a->name; + LOG(LS_INFO) << "B " << old_content_b->name; + + std::string description_info_xml = + JingleDescriptionInfoXml(content_name_a, content_type); + initiator->DeliverStanza( + IqSet("6", kResponder, kInitiator, description_info_xml)); + responder->SkipUnsentStanza(); + EXPECT_EQ(1U, initiator->session_remote_description_update_count); + + const cricket::SessionDescription* new_session_desc = + initiator->session->remote_description(); + const cricket::ContentInfo* new_content_a = + new_session_desc->GetContentByName(content_name_a); + const cricket::ContentDescription* new_content_desc_a = + new_content_a->description; + const cricket::ContentInfo* new_content_b = + new_session_desc->GetContentByName(content_name_b); + const cricket::ContentDescription* new_content_desc_b = + new_content_b->description; + EXPECT_TRUE(new_content_desc_a != NULL); + EXPECT_TRUE(new_content_desc_b != NULL); + + // TODO: We used to replace contents from an update, but + // that no longer works with partial updates. We need to figure out + // a way to merge patial updates into contents. For now, users of + // Session should listen to SignalRemoteDescriptionUpdate and handle + // updates. They should not expect remote_description to be the + // latest value. + // See session.cc OnDescriptionInfoMessage. + + // EXPECT_NE(old_content_desc_a, new_content_desc_a); + + // if (content_name_a != content_name_b) { + // // If content_name_a != content_name_b, then b's content description + // // should not have changed since the description-info message only + // // contained an update for content_name_a. + // EXPECT_EQ(old_content_desc_b, new_content_desc_b); + // } + + EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout); + initiator->ExpectSentStanza( + IqAck("6", kInitiator, kResponder)); + EXPECT_EQ(0U, initiator->sent_stanza_count()); + } else { + responder->SkipUnsentStanza(); + } + + initiator->session->Terminate(); + initiator->ExpectSentStanza( + IqSet("7", kInitiator, kResponder, + TerminateXml(resulting_protocol, + cricket::STR_TERMINATE_SUCCESS))); + + responder->DeliverStanza(initiator->stanza()); + responder->ExpectSentStanza( + IqAck("7", kResponder, kInitiator)); + EXPECT_EQ(cricket::BaseSession::STATE_SENTTERMINATE, + initiator->session_state()); + EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDTERMINATE, + responder->session_state()); + } + + // Test an initiate with other content, called "main". + void TestOtherContent(SignalingProtocol initiator_protocol, + SignalingProtocol responder_protocol, + SignalingProtocol resulting_protocol) { + std::string content_name = "main"; + std::string content_type = "http://oink.splat/session"; + std::string content_name_a = content_name; + std::string channel_name_a = "rtp"; + std::string content_name_b = content_name; + std::string channel_name_b = "rtcp"; + std::string initiate_xml = InitiateXml( + initiator_protocol, + content_name_a, content_type); + std::string transport_info_a_xml = TransportInfo4Xml( + initiator_protocol, content_name, + channel_name_a, 0, 1, + channel_name_b, 2, 3); + std::string transport_info_b_xml = ""; + std::string transport_info_reply_a_xml = TransportInfo4Xml( + resulting_protocol, content_name, + channel_name_a, 4, 5, + channel_name_b, 6, 7); + std::string transport_info_reply_b_xml = ""; + std::string accept_xml = AcceptXml( + resulting_protocol, + content_name_a, content_type); + + + TestSession(initiator_protocol, responder_protocol, resulting_protocol, + content_type, + content_type, + content_name_a, channel_name_a, + content_name_b, channel_name_b, + initiate_xml, + transport_info_a_xml, transport_info_b_xml, + transport_info_reply_a_xml, transport_info_reply_b_xml, + accept_xml); + } + + // Test an initiate with audio content. + void TestAudioContent(SignalingProtocol initiator_protocol, + SignalingProtocol responder_protocol, + SignalingProtocol resulting_protocol) { + std::string gingle_content_type = cricket::NS_GINGLE_AUDIO; + std::string content_name = cricket::CN_AUDIO; + std::string content_type = cricket::NS_JINGLE_RTP; + std::string channel_name_a = "rtp"; + std::string channel_name_b = "rtcp"; + std::string initiate_xml = InitiateXml( + initiator_protocol, + gingle_content_type, + content_name, content_type, + "", ""); + std::string transport_info_a_xml = TransportInfo4Xml( + initiator_protocol, content_name, + channel_name_a, 0, 1, + channel_name_b, 2, 3); + std::string transport_info_b_xml = ""; + std::string transport_info_reply_a_xml = TransportInfo4Xml( + resulting_protocol, content_name, + channel_name_a, 4, 5, + channel_name_b, 6, 7); + std::string transport_info_reply_b_xml = ""; + std::string accept_xml = AcceptXml( + resulting_protocol, + gingle_content_type, + content_name, content_type, + "", ""); + + + TestSession(initiator_protocol, responder_protocol, resulting_protocol, + gingle_content_type, + content_type, + content_name, channel_name_a, + content_name, channel_name_b, + initiate_xml, + transport_info_a_xml, transport_info_b_xml, + transport_info_reply_a_xml, transport_info_reply_b_xml, + accept_xml); + } + + // Since media content is "split" into two contents (audio and + // video), we need to treat it special. + void TestVideoContents(SignalingProtocol initiator_protocol, + SignalingProtocol responder_protocol, + SignalingProtocol resulting_protocol) { + std::string content_type = cricket::NS_JINGLE_RTP; + std::string gingle_content_type = cricket::NS_GINGLE_VIDEO; + std::string content_name_a = cricket::CN_AUDIO; + std::string channel_name_a = "rtp"; + std::string content_name_b = cricket::CN_VIDEO; + std::string channel_name_b = "video_rtp"; + + std::string initiate_xml = InitiateXml( + initiator_protocol, + gingle_content_type, + content_name_a, content_type, + content_name_b, content_type); + std::string transport_info_a_xml = TransportInfo2Xml( + initiator_protocol, content_name_a, + channel_name_a, 0, 1); + std::string transport_info_b_xml = TransportInfo2Xml( + initiator_protocol, content_name_b, + channel_name_b, 2, 3); + std::string transport_info_reply_a_xml = TransportInfo2Xml( + resulting_protocol, content_name_a, + channel_name_a, 4, 5); + std::string transport_info_reply_b_xml = TransportInfo2Xml( + resulting_protocol, content_name_b, + channel_name_b, 6, 7); + std::string accept_xml = AcceptXml( + resulting_protocol, + gingle_content_type, + content_name_a, content_type, + content_name_b, content_type); + + TestSession(initiator_protocol, responder_protocol, resulting_protocol, + gingle_content_type, + content_type, + content_name_a, channel_name_a, + content_name_b, channel_name_b, + initiate_xml, + transport_info_a_xml, transport_info_b_xml, + transport_info_reply_a_xml, transport_info_reply_b_xml, + accept_xml); + } + + void TestBadRedirect(SignalingProtocol protocol) { + std::string content_name = "main"; + std::string content_type = "http://oink.splat/session"; + std::string channel_name_a = "chana"; + std::string channel_name_b = "chanb"; + std::string initiate_xml = InitiateXml( + protocol, content_name, content_type); + std::string transport_info_xml = TransportInfo4Xml( + protocol, content_name, + channel_name_a, 0, 1, + channel_name_b, 2, 3); + std::string transport_info_reply_xml = TransportInfo4Xml( + protocol, content_name, + channel_name_a, 4, 5, + channel_name_b, 6, 7); + std::string accept_xml = AcceptXml( + protocol, content_name, content_type); + std::string responder_full = kResponder + "/full"; + + rtc::scoped_ptr allocator( + new TestPortAllocator()); + int next_message_id = 0; + + rtc::scoped_ptr initiator( + new TestClient(allocator.get(), &next_message_id, + kInitiator, protocol, + content_type, + content_name, channel_name_a, + content_name, channel_name_b)); + + rtc::scoped_ptr responder( + new TestClient(allocator.get(), &next_message_id, + responder_full, protocol, + content_type, + content_name, channel_name_a, + content_name, channel_name_b)); + + // Create Session and check channels and state. + initiator->CreateSession(); + EXPECT_EQ(1U, initiator->session_created_count); + EXPECT_EQ(kSessionId, initiator->session->id()); + EXPECT_EQ(initiator->session->local_name(), kInitiator); + EXPECT_EQ(cricket::BaseSession::STATE_INIT, + initiator->session_state()); + + EXPECT_TRUE(initiator->HasChannel(content_name, 1)); + EXPECT_TRUE(initiator->HasChannel(content_name, 2)); + + // Initiate and expect initiate message sent. + cricket::SessionDescription* offer = NewTestSessionDescription( + content_name, content_type); + EXPECT_TRUE(initiator->session->Initiate(kResponder, offer)); + EXPECT_EQ(initiator->session->remote_name(), kResponder); + EXPECT_EQ(initiator->session->local_description(), offer); + + EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout); + EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE, + initiator->session_state()); + initiator->ExpectSentStanza( + IqSet("0", kInitiator, kResponder, initiate_xml)); + + // Expect transport-info message from initiator. + initiator->DeliverAckToLastStanza(); + initiator->PrepareCandidates(); + EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout); + initiator->ExpectSentStanza( + IqSet("1", kInitiator, kResponder, transport_info_xml)); + + // Send an unauthorized redirect to the initiator and expect it be ignored. + initiator->blow_up_on_error = false; + const buzz::XmlElement* initiate_stanza = initiator->stanza(); + rtc::scoped_ptr redirect_stanza( + buzz::XmlElement::ForStr( + IqError("ER", kResponder, kInitiator, + RedirectXml(protocol, initiate_xml, "not@allowed.com")))); + initiator->session_manager->OnFailedSend( + initiate_stanza, redirect_stanza.get()); + EXPECT_EQ(initiator->session->remote_name(), kResponder); + initiator->blow_up_on_error = true; + EXPECT_EQ(initiator->error_count, 1); + } + + void TestGoodRedirect(SignalingProtocol protocol) { + std::string content_name = "main"; + std::string content_type = "http://oink.splat/session"; + std::string channel_name_a = "chana"; + std::string channel_name_b = "chanb"; + std::string initiate_xml = InitiateXml( + protocol, content_name, content_type); + std::string transport_info_xml = TransportInfo4Xml( + protocol, content_name, + channel_name_a, 0, 1, + channel_name_b, 2, 3); + std::string transport_info_reply_xml = TransportInfo4Xml( + protocol, content_name, + channel_name_a, 4, 5, + channel_name_b, 6, 7); + std::string accept_xml = AcceptXml( + protocol, content_name, content_type); + std::string responder_full = kResponder + "/full"; + + rtc::scoped_ptr allocator( + new TestPortAllocator()); + int next_message_id = 0; + + rtc::scoped_ptr initiator( + new TestClient(allocator.get(), &next_message_id, + kInitiator, protocol, + content_type, + content_name, channel_name_a, + content_name, channel_name_b)); + + rtc::scoped_ptr responder( + new TestClient(allocator.get(), &next_message_id, + responder_full, protocol, + content_type, + content_name, channel_name_a, + content_name, channel_name_b)); + + // Create Session and check channels and state. + initiator->CreateSession(); + EXPECT_EQ(1U, initiator->session_created_count); + EXPECT_EQ(kSessionId, initiator->session->id()); + EXPECT_EQ(initiator->session->local_name(), kInitiator); + EXPECT_EQ(cricket::BaseSession::STATE_INIT, + initiator->session_state()); + + EXPECT_TRUE(initiator->HasChannel(content_name, 1)); + EXPECT_TRUE(initiator->HasChannel(content_name, 2)); + + // Initiate and expect initiate message sent. + cricket::SessionDescription* offer = NewTestSessionDescription( + content_name, content_type); + EXPECT_TRUE(initiator->session->Initiate(kResponder, offer)); + EXPECT_EQ(initiator->session->remote_name(), kResponder); + EXPECT_EQ(initiator->session->local_description(), offer); + + EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout); + EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE, + initiator->session_state()); + initiator->ExpectSentStanza( + IqSet("0", kInitiator, kResponder, initiate_xml)); + + // Expect transport-info message from initiator. + initiator->DeliverAckToLastStanza(); + initiator->PrepareCandidates(); + EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout); + initiator->ExpectSentStanza( + IqSet("1", kInitiator, kResponder, transport_info_xml)); + + // Send a redirect to the initiator and expect all of the message + // to be resent. + const buzz::XmlElement* initiate_stanza = initiator->stanza(); + rtc::scoped_ptr redirect_stanza( + buzz::XmlElement::ForStr( + IqError("ER2", kResponder, kInitiator, + RedirectXml(protocol, initiate_xml, responder_full)))); + initiator->session_manager->OnFailedSend( + initiate_stanza, redirect_stanza.get()); + EXPECT_EQ(initiator->session->remote_name(), responder_full); + + EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout); + initiator->ExpectSentStanza( + IqSet("2", kInitiator, responder_full, initiate_xml)); + initiator->ExpectSentStanza( + IqSet("3", kInitiator, responder_full, transport_info_xml)); + + // Deliver the initiate. Expect ack and session created with + // transports. + responder->DeliverStanza( + IqSet("2", kInitiator, responder_full, initiate_xml)); + responder->ExpectSentStanza( + IqAck("2", responder_full, kInitiator)); + EXPECT_EQ(0U, responder->sent_stanza_count()); + + EXPECT_EQ(1U, responder->session_created_count); + EXPECT_EQ(kSessionId, responder->session->id()); + EXPECT_EQ(responder->session->local_name(), responder_full); + EXPECT_EQ(responder->session->remote_name(), kInitiator); + EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE, + responder->session_state()); + + EXPECT_TRUE(responder->HasChannel(content_name, 1)); + EXPECT_TRUE(responder->HasChannel(content_name, 2)); + + // Deliver transport-info and expect ack. + responder->DeliverStanza( + IqSet("3", kInitiator, responder_full, transport_info_xml)); + responder->ExpectSentStanza( + IqAck("3", responder_full, kInitiator)); + + // Expect reply transport-infos sent to new remote JID + responder->PrepareCandidates(); + EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout); + responder->ExpectSentStanza( + IqSet("4", responder_full, kInitiator, transport_info_reply_xml)); + + initiator->DeliverStanza(responder->stanza()); + initiator->ExpectSentStanza( + IqAck("4", kInitiator, responder_full)); + + // The channels should be able to become writable at this point. This + // requires pinging, so it may take a little while. + EXPECT_TRUE_WAIT(initiator->chan_a->writable() && + initiator->chan_a->readable(), kEventTimeout); + EXPECT_TRUE_WAIT(initiator->chan_b->writable() && + initiator->chan_b->readable(), kEventTimeout); + EXPECT_TRUE_WAIT(responder->chan_a->writable() && + responder->chan_a->readable(), kEventTimeout); + EXPECT_TRUE_WAIT(responder->chan_b->writable() && + responder->chan_b->readable(), kEventTimeout); + + // Accept the session and expect accept stanza. + cricket::SessionDescription* answer = NewTestSessionDescription( + content_name, content_type); + EXPECT_TRUE(responder->session->Accept(answer)); + EXPECT_EQ(responder->session->local_description(), answer); + + responder->ExpectSentStanza( + IqSet("5", responder_full, kInitiator, accept_xml)); + EXPECT_EQ(0U, responder->sent_stanza_count()); + + // Deliver the accept message and expect an ack. + initiator->DeliverStanza(responder->stanza()); + EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout); + initiator->ExpectSentStanza( + IqAck("5", kInitiator, responder_full)); + EXPECT_EQ(0U, initiator->sent_stanza_count()); + + // Both sessions should be in progress and have functioning + // channels. + EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS, + initiator->session_state(), kEventTimeout); + EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS, + responder->session_state(), kEventTimeout); + TestSendRecv(initiator->chan_a.get(), initiator->chan_b.get(), + responder->chan_a.get(), responder->chan_b.get()); + } + + void TestCandidatesInInitiateAndAccept(const std::string& test_name) { + std::string content_name = "main"; + std::string content_type = "http://oink.splat/session"; + std::string channel_name_a = "rtp"; + std::string channel_name_b = "rtcp"; + cricket::SignalingProtocol protocol = PROTOCOL_JINGLE; + + rtc::scoped_ptr allocator( + new TestPortAllocator()); + int next_message_id = 0; + + rtc::scoped_ptr initiator( + new TestClient(allocator.get(), &next_message_id, + kInitiator, protocol, + content_type, + content_name, channel_name_a, + content_name, channel_name_b)); + + rtc::scoped_ptr responder( + new TestClient(allocator.get(), &next_message_id, + kResponder, protocol, + content_type, + content_name, channel_name_a, + content_name, channel_name_b)); + + // Create Session and check channels and state. + initiator->CreateSession(); + EXPECT_TRUE(initiator->HasTransport(content_name)); + EXPECT_TRUE(initiator->HasChannel(content_name, 1)); + EXPECT_TRUE(initiator->HasTransport(content_name)); + EXPECT_TRUE(initiator->HasChannel(content_name, 2)); + + // Initiate and expect initiate message sent. + cricket::SessionDescription* offer = NewTestSessionDescription( + content_name, content_type); + EXPECT_TRUE(initiator->session->Initiate(kResponder, offer)); + + EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout); + EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE, + initiator->session_state()); + initiator->ExpectSentStanza( + IqSet("0", kInitiator, kResponder, + InitiateXml(protocol, content_name, content_type))); + + // Fake the delivery the initiate and candidates together. + responder->DeliverStanza( + IqSet("A", kInitiator, kResponder, + JingleInitiateActionXml( + JingleContentXml( + content_name, content_type, kTransportType, + P2pCandidateXml(channel_name_a, 0) + + P2pCandidateXml(channel_name_a, 1) + + P2pCandidateXml(channel_name_b, 2) + + P2pCandidateXml(channel_name_b, 3))))); + responder->ExpectSentStanza( + IqAck("A", kResponder, kInitiator)); + EXPECT_EQ(0U, responder->sent_stanza_count()); + + EXPECT_EQ(1U, responder->session_created_count); + EXPECT_EQ(kSessionId, responder->session->id()); + EXPECT_EQ(responder->session->local_name(), kResponder); + EXPECT_EQ(responder->session->remote_name(), kInitiator); + EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE, + responder->session_state()); + + EXPECT_TRUE(responder->HasTransport(content_name)); + EXPECT_TRUE(responder->HasChannel(content_name, 1)); + EXPECT_TRUE(responder->HasTransport(content_name)); + EXPECT_TRUE(responder->HasChannel(content_name, 2)); + + // Expect transport-info message from initiator. + // But don't send candidates until initiate ack is received. + initiator->DeliverAckToLastStanza(); + initiator->PrepareCandidates(); + EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout); + initiator->ExpectSentStanza( + IqSet("1", kInitiator, kResponder, + TransportInfo4Xml(protocol, content_name, + channel_name_a, 0, 1, + channel_name_b, 2, 3))); + + responder->PrepareCandidates(); + EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout); + responder->ExpectSentStanza( + IqSet("2", kResponder, kInitiator, + TransportInfo4Xml(protocol, content_name, + channel_name_a, 4, 5, + channel_name_b, 6, 7))); + + // Accept the session and expect accept stanza. + cricket::SessionDescription* answer = NewTestSessionDescription( + content_name, content_type); + EXPECT_TRUE(responder->session->Accept(answer)); + + responder->ExpectSentStanza( + IqSet("3", kResponder, kInitiator, + AcceptXml(protocol, content_name, content_type))); + EXPECT_EQ(0U, responder->sent_stanza_count()); + + // Fake the delivery the accept and candidates together. + initiator->DeliverStanza( + IqSet("B", kResponder, kInitiator, + JingleActionXml("session-accept", + JingleContentXml( + content_name, content_type, kTransportType, + P2pCandidateXml(channel_name_a, 4) + + P2pCandidateXml(channel_name_a, 5) + + P2pCandidateXml(channel_name_b, 6) + + P2pCandidateXml(channel_name_b, 7))))); + EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout); + initiator->ExpectSentStanza( + IqAck("B", kInitiator, kResponder)); + EXPECT_EQ(0U, initiator->sent_stanza_count()); + + // The channels should be able to become writable at this point. This + // requires pinging, so it may take a little while. + EXPECT_TRUE_WAIT(initiator->chan_a->writable() && + initiator->chan_a->readable(), kEventTimeout); + EXPECT_TRUE_WAIT(initiator->chan_b->writable() && + initiator->chan_b->readable(), kEventTimeout); + EXPECT_TRUE_WAIT(responder->chan_a->writable() && + responder->chan_a->readable(), kEventTimeout); + EXPECT_TRUE_WAIT(responder->chan_b->writable() && + responder->chan_b->readable(), kEventTimeout); + + + // Both sessions should be in progress and have functioning + // channels. + EXPECT_EQ(protocol, initiator->session->current_protocol()); + EXPECT_EQ(protocol, responder->session->current_protocol()); + EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS, + initiator->session_state(), kEventTimeout); + EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS, + responder->session_state(), kEventTimeout); + TestSendRecv(initiator->chan_a.get(), initiator->chan_b.get(), + responder->chan_a.get(), responder->chan_b.get()); + } + + // Tests that when an initiator terminates right after initiate, + // everything behaves correctly. + void TestEarlyTerminationFromInitiator(SignalingProtocol protocol) { + std::string content_name = "main"; + std::string content_type = "http://oink.splat/session"; + + rtc::scoped_ptr allocator( + new TestPortAllocator()); + int next_message_id = 0; + + rtc::scoped_ptr initiator( + new TestClient(allocator.get(), &next_message_id, + kInitiator, protocol, + content_type, + content_name, "a", + content_name, "b")); + + rtc::scoped_ptr responder( + new TestClient(allocator.get(), &next_message_id, + kResponder, protocol, + content_type, + content_name, "a", + content_name, "b")); + + // Send initiate + initiator->CreateSession(); + EXPECT_TRUE(initiator->session->Initiate( + kResponder, NewTestSessionDescription(content_name, content_type))); + initiator->ExpectSentStanza( + IqSet("0", kInitiator, kResponder, + InitiateXml(protocol, content_name, content_type))); + EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE, + initiator->session_state()); + + responder->DeliverStanza(initiator->stanza()); + responder->ExpectSentStanza( + IqAck("0", kResponder, kInitiator)); + EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE, + responder->session_state()); + + initiator->session->TerminateWithReason(cricket::STR_TERMINATE_ERROR); + initiator->ExpectSentStanza( + IqSet("1", kInitiator, kResponder, + TerminateXml(protocol, cricket::STR_TERMINATE_ERROR))); + EXPECT_EQ(cricket::BaseSession::STATE_SENTTERMINATE, + initiator->session_state()); + + responder->DeliverStanza(initiator->stanza()); + responder->ExpectSentStanza( + IqAck("1", kResponder, kInitiator)); + EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDTERMINATE, + responder->session_state()); + } + + // Tests that when the responder rejects, everything behaves + // correctly. + void TestRejection(SignalingProtocol protocol) { + std::string content_name = "main"; + std::string content_type = "http://oink.splat/session"; + + rtc::scoped_ptr allocator( + new TestPortAllocator()); + int next_message_id = 0; + + rtc::scoped_ptr initiator( + new TestClient(allocator.get(), &next_message_id, + kInitiator, protocol, + content_type, + content_name, "a", + content_name, "b")); + + // Send initiate + initiator->CreateSession(); + EXPECT_TRUE(initiator->session->Initiate( + kResponder, NewTestSessionDescription(content_name, content_type))); + initiator->ExpectSentStanza( + IqSet("0", kInitiator, kResponder, + InitiateXml(protocol, content_name, content_type))); + EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE, + initiator->session_state()); + + initiator->DeliverStanza( + IqSet("1", kResponder, kInitiator, + RejectXml(protocol, cricket::STR_TERMINATE_ERROR))); + initiator->ExpectSentStanza( + IqAck("1", kInitiator, kResponder)); + if (protocol == PROTOCOL_JINGLE) { + EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDTERMINATE, + initiator->session_state()); + } else { + EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDREJECT, + initiator->session_state()); + } + } + + void TestTransportMux() { + SignalingProtocol initiator_protocol = PROTOCOL_JINGLE; + SignalingProtocol responder_protocol = PROTOCOL_JINGLE; + SignalingProtocol resulting_protocol = PROTOCOL_JINGLE; + std::string content_type = cricket::NS_JINGLE_RTP; + std::string gingle_content_type = cricket::NS_GINGLE_VIDEO; + std::string content_name_a = cricket::CN_AUDIO; + std::string channel_name_a = "rtp"; + std::string content_name_b = cricket::CN_VIDEO; + std::string channel_name_b = "video_rtp"; + + std::string initiate_xml = InitiateXml( + initiator_protocol, + gingle_content_type, + content_name_a, content_type, + content_name_b, content_type, true); + std::string transport_info_a_xml = TransportInfo2Xml( + initiator_protocol, content_name_a, + channel_name_a, 0, 1); + std::string transport_info_b_xml = TransportInfo2Xml( + initiator_protocol, content_name_b, + channel_name_b, 2, 3); + std::string transport_info_reply_a_xml = TransportInfo2Xml( + resulting_protocol, content_name_a, + channel_name_a, 4, 5); + std::string transport_info_reply_b_xml = TransportInfo2Xml( + resulting_protocol, content_name_b, + channel_name_b, 6, 7); + std::string accept_xml = AcceptXml( + resulting_protocol, + gingle_content_type, + content_name_a, content_type, + content_name_b, content_type, true); + + TestSession(initiator_protocol, responder_protocol, resulting_protocol, + gingle_content_type, + content_type, + content_name_a, channel_name_a, + content_name_b, channel_name_b, + initiate_xml, + transport_info_a_xml, transport_info_b_xml, + transport_info_reply_a_xml, transport_info_reply_b_xml, + accept_xml, + true); + } + + void TestSendDescriptionInfo() { + rtc::scoped_ptr allocator( + new TestPortAllocator()); + int next_message_id = 0; + + std::string content_name = "content-name"; + std::string content_type = "content-type"; + rtc::scoped_ptr initiator( + new TestClient(allocator.get(), &next_message_id, + kInitiator, PROTOCOL_JINGLE, + content_type, + content_name, "", + "", "")); + + initiator->CreateSession(); + cricket::SessionDescription* offer = NewTestSessionDescription( + content_name, content_type); + std::string initiate_xml = InitiateXml( + PROTOCOL_JINGLE, content_name, content_type); + + cricket::ContentInfos contents; + TestContentDescription content(content_type, content_type); + contents.push_back( + cricket::ContentInfo(content_name, content_type, &content)); + std::string description_info_xml = JingleDescriptionInfoXml( + content_name, content_type); + + EXPECT_TRUE(initiator->session->Initiate(kResponder, offer)); + initiator->ExpectSentStanza( + IqSet("0", kInitiator, kResponder, initiate_xml)); + + EXPECT_TRUE(initiator->session->SendDescriptionInfoMessage(contents)); + initiator->ExpectSentStanza( + IqSet("1", kInitiator, kResponder, description_info_xml)); + } + + void DoTestSignalNewDescription( + TestClient* client, + cricket::BaseSession::State state, + cricket::ContentAction expected_content_action, + cricket::ContentSource expected_content_source) { + // Clean up before the new test. + client->new_local_description = false; + client->new_remote_description = false; + + client->SetSessionState(state); + EXPECT_EQ((expected_content_source == cricket::CS_LOCAL), + client->new_local_description); + EXPECT_EQ((expected_content_source == cricket::CS_REMOTE), + client->new_remote_description); + EXPECT_EQ(expected_content_action, client->last_content_action); + EXPECT_EQ(expected_content_source, client->last_content_source); + } + + void TestCallerSignalNewDescription() { + rtc::scoped_ptr allocator( + new TestPortAllocator()); + int next_message_id = 0; + + std::string content_name = "content-name"; + std::string content_type = "content-type"; + rtc::scoped_ptr initiator( + new TestClient(allocator.get(), &next_message_id, + kInitiator, PROTOCOL_JINGLE, + content_type, + content_name, "", + "", "")); + + initiator->CreateSession(); + + // send offer -> send update offer -> + // receive pr answer -> receive update pr answer -> + // receive answer + DoTestSignalNewDescription( + initiator.get(), cricket::BaseSession::STATE_SENTINITIATE, + cricket::CA_OFFER, cricket::CS_LOCAL); + + DoTestSignalNewDescription( + initiator.get(), cricket::BaseSession::STATE_SENTINITIATE, + cricket::CA_OFFER, cricket::CS_LOCAL); + + DoTestSignalNewDescription( + initiator.get(), cricket::BaseSession::STATE_RECEIVEDPRACCEPT, + cricket::CA_PRANSWER, cricket::CS_REMOTE); + + DoTestSignalNewDescription( + initiator.get(), cricket::BaseSession::STATE_RECEIVEDPRACCEPT, + cricket::CA_PRANSWER, cricket::CS_REMOTE); + + DoTestSignalNewDescription( + initiator.get(), cricket::BaseSession::STATE_RECEIVEDACCEPT, + cricket::CA_ANSWER, cricket::CS_REMOTE); + } + + void TestCalleeSignalNewDescription() { + rtc::scoped_ptr allocator( + new TestPortAllocator()); + int next_message_id = 0; + + std::string content_name = "content-name"; + std::string content_type = "content-type"; + rtc::scoped_ptr initiator( + new TestClient(allocator.get(), &next_message_id, + kInitiator, PROTOCOL_JINGLE, + content_type, + content_name, "", + "", "")); + + initiator->CreateSession(); + + // receive offer -> receive update offer -> + // send pr answer -> send update pr answer -> + // send answer + DoTestSignalNewDescription( + initiator.get(), cricket::BaseSession::STATE_RECEIVEDINITIATE, + cricket::CA_OFFER, cricket::CS_REMOTE); + + DoTestSignalNewDescription( + initiator.get(), cricket::BaseSession::STATE_RECEIVEDINITIATE, + cricket::CA_OFFER, cricket::CS_REMOTE); + + DoTestSignalNewDescription( + initiator.get(), cricket::BaseSession::STATE_SENTPRACCEPT, + cricket::CA_PRANSWER, cricket::CS_LOCAL); + + DoTestSignalNewDescription( + initiator.get(), cricket::BaseSession::STATE_SENTPRACCEPT, + cricket::CA_PRANSWER, cricket::CS_LOCAL); + + DoTestSignalNewDescription( + initiator.get(), cricket::BaseSession::STATE_SENTACCEPT, + cricket::CA_ANSWER, cricket::CS_LOCAL); + } + + void TestGetTransportStats() { + rtc::scoped_ptr allocator( + new TestPortAllocator()); + int next_message_id = 0; + + std::string content_name = "content-name"; + std::string content_type = "content-type"; + rtc::scoped_ptr initiator( + new TestClient(allocator.get(), &next_message_id, + kInitiator, PROTOCOL_JINGLE, + content_type, + content_name, "", + "", "")); + initiator->CreateSession(); + + cricket::SessionStats stats; + EXPECT_TRUE(initiator->session->GetStats(&stats)); + // At initiation, there are 2 transports. + EXPECT_EQ(2ul, stats.proxy_to_transport.size()); + EXPECT_EQ(2ul, stats.transport_stats.size()); + } +}; + +// For each of these, "X => Y = Z" means "if a client with protocol X +// initiates to a client with protocol Y, they end up speaking protocol Z. + +// Gingle => Gingle = Gingle (with other content) +TEST_F(SessionTest, GingleToGingleOtherContent) { + TestOtherContent(PROTOCOL_GINGLE, PROTOCOL_GINGLE, PROTOCOL_GINGLE); +} + +// Gingle => Gingle = Gingle (with audio content) +TEST_F(SessionTest, GingleToGingleAudioContent) { + TestAudioContent(PROTOCOL_GINGLE, PROTOCOL_GINGLE, PROTOCOL_GINGLE); +} + +// Gingle => Gingle = Gingle (with video contents) +TEST_F(SessionTest, GingleToGingleVideoContents) { + TestVideoContents(PROTOCOL_GINGLE, PROTOCOL_GINGLE, PROTOCOL_GINGLE); +} + +// Jingle => Jingle = Jingle (with other content) +TEST_F(SessionTest, JingleToJingleOtherContent) { + TestOtherContent(PROTOCOL_JINGLE, PROTOCOL_JINGLE, PROTOCOL_JINGLE); +} + +// Jingle => Jingle = Jingle (with audio content) +TEST_F(SessionTest, JingleToJingleAudioContent) { + TestAudioContent(PROTOCOL_JINGLE, PROTOCOL_JINGLE, PROTOCOL_JINGLE); +} + +// Jingle => Jingle = Jingle (with video contents) +TEST_F(SessionTest, JingleToJingleVideoContents) { + TestVideoContents(PROTOCOL_JINGLE, PROTOCOL_JINGLE, PROTOCOL_JINGLE); +} + +// Hybrid => Hybrid = Jingle (with other content) +TEST_F(SessionTest, HybridToHybridOtherContent) { + TestOtherContent(PROTOCOL_HYBRID, PROTOCOL_HYBRID, PROTOCOL_JINGLE); +} + +// Hybrid => Hybrid = Jingle (with audio content) +TEST_F(SessionTest, HybridToHybridAudioContent) { + TestAudioContent(PROTOCOL_HYBRID, PROTOCOL_HYBRID, PROTOCOL_JINGLE); +} + +// Hybrid => Hybrid = Jingle (with video contents) +TEST_F(SessionTest, HybridToHybridVideoContents) { + TestVideoContents(PROTOCOL_HYBRID, PROTOCOL_HYBRID, PROTOCOL_JINGLE); +} + +// Gingle => Hybrid = Gingle (with other content) +TEST_F(SessionTest, GingleToHybridOtherContent) { + TestOtherContent(PROTOCOL_GINGLE, PROTOCOL_HYBRID, PROTOCOL_GINGLE); +} + +// Gingle => Hybrid = Gingle (with audio content) +TEST_F(SessionTest, GingleToHybridAudioContent) { + TestAudioContent(PROTOCOL_GINGLE, PROTOCOL_HYBRID, PROTOCOL_GINGLE); +} + +// Gingle => Hybrid = Gingle (with video contents) +TEST_F(SessionTest, GingleToHybridVideoContents) { + TestVideoContents(PROTOCOL_GINGLE, PROTOCOL_HYBRID, PROTOCOL_GINGLE); +} + +// Jingle => Hybrid = Jingle (with other content) +TEST_F(SessionTest, JingleToHybridOtherContent) { + TestOtherContent(PROTOCOL_JINGLE, PROTOCOL_HYBRID, PROTOCOL_JINGLE); +} + +// Jingle => Hybrid = Jingle (with audio content) +TEST_F(SessionTest, JingleToHybridAudioContent) { + TestAudioContent(PROTOCOL_JINGLE, PROTOCOL_HYBRID, PROTOCOL_JINGLE); +} + +// Jingle => Hybrid = Jingle (with video contents) +TEST_F(SessionTest, JingleToHybridVideoContents) { + TestVideoContents(PROTOCOL_JINGLE, PROTOCOL_HYBRID, PROTOCOL_JINGLE); +} + +// Hybrid => Gingle = Gingle (with other content) +TEST_F(SessionTest, HybridToGingleOtherContent) { + TestOtherContent(PROTOCOL_HYBRID, PROTOCOL_GINGLE, PROTOCOL_GINGLE); +} + +// Hybrid => Gingle = Gingle (with audio content) +TEST_F(SessionTest, HybridToGingleAudioContent) { + TestAudioContent(PROTOCOL_HYBRID, PROTOCOL_GINGLE, PROTOCOL_GINGLE); +} + +// Hybrid => Gingle = Gingle (with video contents) +TEST_F(SessionTest, HybridToGingleVideoContents) { + TestVideoContents(PROTOCOL_HYBRID, PROTOCOL_GINGLE, PROTOCOL_GINGLE); +} + +// Hybrid => Jingle = Jingle (with other content) +TEST_F(SessionTest, HybridToJingleOtherContent) { + TestOtherContent(PROTOCOL_HYBRID, PROTOCOL_JINGLE, PROTOCOL_JINGLE); +} + +// Hybrid => Jingle = Jingle (with audio content) +TEST_F(SessionTest, HybridToJingleAudioContent) { + TestAudioContent(PROTOCOL_HYBRID, PROTOCOL_JINGLE, PROTOCOL_JINGLE); +} + +// Hybrid => Jingle = Jingle (with video contents) +TEST_F(SessionTest, HybridToJingleVideoContents) { + TestVideoContents(PROTOCOL_HYBRID, PROTOCOL_JINGLE, PROTOCOL_JINGLE); +} + +TEST_F(SessionTest, GingleEarlyTerminationFromInitiator) { + TestEarlyTerminationFromInitiator(PROTOCOL_GINGLE); +} + +TEST_F(SessionTest, JingleEarlyTerminationFromInitiator) { + TestEarlyTerminationFromInitiator(PROTOCOL_JINGLE); +} + +TEST_F(SessionTest, HybridEarlyTerminationFromInitiator) { + TestEarlyTerminationFromInitiator(PROTOCOL_HYBRID); +} + +TEST_F(SessionTest, GingleRejection) { + TestRejection(PROTOCOL_GINGLE); +} + +TEST_F(SessionTest, JingleRejection) { + TestRejection(PROTOCOL_JINGLE); +} + +TEST_F(SessionTest, GingleGoodRedirect) { + TestGoodRedirect(PROTOCOL_GINGLE); +} + +TEST_F(SessionTest, JingleGoodRedirect) { + TestGoodRedirect(PROTOCOL_JINGLE); +} + +TEST_F(SessionTest, GingleBadRedirect) { + TestBadRedirect(PROTOCOL_GINGLE); +} + +TEST_F(SessionTest, JingleBadRedirect) { + TestBadRedirect(PROTOCOL_JINGLE); +} + +TEST_F(SessionTest, TestCandidatesInInitiateAndAccept) { + TestCandidatesInInitiateAndAccept("Candidates in initiate/accept"); +} + +TEST_F(SessionTest, TestTransportMux) { + TestTransportMux(); +} + +TEST_F(SessionTest, TestSendDescriptionInfo) { + TestSendDescriptionInfo(); +} + +TEST_F(SessionTest, TestCallerSignalNewDescription) { + TestCallerSignalNewDescription(); +} + +TEST_F(SessionTest, TestCalleeSignalNewDescription) { + TestCalleeSignalNewDescription(); +} + +TEST_F(SessionTest, TestGetTransportStats) { + TestGetTransportStats(); +} diff --git a/webrtc/p2p/base/sessionclient.h b/webrtc/p2p/base/sessionclient.h new file mode 100644 index 000000000..896878858 --- /dev/null +++ b/webrtc/p2p/base/sessionclient.h @@ -0,0 +1,78 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_SESSIONCLIENT_H_ +#define WEBRTC_P2P_BASE_SESSIONCLIENT_H_ + +#include "webrtc/p2p/base/constants.h" + +namespace buzz { +class XmlElement; +} + +namespace cricket { + +struct ParseError; +class Session; +class ContentDescription; + +class ContentParser { + public: + virtual bool ParseContent(SignalingProtocol protocol, + const buzz::XmlElement* elem, + ContentDescription** content, + ParseError* error) = 0; + // If not IsWriteable, then a given content should be "skipped" when + // writing in the given protocol, as if it didn't exist. We assume + // most things are writeable. We do this to avoid strange cases + // like data contents in Gingle, which aren't writable. + virtual bool IsWritable(SignalingProtocol protocol, + const ContentDescription* content) { + return true; + } + virtual bool WriteContent(SignalingProtocol protocol, + const ContentDescription* content, + buzz::XmlElement** elem, + WriteError* error) = 0; + virtual ~ContentParser() {} +}; + +// A SessionClient exists in 1-1 relation with each session. The implementor +// of this interface is the one that understands *what* the two sides are +// trying to send to one another. The lower-level layers only know how to send +// data; they do not know what is being sent. +class SessionClient : public ContentParser { + public: + // Notifies the client of the creation / destruction of sessions of this type. + // + // IMPORTANT: The SessionClient, in its handling of OnSessionCreate, must + // create whatever channels are indicate in the description. This is because + // the remote client may already be attempting to connect those channels. If + // we do not create our channel right away, then connection may fail or be + // delayed. + virtual void OnSessionCreate(Session* session, bool received_initiate) = 0; + virtual void OnSessionDestroy(Session* session) = 0; + + virtual bool ParseContent(SignalingProtocol protocol, + const buzz::XmlElement* elem, + ContentDescription** content, + ParseError* error) = 0; + virtual bool WriteContent(SignalingProtocol protocol, + const ContentDescription* content, + buzz::XmlElement** elem, + WriteError* error) = 0; + protected: + // The SessionClient interface explicitly does not include destructor + virtual ~SessionClient() { } +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_SESSIONCLIENT_H_ diff --git a/webrtc/p2p/base/sessiondescription.cc b/webrtc/p2p/base/sessiondescription.cc new file mode 100644 index 000000000..b05dc5108 --- /dev/null +++ b/webrtc/p2p/base/sessiondescription.cc @@ -0,0 +1,222 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/sessiondescription.h" + +#include "webrtc/libjingle/xmllite/xmlelement.h" + +namespace cricket { + +ContentInfo* FindContentInfoByName( + ContentInfos& contents, const std::string& name) { + for (ContentInfos::iterator content = contents.begin(); + content != contents.end(); ++content) { + if (content->name == name) { + return &(*content); + } + } + return NULL; +} + +const ContentInfo* FindContentInfoByName( + const ContentInfos& contents, const std::string& name) { + for (ContentInfos::const_iterator content = contents.begin(); + content != contents.end(); ++content) { + if (content->name == name) { + return &(*content); + } + } + return NULL; +} + +const ContentInfo* FindContentInfoByType( + const ContentInfos& contents, const std::string& type) { + for (ContentInfos::const_iterator content = contents.begin(); + content != contents.end(); ++content) { + if (content->type == type) { + return &(*content); + } + } + return NULL; +} + +const std::string* ContentGroup::FirstContentName() const { + return (!content_names_.empty()) ? &(*content_names_.begin()) : NULL; +} + +bool ContentGroup::HasContentName(const std::string& content_name) const { + return (std::find(content_names_.begin(), content_names_.end(), + content_name) != content_names_.end()); +} + +void ContentGroup::AddContentName(const std::string& content_name) { + if (!HasContentName(content_name)) { + content_names_.push_back(content_name); + } +} + +bool ContentGroup::RemoveContentName(const std::string& content_name) { + ContentNames::iterator iter = std::find( + content_names_.begin(), content_names_.end(), content_name); + if (iter == content_names_.end()) { + return false; + } + content_names_.erase(iter); + return true; +} + +SessionDescription* SessionDescription::Copy() const { + SessionDescription* copy = new SessionDescription(*this); + // Copy all ContentDescriptions. + for (ContentInfos::iterator content = copy->contents_.begin(); + content != copy->contents().end(); ++content) { + content->description = content->description->Copy(); + } + return copy; +} + +const ContentInfo* SessionDescription::GetContentByName( + const std::string& name) const { + return FindContentInfoByName(contents_, name); +} + +ContentInfo* SessionDescription::GetContentByName( + const std::string& name) { + return FindContentInfoByName(contents_, name); +} + +const ContentDescription* SessionDescription::GetContentDescriptionByName( + const std::string& name) const { + const ContentInfo* cinfo = FindContentInfoByName(contents_, name); + if (cinfo == NULL) { + return NULL; + } + + return cinfo->description; +} + +ContentDescription* SessionDescription::GetContentDescriptionByName( + const std::string& name) { + ContentInfo* cinfo = FindContentInfoByName(contents_, name); + if (cinfo == NULL) { + return NULL; + } + + return cinfo->description; +} + +const ContentInfo* SessionDescription::FirstContentByType( + const std::string& type) const { + return FindContentInfoByType(contents_, type); +} + +const ContentInfo* SessionDescription::FirstContent() const { + return (contents_.empty()) ? NULL : &(*contents_.begin()); +} + +void SessionDescription::AddContent(const std::string& name, + const std::string& type, + ContentDescription* description) { + contents_.push_back(ContentInfo(name, type, description)); +} + +void SessionDescription::AddContent(const std::string& name, + const std::string& type, + bool rejected, + ContentDescription* description) { + contents_.push_back(ContentInfo(name, type, rejected, description)); +} + +bool SessionDescription::RemoveContentByName(const std::string& name) { + for (ContentInfos::iterator content = contents_.begin(); + content != contents_.end(); ++content) { + if (content->name == name) { + delete content->description; + contents_.erase(content); + return true; + } + } + + return false; +} + +bool SessionDescription::AddTransportInfo(const TransportInfo& transport_info) { + if (GetTransportInfoByName(transport_info.content_name) != NULL) { + return false; + } + transport_infos_.push_back(transport_info); + return true; +} + +bool SessionDescription::RemoveTransportInfoByName(const std::string& name) { + for (TransportInfos::iterator transport_info = transport_infos_.begin(); + transport_info != transport_infos_.end(); ++transport_info) { + if (transport_info->content_name == name) { + transport_infos_.erase(transport_info); + return true; + } + } + return false; +} + +const TransportInfo* SessionDescription::GetTransportInfoByName( + const std::string& name) const { + for (TransportInfos::const_iterator iter = transport_infos_.begin(); + iter != transport_infos_.end(); ++iter) { + if (iter->content_name == name) { + return &(*iter); + } + } + return NULL; +} + +TransportInfo* SessionDescription::GetTransportInfoByName( + const std::string& name) { + for (TransportInfos::iterator iter = transport_infos_.begin(); + iter != transport_infos_.end(); ++iter) { + if (iter->content_name == name) { + return &(*iter); + } + } + return NULL; +} + +void SessionDescription::RemoveGroupByName(const std::string& name) { + for (ContentGroups::iterator iter = content_groups_.begin(); + iter != content_groups_.end(); ++iter) { + if (iter->semantics() == name) { + content_groups_.erase(iter); + break; + } + } +} + +bool SessionDescription::HasGroup(const std::string& name) const { + for (ContentGroups::const_iterator iter = content_groups_.begin(); + iter != content_groups_.end(); ++iter) { + if (iter->semantics() == name) { + return true; + } + } + return false; +} + +const ContentGroup* SessionDescription::GetGroupByName( + const std::string& name) const { + for (ContentGroups::const_iterator iter = content_groups_.begin(); + iter != content_groups_.end(); ++iter) { + if (iter->semantics() == name) { + return &(*iter); + } + } + return NULL; +} + +} // namespace cricket diff --git a/webrtc/p2p/base/sessiondescription.h b/webrtc/p2p/base/sessiondescription.h new file mode 100644 index 000000000..1182a6774 --- /dev/null +++ b/webrtc/p2p/base/sessiondescription.h @@ -0,0 +1,185 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_SESSIONDESCRIPTION_H_ +#define WEBRTC_P2P_BASE_SESSIONDESCRIPTION_H_ + +#include +#include + +#include "webrtc/p2p/base/transportinfo.h" +#include "webrtc/base/constructormagic.h" + +namespace cricket { + +// Describes a session content. Individual content types inherit from +// this class. Analagous to a or +// . +class ContentDescription { + public: + virtual ~ContentDescription() {} + virtual ContentDescription* Copy() const = 0; +}; + +// Analagous to a or . +// name = name of +// type = xmlns of +struct ContentInfo { + ContentInfo() : description(NULL) {} + ContentInfo(const std::string& name, + const std::string& type, + ContentDescription* description) : + name(name), type(type), rejected(false), description(description) {} + ContentInfo(const std::string& name, + const std::string& type, + bool rejected, + ContentDescription* description) : + name(name), type(type), rejected(rejected), description(description) {} + std::string name; + std::string type; + bool rejected; + ContentDescription* description; +}; + +typedef std::vector ContentNames; + +// This class provides a mechanism to aggregate different media contents into a +// group. This group can also be shared with the peers in a pre-defined format. +// GroupInfo should be populated only with the |content_name| of the +// MediaDescription. +class ContentGroup { + public: + explicit ContentGroup(const std::string& semantics) : + semantics_(semantics) {} + + const std::string& semantics() const { return semantics_; } + const ContentNames& content_names() const { return content_names_; } + + const std::string* FirstContentName() const; + bool HasContentName(const std::string& content_name) const; + void AddContentName(const std::string& content_name); + bool RemoveContentName(const std::string& content_name); + + private: + std::string semantics_; + ContentNames content_names_; +}; + +typedef std::vector ContentInfos; +typedef std::vector ContentGroups; + +const ContentInfo* FindContentInfoByName( + const ContentInfos& contents, const std::string& name); +const ContentInfo* FindContentInfoByType( + const ContentInfos& contents, const std::string& type); + +// Describes a collection of contents, each with its own name and +// type. Analogous to a or stanza. Assumes that +// contents are unique be name, but doesn't enforce that. +class SessionDescription { + public: + SessionDescription() {} + explicit SessionDescription(const ContentInfos& contents) : + contents_(contents) {} + SessionDescription(const ContentInfos& contents, + const ContentGroups& groups) : + contents_(contents), + content_groups_(groups) {} + SessionDescription(const ContentInfos& contents, + const TransportInfos& transports, + const ContentGroups& groups) : + contents_(contents), + transport_infos_(transports), + content_groups_(groups) {} + ~SessionDescription() { + for (ContentInfos::iterator content = contents_.begin(); + content != contents_.end(); ++content) { + delete content->description; + } + } + + SessionDescription* Copy() const; + + // Content accessors. + const ContentInfos& contents() const { return contents_; } + ContentInfos& contents() { return contents_; } + const ContentInfo* GetContentByName(const std::string& name) const; + ContentInfo* GetContentByName(const std::string& name); + const ContentDescription* GetContentDescriptionByName( + const std::string& name) const; + ContentDescription* GetContentDescriptionByName(const std::string& name); + const ContentInfo* FirstContentByType(const std::string& type) const; + const ContentInfo* FirstContent() const; + + // Content mutators. + // Adds a content to this description. Takes ownership of ContentDescription*. + void AddContent(const std::string& name, + const std::string& type, + ContentDescription* description); + void AddContent(const std::string& name, + const std::string& type, + bool rejected, + ContentDescription* description); + bool RemoveContentByName(const std::string& name); + + // Transport accessors. + const TransportInfos& transport_infos() const { return transport_infos_; } + TransportInfos& transport_infos() { return transport_infos_; } + const TransportInfo* GetTransportInfoByName( + const std::string& name) const; + TransportInfo* GetTransportInfoByName(const std::string& name); + const TransportDescription* GetTransportDescriptionByName( + const std::string& name) const { + const TransportInfo* tinfo = GetTransportInfoByName(name); + return tinfo ? &tinfo->description : NULL; + } + + // Transport mutators. + void set_transport_infos(const TransportInfos& transport_infos) { + transport_infos_ = transport_infos; + } + // Adds a TransportInfo to this description. + // Returns false if a TransportInfo with the same name already exists. + bool AddTransportInfo(const TransportInfo& transport_info); + bool RemoveTransportInfoByName(const std::string& name); + + // Group accessors. + const ContentGroups& groups() const { return content_groups_; } + const ContentGroup* GetGroupByName(const std::string& name) const; + bool HasGroup(const std::string& name) const; + + // Group mutators. + void AddGroup(const ContentGroup& group) { content_groups_.push_back(group); } + // Remove the first group with the same semantics specified by |name|. + void RemoveGroupByName(const std::string& name); + + private: + ContentInfos contents_; + TransportInfos transport_infos_; + ContentGroups content_groups_; +}; + +// Indicates whether a ContentDescription was an offer or an answer, as +// described in http://www.ietf.org/rfc/rfc3264.txt. CA_UPDATE +// indicates a jingle update message which contains a subset of a full +// session description +enum ContentAction { + CA_OFFER, CA_PRANSWER, CA_ANSWER, CA_UPDATE +}; + +// Indicates whether a ContentDescription was sent by the local client +// or received from the remote client. +enum ContentSource { + CS_LOCAL, CS_REMOTE +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_SESSIONDESCRIPTION_H_ diff --git a/webrtc/p2p/base/sessionid.h b/webrtc/p2p/base/sessionid.h new file mode 100644 index 000000000..f69570039 --- /dev/null +++ b/webrtc/p2p/base/sessionid.h @@ -0,0 +1,20 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_SESSIONID_H_ +#define WEBRTC_P2P_BASE_SESSIONID_H_ + +// TODO: Remove this file. + +namespace cricket { + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_SESSIONID_H_ diff --git a/webrtc/p2p/base/sessionmanager.cc b/webrtc/p2p/base/sessionmanager.cc new file mode 100644 index 000000000..f375dea5c --- /dev/null +++ b/webrtc/p2p/base/sessionmanager.cc @@ -0,0 +1,309 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/sessionmanager.h" + +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/session.h" +#include "webrtc/p2p/base/sessionmessages.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/jid.h" +#include "webrtc/base/common.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/stringencode.h" + +namespace cricket { + +SessionManager::SessionManager(PortAllocator *allocator, + rtc::Thread *worker) { + allocator_ = allocator; + signaling_thread_ = rtc::Thread::Current(); + if (worker == NULL) { + worker_thread_ = rtc::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(); + SignalDestroyed(); +} + +void SessionManager::AddClient(const std::string& content_type, + SessionClient* client) { + ASSERT(client_map_.find(content_type) == client_map_.end()); + client_map_[content_type] = client; +} + +void SessionManager::RemoveClient(const std::string& content_type) { + ClientMap::iterator iter = client_map_.find(content_type); + ASSERT(iter != client_map_.end()); + client_map_.erase(iter); +} + +SessionClient* SessionManager::GetClient(const std::string& content_type) { + ClientMap::iterator iter = client_map_.find(content_type); + return (iter != client_map_.end()) ? iter->second : NULL; +} + +Session* SessionManager::CreateSession(const std::string& local_name, + const std::string& content_type) { + std::string id; + return CreateSession(id, local_name, content_type); +} + +Session* SessionManager::CreateSession(const std::string& id, + const std::string& local_name, + const std::string& content_type) { + std::string sid = + id.empty() ? rtc::ToString(rtc::CreateRandomId64()) : id; + return CreateSession(local_name, local_name, sid, content_type, false); +} + +Session* SessionManager::CreateSession( + const std::string& local_name, const std::string& initiator_name, + const std::string& sid, const std::string& content_type, + bool received_initiate) { + SessionClient* client = GetClient(content_type); + ASSERT(client != NULL); + + Session* session = new Session(this, local_name, initiator_name, + sid, content_type, client); + session->SetIdentity(transport_desc_factory_.identity()); + session_map_[session->id()] = session; + session->SignalRequestSignaling.connect( + this, &SessionManager::OnRequestSignaling); + session->SignalOutgoingMessage.connect( + this, &SessionManager::OnOutgoingMessage); + session->SignalErrorMessage.connect(this, &SessionManager::OnErrorMessage); + SignalSessionCreate(session, received_initiate); + session->client()->OnSessionCreate(session, received_initiate); + return session; +} + +void SessionManager::DestroySession(Session* session) { + if (session != NULL) { + SessionMap::iterator it = session_map_.find(session->id()); + if (it != session_map_.end()) { + SignalSessionDestroy(session); + session->client()->OnSessionDestroy(session); + session_map_.erase(it); + delete session; + } + } +} + +Session* SessionManager::GetSession(const std::string& sid) { + SessionMap::iterator it = session_map_.find(sid); + 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(); + } +} + +bool SessionManager::IsSessionMessage(const buzz::XmlElement* stanza) { + return cricket::IsSessionMessage(stanza); +} + +Session* SessionManager::FindSession(const std::string& sid, + const std::string& remote_name) { + SessionMap::iterator iter = session_map_.find(sid); + if (iter == session_map_.end()) + return NULL; + + Session* session = iter->second; + if (buzz::Jid(remote_name) != buzz::Jid(session->remote_name())) + return NULL; + + return session; +} + +void SessionManager::OnIncomingMessage(const buzz::XmlElement* stanza) { + SessionMessage msg; + ParseError error; + + if (!ParseSessionMessage(stanza, &msg, &error)) { + SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify", + error.text, NULL); + return; + } + + Session* session = FindSession(msg.sid, msg.from); + if (session) { + session->OnIncomingMessage(msg); + return; + } + if (msg.type != ACTION_SESSION_INITIATE) { + SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify", + "unknown session", NULL); + return; + } + + std::string content_type; + if (!ParseContentType(msg.protocol, msg.action_elem, + &content_type, &error)) { + SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify", + error.text, NULL); + return; + } + + if (!GetClient(content_type)) { + SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify", + "unknown content type: " + content_type, NULL); + return; + } + + session = CreateSession(msg.to, msg.initiator, msg.sid, + content_type, true); + session->OnIncomingMessage(msg); +} + +void SessionManager::OnIncomingResponse(const buzz::XmlElement* orig_stanza, + const buzz::XmlElement* response_stanza) { + if (orig_stanza == NULL || response_stanza == NULL) { + return; + } + + SessionMessage msg; + ParseError error; + if (!ParseSessionMessage(orig_stanza, &msg, &error)) { + LOG(LS_WARNING) << "Error parsing incoming response: " << error.text + << ":" << orig_stanza; + return; + } + + Session* session = FindSession(msg.sid, msg.to); + if (!session) { + // Also try the QN_FROM in the response stanza, in case we sent the request + // to a bare JID but got the response from a full JID. + std::string ack_from = response_stanza->Attr(buzz::QN_FROM); + session = FindSession(msg.sid, ack_from); + } + if (session) { + session->OnIncomingResponse(orig_stanza, response_stanza, msg); + } +} + +void SessionManager::OnFailedSend(const buzz::XmlElement* orig_stanza, + const buzz::XmlElement* error_stanza) { + SessionMessage msg; + ParseError error; + if (!ParseSessionMessage(orig_stanza, &msg, &error)) { + return; // TODO: log somewhere? + } + + Session* session = FindSession(msg.sid, msg.to); + if (session) { + rtc::scoped_ptr synthetic_error; + if (!error_stanza) { + // A failed send is semantically equivalent to an error response, so we + // can just turn the former into the latter. + synthetic_error.reset( + CreateErrorMessage(orig_stanza, buzz::QN_STANZA_ITEM_NOT_FOUND, + "cancel", "Recipient did not respond", NULL)); + error_stanza = synthetic_error.get(); + } + + session->OnFailedSend(orig_stanza, error_stanza); + } +} + +void SessionManager::SendErrorMessage(const buzz::XmlElement* stanza, + const buzz::QName& name, + const std::string& type, + const std::string& text, + const buzz::XmlElement* extra_info) { + rtc::scoped_ptr msg( + CreateErrorMessage(stanza, name, type, text, extra_info)); + SignalOutgoingMessage(this, msg.get()); +} + +buzz::XmlElement* SessionManager::CreateErrorMessage( + const buzz::XmlElement* stanza, + const buzz::QName& name, + const std::string& type, + const std::string& text, + const buzz::XmlElement* extra_info) { + buzz::XmlElement* iq = new buzz::XmlElement(buzz::QN_IQ); + iq->SetAttr(buzz::QN_TO, stanza->Attr(buzz::QN_FROM)); + iq->SetAttr(buzz::QN_ID, stanza->Attr(buzz::QN_ID)); + iq->SetAttr(buzz::QN_TYPE, "error"); + + CopyXmlChildren(stanza, iq); + + buzz::XmlElement* error = new buzz::XmlElement(buzz::QN_ERROR); + error->SetAttr(buzz::QN_TYPE, type); + iq->AddElement(error); + + // If the error name is not in the standard namespace, we have to first add + // some error from that namespace. + if (name.Namespace() != buzz::NS_STANZA) { + error->AddElement( + new buzz::XmlElement(buzz::QN_STANZA_UNDEFINED_CONDITION)); + } + error->AddElement(new buzz::XmlElement(name)); + + if (extra_info) + error->AddElement(new buzz::XmlElement(*extra_info)); + + if (text.size() > 0) { + // It's okay to always use English here. This text is for debugging + // purposes only. + buzz::XmlElement* text_elem = new buzz::XmlElement(buzz::QN_STANZA_TEXT); + text_elem->SetAttr(buzz::QN_XML_LANG, "en"); + text_elem->SetBodyText(text); + error->AddElement(text_elem); + } + + // TODO: Should we include error codes as well for SIP compatibility? + + return iq; +} + +void SessionManager::OnOutgoingMessage(Session* session, + const buzz::XmlElement* stanza) { + SignalOutgoingMessage(this, stanza); +} + +void SessionManager::OnErrorMessage(BaseSession* session, + const buzz::XmlElement* stanza, + const buzz::QName& name, + const std::string& type, + const std::string& text, + const buzz::XmlElement* extra_info) { + SendErrorMessage(stanza, name, type, text, extra_info); +} + +void SessionManager::OnSignalingReady() { + for (SessionMap::iterator it = session_map_.begin(); + it != session_map_.end(); + ++it) { + it->second->OnSignalingReady(); + } +} + +void SessionManager::OnRequestSignaling(Session* session) { + SignalRequestSignaling(); +} + +} // namespace cricket diff --git a/webrtc/p2p/base/sessionmanager.h b/webrtc/p2p/base/sessionmanager.h new file mode 100644 index 000000000..74ee5c073 --- /dev/null +++ b/webrtc/p2p/base/sessionmanager.h @@ -0,0 +1,194 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_SESSIONMANAGER_H_ +#define WEBRTC_P2P_BASE_SESSIONMANAGER_H_ + +#include +#include +#include +#include + +#include "webrtc/p2p/base/portallocator.h" +#include "webrtc/p2p/base/transportdescriptionfactory.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/thread.h" + +namespace buzz { +class QName; +class XmlElement; +} + +namespace cricket { + +class Session; +class BaseSession; +class SessionClient; + +// SessionManager manages session instances. +class SessionManager : public sigslot::has_slots<> { + public: + SessionManager(PortAllocator *allocator, + rtc::Thread *worker_thread = NULL); + virtual ~SessionManager(); + + PortAllocator *port_allocator() const { return allocator_; } + rtc::Thread *worker_thread() const { return worker_thread_; } + rtc::Thread *signaling_thread() const { return signaling_thread_; } + + int session_timeout() const { return timeout_; } + void set_session_timeout(int timeout) { timeout_ = timeout; } + + // Set what transport protocol we want to default to. + void set_transport_protocol(TransportProtocol proto) { + transport_desc_factory_.set_protocol(proto); + } + + // Control use of DTLS. An identity must be supplied if DTLS is enabled. + void set_secure(SecurePolicy policy) { + transport_desc_factory_.set_secure(policy); + } + void set_identity(rtc::SSLIdentity* identity) { + transport_desc_factory_.set_identity(identity); + } + const TransportDescriptionFactory* transport_desc_factory() const { + return &transport_desc_factory_; + } + + // Registers support for the given client. If we receive an initiate + // describing a session of the given type, we will automatically create a + // Session object and notify this client. The client may then accept or + // reject the session. + void AddClient(const std::string& content_type, SessionClient* client); + void RemoveClient(const std::string& content_type); + SessionClient* GetClient(const std::string& content_type); + + // Creates a new session. The given name is the JID of the client on whose + // behalf we initiate the session. + Session *CreateSession(const std::string& local_name, + const std::string& content_type); + + Session *CreateSession(const std::string& id, + const std::string& local_name, + const std::string& content_type); + + // Destroys the given session. + void DestroySession(Session *session); + + // Returns the session with the given ID or NULL if none exists. + Session *GetSession(const std::string& sid); + + // Terminates all of the sessions created by this manager. + void TerminateAll(); + + // These are signaled whenever the set of existing sessions changes. + sigslot::signal2 SignalSessionCreate; + sigslot::signal1 SignalSessionDestroy; + + // Determines whether the given stanza is intended for some session. + bool IsSessionMessage(const buzz::XmlElement* stanza); + + // Given a sid, initiator, and remote_name, this finds the matching Session + Session* FindSession(const std::string& sid, + const std::string& remote_name); + + // Called when we receive a stanza for which IsSessionMessage is true. + void OnIncomingMessage(const buzz::XmlElement* stanza); + + // Called when we get a response to a message that we sent. + void OnIncomingResponse(const buzz::XmlElement* orig_stanza, + const buzz::XmlElement* response_stanza); + + // Called if an attempted to send times out or an error is returned. In the + // timeout case error_stanza will be NULL + void OnFailedSend(const buzz::XmlElement* orig_stanza, + const buzz::XmlElement* error_stanza); + + // Signalled each time a session generates a signaling message to send. + // Also signalled on errors, but with a NULL session. + sigslot::signal2 SignalOutgoingMessage; + + // Signaled before sessions try to send certain signaling messages. The + // client should call OnSignalingReady once it is safe to send them. These + // steps are taken so that we don't send signaling messages trying to + // re-establish the connectivity of a session when the client cannot send + // the messages (and would probably just drop them on the floor). + // + // Note: you can connect this directly to OnSignalingReady(), if a signalling + // check is not supported. + sigslot::signal0<> SignalRequestSignaling; + void OnSignalingReady(); + + // Signaled when this SessionManager is deleted. + sigslot::signal0<> SignalDestroyed; + + private: + typedef std::map SessionMap; + typedef std::map ClientMap; + + // Helper function for CreateSession. This is also invoked when we receive + // a message attempting to initiate a session with this client. + Session *CreateSession(const std::string& local_name, + const std::string& initiator, + const std::string& sid, + const std::string& content_type, + bool received_initiate); + + // Attempts to find a registered session type whose description appears as + // a child of the session element. Such a child should be present indicating + // the application they hope to initiate. + std::string FindClient(const buzz::XmlElement* session); + + // Sends a message back to the other client indicating that we found an error + // in the stanza they sent. name identifies the error, type is one of the + // standard XMPP types (cancel, continue, modify, auth, wait), and text is a + // description for debugging purposes. + void SendErrorMessage(const buzz::XmlElement* stanza, + const buzz::QName& name, + const std::string& type, + const std::string& text, + const buzz::XmlElement* extra_info); + + // Creates and returns an error message from the given components. The + // caller is responsible for deleting this. + buzz::XmlElement* CreateErrorMessage( + const buzz::XmlElement* stanza, + const buzz::QName& name, + const std::string& type, + const std::string& text, + const buzz::XmlElement* extra_info); + + // Called each time a session requests signaling. + void OnRequestSignaling(Session* session); + + // Called each time a session has an outgoing message. + void OnOutgoingMessage(Session* session, const buzz::XmlElement* stanza); + + // Called each time a session has an error to send. + void OnErrorMessage(BaseSession* session, + const buzz::XmlElement* stanza, + const buzz::QName& name, + const std::string& type, + const std::string& text, + const buzz::XmlElement* extra_info); + + PortAllocator *allocator_; + rtc::Thread *signaling_thread_; + rtc::Thread *worker_thread_; + int timeout_; + TransportDescriptionFactory transport_desc_factory_; + SessionMap session_map_; + ClientMap client_map_; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_SESSIONMANAGER_H_ diff --git a/webrtc/p2p/base/sessionmessages.cc b/webrtc/p2p/base/sessionmessages.cc new file mode 100644 index 000000000..cc63673f4 --- /dev/null +++ b/webrtc/p2p/base/sessionmessages.cc @@ -0,0 +1,1132 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/sessionmessages.h" + +#include +#include + +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/p2ptransport.h" +#include "webrtc/p2p/base/parsing.h" +#include "webrtc/p2p/base/sessionclient.h" +#include "webrtc/p2p/base/sessiondescription.h" +#include "webrtc/p2p/base/transport.h" +#include "webrtc/libjingle/xmllite/xmlconstants.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/stringutils.h" + +namespace cricket { + +ActionType ToActionType(const std::string& type) { + if (type == GINGLE_ACTION_INITIATE) + return ACTION_SESSION_INITIATE; + if (type == GINGLE_ACTION_INFO) + return ACTION_SESSION_INFO; + if (type == GINGLE_ACTION_ACCEPT) + return ACTION_SESSION_ACCEPT; + if (type == GINGLE_ACTION_REJECT) + return ACTION_SESSION_REJECT; + if (type == GINGLE_ACTION_TERMINATE) + return ACTION_SESSION_TERMINATE; + if (type == GINGLE_ACTION_CANDIDATES) + return ACTION_TRANSPORT_INFO; + if (type == JINGLE_ACTION_SESSION_INITIATE) + return ACTION_SESSION_INITIATE; + if (type == JINGLE_ACTION_TRANSPORT_INFO) + return ACTION_TRANSPORT_INFO; + if (type == JINGLE_ACTION_TRANSPORT_ACCEPT) + return ACTION_TRANSPORT_ACCEPT; + if (type == JINGLE_ACTION_SESSION_INFO) + return ACTION_SESSION_INFO; + if (type == JINGLE_ACTION_SESSION_ACCEPT) + return ACTION_SESSION_ACCEPT; + if (type == JINGLE_ACTION_SESSION_TERMINATE) + return ACTION_SESSION_TERMINATE; + if (type == JINGLE_ACTION_TRANSPORT_INFO) + return ACTION_TRANSPORT_INFO; + if (type == JINGLE_ACTION_TRANSPORT_ACCEPT) + return ACTION_TRANSPORT_ACCEPT; + if (type == JINGLE_ACTION_DESCRIPTION_INFO) + return ACTION_DESCRIPTION_INFO; + if (type == GINGLE_ACTION_UPDATE) + return ACTION_DESCRIPTION_INFO; + + return ACTION_UNKNOWN; +} + +std::string ToJingleString(ActionType type) { + switch (type) { + case ACTION_SESSION_INITIATE: + return JINGLE_ACTION_SESSION_INITIATE; + case ACTION_SESSION_INFO: + return JINGLE_ACTION_SESSION_INFO; + case ACTION_DESCRIPTION_INFO: + return JINGLE_ACTION_DESCRIPTION_INFO; + case ACTION_SESSION_ACCEPT: + return JINGLE_ACTION_SESSION_ACCEPT; + // Notice that reject and terminate both go to + // "session-terminate", but there is no "session-reject". + case ACTION_SESSION_REJECT: + case ACTION_SESSION_TERMINATE: + return JINGLE_ACTION_SESSION_TERMINATE; + case ACTION_TRANSPORT_INFO: + return JINGLE_ACTION_TRANSPORT_INFO; + case ACTION_TRANSPORT_ACCEPT: + return JINGLE_ACTION_TRANSPORT_ACCEPT; + default: + return ""; + } +} + +std::string ToGingleString(ActionType type) { + switch (type) { + case ACTION_SESSION_INITIATE: + return GINGLE_ACTION_INITIATE; + case ACTION_SESSION_INFO: + return GINGLE_ACTION_INFO; + case ACTION_SESSION_ACCEPT: + return GINGLE_ACTION_ACCEPT; + case ACTION_SESSION_REJECT: + return GINGLE_ACTION_REJECT; + case ACTION_SESSION_TERMINATE: + return GINGLE_ACTION_TERMINATE; + case ACTION_TRANSPORT_INFO: + return GINGLE_ACTION_CANDIDATES; + default: + return ""; + } +} + + +bool IsJingleMessage(const buzz::XmlElement* stanza) { + const buzz::XmlElement* jingle = stanza->FirstNamed(QN_JINGLE); + if (jingle == NULL) + return false; + + return (jingle->HasAttr(buzz::QN_ACTION) && jingle->HasAttr(QN_SID)); +} + +bool IsGingleMessage(const buzz::XmlElement* stanza) { + const buzz::XmlElement* session = stanza->FirstNamed(QN_GINGLE_SESSION); + if (session == NULL) + return false; + + return (session->HasAttr(buzz::QN_TYPE) && + session->HasAttr(buzz::QN_ID) && + session->HasAttr(QN_INITIATOR)); +} + +bool IsSessionMessage(const buzz::XmlElement* stanza) { + return (stanza->Name() == buzz::QN_IQ && + stanza->Attr(buzz::QN_TYPE) == buzz::STR_SET && + (IsJingleMessage(stanza) || + IsGingleMessage(stanza))); +} + +bool ParseGingleSessionMessage(const buzz::XmlElement* session, + SessionMessage* msg, + ParseError* error) { + msg->protocol = PROTOCOL_GINGLE; + std::string type_string = session->Attr(buzz::QN_TYPE); + msg->type = ToActionType(type_string); + msg->sid = session->Attr(buzz::QN_ID); + msg->initiator = session->Attr(QN_INITIATOR); + msg->action_elem = session; + + if (msg->type == ACTION_UNKNOWN) + return BadParse("unknown action: " + type_string, error); + + return true; +} + +bool ParseJingleSessionMessage(const buzz::XmlElement* jingle, + SessionMessage* msg, + ParseError* error) { + msg->protocol = PROTOCOL_JINGLE; + std::string type_string = jingle->Attr(buzz::QN_ACTION); + msg->type = ToActionType(type_string); + msg->sid = jingle->Attr(QN_SID); + msg->initiator = GetXmlAttr(jingle, QN_INITIATOR, buzz::STR_EMPTY); + msg->action_elem = jingle; + + if (msg->type == ACTION_UNKNOWN) + return BadParse("unknown action: " + type_string, error); + + return true; +} + +bool ParseHybridSessionMessage(const buzz::XmlElement* jingle, + SessionMessage* msg, + ParseError* error) { + if (!ParseJingleSessionMessage(jingle, msg, error)) + return false; + msg->protocol = PROTOCOL_HYBRID; + + return true; +} + +bool ParseSessionMessage(const buzz::XmlElement* stanza, + SessionMessage* msg, + ParseError* error) { + msg->id = stanza->Attr(buzz::QN_ID); + msg->from = stanza->Attr(buzz::QN_FROM); + msg->to = stanza->Attr(buzz::QN_TO); + msg->stanza = stanza; + + const buzz::XmlElement* jingle = stanza->FirstNamed(QN_JINGLE); + const buzz::XmlElement* session = stanza->FirstNamed(QN_GINGLE_SESSION); + if (jingle && session) + return ParseHybridSessionMessage(jingle, msg, error); + if (jingle != NULL) + return ParseJingleSessionMessage(jingle, msg, error); + if (session != NULL) + return ParseGingleSessionMessage(session, msg, error); + return false; +} + +buzz::XmlElement* WriteGingleAction(const SessionMessage& msg, + const XmlElements& action_elems) { + buzz::XmlElement* session = new buzz::XmlElement(QN_GINGLE_SESSION, true); + session->AddAttr(buzz::QN_TYPE, ToGingleString(msg.type)); + session->AddAttr(buzz::QN_ID, msg.sid); + session->AddAttr(QN_INITIATOR, msg.initiator); + AddXmlChildren(session, action_elems); + return session; +} + +buzz::XmlElement* WriteJingleAction(const SessionMessage& msg, + const XmlElements& action_elems) { + buzz::XmlElement* jingle = new buzz::XmlElement(QN_JINGLE, true); + jingle->AddAttr(buzz::QN_ACTION, ToJingleString(msg.type)); + jingle->AddAttr(QN_SID, msg.sid); + if (msg.type == ACTION_SESSION_INITIATE) { + jingle->AddAttr(QN_INITIATOR, msg.initiator); + } + AddXmlChildren(jingle, action_elems); + return jingle; +} + +void WriteSessionMessage(const SessionMessage& msg, + const XmlElements& action_elems, + buzz::XmlElement* stanza) { + stanza->SetAttr(buzz::QN_TO, msg.to); + stanza->SetAttr(buzz::QN_TYPE, buzz::STR_SET); + + if (msg.protocol == PROTOCOL_GINGLE) { + stanza->AddElement(WriteGingleAction(msg, action_elems)); + } else { + stanza->AddElement(WriteJingleAction(msg, action_elems)); + } +} + + +TransportParser* GetTransportParser(const TransportParserMap& trans_parsers, + const std::string& transport_type) { + TransportParserMap::const_iterator map = trans_parsers.find(transport_type); + if (map == trans_parsers.end()) { + return NULL; + } else { + return map->second; + } +} + +CandidateTranslator* GetCandidateTranslator( + const CandidateTranslatorMap& translators, + const std::string& content_name) { + CandidateTranslatorMap::const_iterator map = translators.find(content_name); + if (map == translators.end()) { + return NULL; + } else { + return map->second; + } +} + +bool GetParserAndTranslator(const TransportParserMap& trans_parsers, + const CandidateTranslatorMap& translators, + const std::string& transport_type, + const std::string& content_name, + TransportParser** parser, + CandidateTranslator** translator, + ParseError* error) { + *parser = GetTransportParser(trans_parsers, transport_type); + if (*parser == NULL) { + return BadParse("unknown transport type: " + transport_type, error); + } + // Not having a translator isn't fatal when parsing. If this is called for an + // initiate message, we won't have our proxies set up to do the translation. + // Fortunately, for the cases where translation is needed, candidates are + // never sent in initiates. + *translator = GetCandidateTranslator(translators, content_name); + return true; +} + +bool GetParserAndTranslator(const TransportParserMap& trans_parsers, + const CandidateTranslatorMap& translators, + const std::string& transport_type, + const std::string& content_name, + TransportParser** parser, + CandidateTranslator** translator, + WriteError* error) { + *parser = GetTransportParser(trans_parsers, transport_type); + if (*parser == NULL) { + return BadWrite("unknown transport type: " + transport_type, error); + } + *translator = GetCandidateTranslator(translators, content_name); + if (*translator == NULL) { + return BadWrite("unknown content name: " + content_name, error); + } + return true; +} + +bool ParseGingleCandidate(const buzz::XmlElement* candidate_elem, + const TransportParserMap& trans_parsers, + const CandidateTranslatorMap& translators, + const std::string& content_name, + Candidates* candidates, + ParseError* error) { + TransportParser* trans_parser; + CandidateTranslator* translator; + if (!GetParserAndTranslator(trans_parsers, translators, + NS_GINGLE_P2P, content_name, + &trans_parser, &translator, error)) + return false; + + Candidate candidate; + if (!trans_parser->ParseGingleCandidate( + candidate_elem, translator, &candidate, error)) { + return false; + } + + candidates->push_back(candidate); + return true; +} + +bool ParseGingleCandidates(const buzz::XmlElement* parent, + const TransportParserMap& trans_parsers, + const CandidateTranslatorMap& translators, + const std::string& content_name, + Candidates* candidates, + ParseError* error) { + for (const buzz::XmlElement* candidate_elem = parent->FirstElement(); + candidate_elem != NULL; + candidate_elem = candidate_elem->NextElement()) { + if (candidate_elem->Name().LocalPart() == LN_CANDIDATE) { + if (!ParseGingleCandidate(candidate_elem, trans_parsers, translators, + content_name, candidates, error)) { + return false; + } + } + } + return true; +} + +bool ParseGingleTransportInfos(const buzz::XmlElement* action_elem, + const ContentInfos& contents, + const TransportParserMap& trans_parsers, + const CandidateTranslatorMap& translators, + TransportInfos* tinfos, + ParseError* error) { + bool has_audio = FindContentInfoByName(contents, CN_AUDIO) != NULL; + bool has_video = FindContentInfoByName(contents, CN_VIDEO) != NULL; + + // If we don't have media, no need to separate the candidates. + if (!has_audio && !has_video) { + TransportInfo tinfo(CN_OTHER, + TransportDescription(NS_GINGLE_P2P, std::string(), std::string())); + if (!ParseGingleCandidates(action_elem, trans_parsers, translators, + CN_OTHER, &tinfo.description.candidates, + error)) { + return false; + } + + tinfos->push_back(tinfo); + return true; + } + + // If we have media, separate the candidates. + TransportInfo audio_tinfo( + CN_AUDIO, + TransportDescription(NS_GINGLE_P2P, std::string(), std::string())); + TransportInfo video_tinfo( + CN_VIDEO, + TransportDescription(NS_GINGLE_P2P, std::string(), std::string())); + for (const buzz::XmlElement* candidate_elem = action_elem->FirstElement(); + candidate_elem != NULL; + candidate_elem = candidate_elem->NextElement()) { + if (candidate_elem->Name().LocalPart() == LN_CANDIDATE) { + const std::string& channel_name = candidate_elem->Attr(buzz::QN_NAME); + if (has_audio && + (channel_name == GICE_CHANNEL_NAME_RTP || + channel_name == GICE_CHANNEL_NAME_RTCP)) { + if (!ParseGingleCandidate( + candidate_elem, trans_parsers, + translators, CN_AUDIO, + &audio_tinfo.description.candidates, error)) { + return false; + } + } else if (has_video && + (channel_name == GICE_CHANNEL_NAME_VIDEO_RTP || + channel_name == GICE_CHANNEL_NAME_VIDEO_RTCP)) { + if (!ParseGingleCandidate( + candidate_elem, trans_parsers, + translators, CN_VIDEO, + &video_tinfo.description.candidates, error)) { + return false; + } + } else { + return BadParse("Unknown channel name: " + channel_name, error); + } + } + } + + if (has_audio) { + tinfos->push_back(audio_tinfo); + } + if (has_video) { + tinfos->push_back(video_tinfo); + } + return true; +} + +bool ParseJingleTransportInfo(const buzz::XmlElement* trans_elem, + const std::string& content_name, + const TransportParserMap& trans_parsers, + const CandidateTranslatorMap& translators, + TransportInfo* tinfo, + ParseError* error) { + TransportParser* trans_parser; + CandidateTranslator* translator; + if (!GetParserAndTranslator(trans_parsers, translators, + trans_elem->Name().Namespace(), content_name, + &trans_parser, &translator, error)) + return false; + + TransportDescription tdesc; + if (!trans_parser->ParseTransportDescription(trans_elem, translator, + &tdesc, error)) + return false; + + *tinfo = TransportInfo(content_name, tdesc); + return true; +} + +bool ParseJingleTransportInfos(const buzz::XmlElement* jingle, + const ContentInfos& contents, + const TransportParserMap trans_parsers, + const CandidateTranslatorMap& translators, + TransportInfos* tinfos, + ParseError* error) { + for (const buzz::XmlElement* pair_elem + = jingle->FirstNamed(QN_JINGLE_CONTENT); + pair_elem != NULL; + pair_elem = pair_elem->NextNamed(QN_JINGLE_CONTENT)) { + std::string content_name; + if (!RequireXmlAttr(pair_elem, QN_JINGLE_CONTENT_NAME, + &content_name, error)) + return false; + + const ContentInfo* content = FindContentInfoByName(contents, content_name); + if (!content) + return BadParse("Unknown content name: " + content_name, error); + + const buzz::XmlElement* trans_elem; + if (!RequireXmlChild(pair_elem, LN_TRANSPORT, &trans_elem, error)) + return false; + + TransportInfo tinfo; + if (!ParseJingleTransportInfo(trans_elem, content->name, + trans_parsers, translators, + &tinfo, error)) + return false; + + tinfos->push_back(tinfo); + } + + return true; +} + +buzz::XmlElement* NewTransportElement(const std::string& name) { + return new buzz::XmlElement(buzz::QName(name, LN_TRANSPORT), true); +} + +bool WriteGingleCandidates(const Candidates& candidates, + const TransportParserMap& trans_parsers, + const std::string& transport_type, + const CandidateTranslatorMap& translators, + const std::string& content_name, + XmlElements* elems, + WriteError* error) { + TransportParser* trans_parser; + CandidateTranslator* translator; + if (!GetParserAndTranslator(trans_parsers, translators, + transport_type, content_name, + &trans_parser, &translator, error)) + return false; + + for (size_t i = 0; i < candidates.size(); ++i) { + rtc::scoped_ptr element; + if (!trans_parser->WriteGingleCandidate(candidates[i], translator, + element.accept(), error)) { + return false; + } + + elems->push_back(element.release()); + } + + return true; +} + +bool WriteGingleTransportInfos(const TransportInfos& tinfos, + const TransportParserMap& trans_parsers, + const CandidateTranslatorMap& translators, + XmlElements* elems, + WriteError* error) { + for (TransportInfos::const_iterator tinfo = tinfos.begin(); + tinfo != tinfos.end(); ++tinfo) { + if (!WriteGingleCandidates(tinfo->description.candidates, + trans_parsers, tinfo->description.transport_type, + translators, tinfo->content_name, + elems, error)) + return false; + } + + return true; +} + +bool WriteJingleTransportInfo(const TransportInfo& tinfo, + const TransportParserMap& trans_parsers, + const CandidateTranslatorMap& translators, + XmlElements* elems, + WriteError* error) { + std::string transport_type = tinfo.description.transport_type; + TransportParser* trans_parser; + CandidateTranslator* translator; + if (!GetParserAndTranslator(trans_parsers, translators, + transport_type, tinfo.content_name, + &trans_parser, &translator, error)) + return false; + + buzz::XmlElement* trans_elem; + if (!trans_parser->WriteTransportDescription(tinfo.description, translator, + &trans_elem, error)) { + return false; + } + + elems->push_back(trans_elem); + return true; +} + +void WriteJingleContent(const std::string name, + const XmlElements& child_elems, + XmlElements* elems) { + buzz::XmlElement* content_elem = new buzz::XmlElement(QN_JINGLE_CONTENT); + content_elem->SetAttr(QN_JINGLE_CONTENT_NAME, name); + content_elem->SetAttr(QN_CREATOR, LN_INITIATOR); + AddXmlChildren(content_elem, child_elems); + + elems->push_back(content_elem); +} + +bool WriteJingleTransportInfos(const TransportInfos& tinfos, + const TransportParserMap& trans_parsers, + const CandidateTranslatorMap& translators, + XmlElements* elems, + WriteError* error) { + for (TransportInfos::const_iterator tinfo = tinfos.begin(); + tinfo != tinfos.end(); ++tinfo) { + XmlElements content_child_elems; + if (!WriteJingleTransportInfo(*tinfo, trans_parsers, translators, + &content_child_elems, error)) + + return false; + + WriteJingleContent(tinfo->content_name, content_child_elems, elems); + } + + return true; +} + +ContentParser* GetContentParser(const ContentParserMap& content_parsers, + const std::string& type) { + ContentParserMap::const_iterator map = content_parsers.find(type); + if (map == content_parsers.end()) { + return NULL; + } else { + return map->second; + } +} + +bool ParseContentInfo(SignalingProtocol protocol, + const std::string& name, + const std::string& type, + const buzz::XmlElement* elem, + const ContentParserMap& parsers, + ContentInfos* contents, + ParseError* error) { + ContentParser* parser = GetContentParser(parsers, type); + if (parser == NULL) + return BadParse("unknown application content: " + type, error); + + ContentDescription* desc; + if (!parser->ParseContent(protocol, elem, &desc, error)) + return false; + + contents->push_back(ContentInfo(name, type, desc)); + return true; +} + +bool ParseContentType(const buzz::XmlElement* parent_elem, + std::string* content_type, + const buzz::XmlElement** content_elem, + ParseError* error) { + if (!RequireXmlChild(parent_elem, LN_DESCRIPTION, content_elem, error)) + return false; + + *content_type = (*content_elem)->Name().Namespace(); + return true; +} + +bool ParseGingleContentInfos(const buzz::XmlElement* session, + const ContentParserMap& content_parsers, + ContentInfos* contents, + ParseError* error) { + std::string content_type; + const buzz::XmlElement* content_elem; + if (!ParseContentType(session, &content_type, &content_elem, error)) + return false; + + if (content_type == NS_GINGLE_VIDEO) { + // A parser parsing audio or video content should look at the + // namespace and only parse the codecs relevant to that namespace. + // We use this to control which codecs get parsed: first audio, + // then video. + rtc::scoped_ptr audio_elem( + new buzz::XmlElement(QN_GINGLE_AUDIO_CONTENT)); + CopyXmlChildren(content_elem, audio_elem.get()); + if (!ParseContentInfo(PROTOCOL_GINGLE, CN_AUDIO, NS_JINGLE_RTP, + audio_elem.get(), content_parsers, + contents, error)) + return false; + + if (!ParseContentInfo(PROTOCOL_GINGLE, CN_VIDEO, NS_JINGLE_RTP, + content_elem, content_parsers, + contents, error)) + return false; + } else if (content_type == NS_GINGLE_AUDIO) { + if (!ParseContentInfo(PROTOCOL_GINGLE, CN_AUDIO, NS_JINGLE_RTP, + content_elem, content_parsers, + contents, error)) + return false; + } else { + if (!ParseContentInfo(PROTOCOL_GINGLE, CN_OTHER, content_type, + content_elem, content_parsers, + contents, error)) + return false; + } + return true; +} + +bool ParseJingleContentInfos(const buzz::XmlElement* jingle, + const ContentParserMap& content_parsers, + ContentInfos* contents, + ParseError* error) { + for (const buzz::XmlElement* pair_elem + = jingle->FirstNamed(QN_JINGLE_CONTENT); + pair_elem != NULL; + pair_elem = pair_elem->NextNamed(QN_JINGLE_CONTENT)) { + std::string content_name; + if (!RequireXmlAttr(pair_elem, QN_JINGLE_CONTENT_NAME, + &content_name, error)) + return false; + + std::string content_type; + const buzz::XmlElement* content_elem; + if (!ParseContentType(pair_elem, &content_type, &content_elem, error)) + return false; + + if (!ParseContentInfo(PROTOCOL_JINGLE, content_name, content_type, + content_elem, content_parsers, + contents, error)) + return false; + } + return true; +} + +bool ParseJingleGroupInfos(const buzz::XmlElement* jingle, + ContentGroups* groups, + ParseError* error) { + for (const buzz::XmlElement* pair_elem + = jingle->FirstNamed(QN_JINGLE_DRAFT_GROUP); + pair_elem != NULL; + pair_elem = pair_elem->NextNamed(QN_JINGLE_DRAFT_GROUP)) { + std::string group_name; + if (!RequireXmlAttr(pair_elem, QN_JINGLE_DRAFT_GROUP_TYPE, + &group_name, error)) + return false; + + ContentGroup group(group_name); + for (const buzz::XmlElement* child_elem + = pair_elem->FirstNamed(QN_JINGLE_CONTENT); + child_elem != NULL; + child_elem = child_elem->NextNamed(QN_JINGLE_CONTENT)) { + std::string content_name; + if (!RequireXmlAttr(child_elem, QN_JINGLE_CONTENT_NAME, + &content_name, error)) + return false; + group.AddContentName(content_name); + } + groups->push_back(group); + } + return true; +} + +buzz::XmlElement* WriteContentInfo(SignalingProtocol protocol, + const ContentInfo& content, + const ContentParserMap& parsers, + WriteError* error) { + ContentParser* parser = GetContentParser(parsers, content.type); + if (parser == NULL) { + BadWrite("unknown content type: " + content.type, error); + return NULL; + } + + buzz::XmlElement* elem = NULL; + if (!parser->WriteContent(protocol, content.description, &elem, error)) + return NULL; + + return elem; +} + +bool IsWritable(SignalingProtocol protocol, + const ContentInfo& content, + const ContentParserMap& parsers) { + ContentParser* parser = GetContentParser(parsers, content.type); + if (parser == NULL) { + return false; + } + + return parser->IsWritable(protocol, content.description); +} + +bool WriteGingleContentInfos(const ContentInfos& contents, + const ContentParserMap& parsers, + XmlElements* elems, + WriteError* error) { + if (contents.size() == 1 || + (contents.size() == 2 && + !IsWritable(PROTOCOL_GINGLE, contents.at(1), parsers))) { + if (contents.front().rejected) { + return BadWrite("Gingle protocol may not reject individual contents.", + error); + } + buzz::XmlElement* elem = WriteContentInfo( + PROTOCOL_GINGLE, contents.front(), parsers, error); + if (!elem) + return false; + + elems->push_back(elem); + } else if (contents.size() >= 2 && + contents.at(0).type == NS_JINGLE_RTP && + contents.at(1).type == NS_JINGLE_RTP) { + // Special-case audio + video contents so that they are "merged" + // into one "video" content. + if (contents.at(0).rejected || contents.at(1).rejected) { + return BadWrite("Gingle protocol may not reject individual contents.", + error); + } + buzz::XmlElement* audio = WriteContentInfo( + PROTOCOL_GINGLE, contents.at(0), parsers, error); + if (!audio) + return false; + + buzz::XmlElement* video = WriteContentInfo( + PROTOCOL_GINGLE, contents.at(1), parsers, error); + if (!video) { + delete audio; + return false; + } + + CopyXmlChildren(audio, video); + elems->push_back(video); + delete audio; + } else { + return BadWrite("Gingle protocol may only have one content.", error); + } + + return true; +} + +const TransportInfo* GetTransportInfoByContentName( + const TransportInfos& tinfos, const std::string& content_name) { + for (TransportInfos::const_iterator tinfo = tinfos.begin(); + tinfo != tinfos.end(); ++tinfo) { + if (content_name == tinfo->content_name) { + return &*tinfo; + } + } + return NULL; +} + +bool WriteJingleContents(const ContentInfos& contents, + const ContentParserMap& content_parsers, + const TransportInfos& tinfos, + const TransportParserMap& trans_parsers, + const CandidateTranslatorMap& translators, + XmlElements* elems, + WriteError* error) { + for (ContentInfos::const_iterator content = contents.begin(); + content != contents.end(); ++content) { + if (content->rejected) { + continue; + } + const TransportInfo* tinfo = + GetTransportInfoByContentName(tinfos, content->name); + if (!tinfo) + return BadWrite("No transport for content: " + content->name, error); + + XmlElements pair_elems; + buzz::XmlElement* elem = WriteContentInfo( + PROTOCOL_JINGLE, *content, content_parsers, error); + if (!elem) + return false; + pair_elems.push_back(elem); + + if (!WriteJingleTransportInfo(*tinfo, trans_parsers, translators, + &pair_elems, error)) + return false; + + WriteJingleContent(content->name, pair_elems, elems); + } + return true; +} + +bool WriteJingleContentInfos(const ContentInfos& contents, + const ContentParserMap& content_parsers, + XmlElements* elems, + WriteError* error) { + for (ContentInfos::const_iterator content = contents.begin(); + content != contents.end(); ++content) { + if (content->rejected) { + continue; + } + XmlElements content_child_elems; + buzz::XmlElement* elem = WriteContentInfo( + PROTOCOL_JINGLE, *content, content_parsers, error); + if (!elem) + return false; + content_child_elems.push_back(elem); + WriteJingleContent(content->name, content_child_elems, elems); + } + return true; +} + +bool WriteJingleGroupInfo(const ContentInfos& contents, + const ContentGroups& groups, + XmlElements* elems, + WriteError* error) { + if (!groups.empty()) { + buzz::XmlElement* pair_elem = new buzz::XmlElement(QN_JINGLE_DRAFT_GROUP); + pair_elem->SetAttr(QN_JINGLE_DRAFT_GROUP_TYPE, GROUP_TYPE_BUNDLE); + + XmlElements pair_elems; + for (ContentInfos::const_iterator content = contents.begin(); + content != contents.end(); ++content) { + buzz::XmlElement* child_elem = + new buzz::XmlElement(QN_JINGLE_CONTENT, false); + child_elem->SetAttr(QN_JINGLE_CONTENT_NAME, content->name); + pair_elems.push_back(child_elem); + } + AddXmlChildren(pair_elem, pair_elems); + elems->push_back(pair_elem); + } + return true; +} + +bool ParseContentType(SignalingProtocol protocol, + const buzz::XmlElement* action_elem, + std::string* content_type, + ParseError* error) { + const buzz::XmlElement* content_elem; + if (protocol == PROTOCOL_GINGLE) { + if (!ParseContentType(action_elem, content_type, &content_elem, error)) + return false; + + // Internally, we only use NS_JINGLE_RTP. + if (*content_type == NS_GINGLE_AUDIO || + *content_type == NS_GINGLE_VIDEO) + *content_type = NS_JINGLE_RTP; + } else { + const buzz::XmlElement* pair_elem + = action_elem->FirstNamed(QN_JINGLE_CONTENT); + if (pair_elem == NULL) + return BadParse("No contents found", error); + + if (!ParseContentType(pair_elem, content_type, &content_elem, error)) + return false; + } + + return true; +} + +static bool ParseContentMessage( + SignalingProtocol protocol, + const buzz::XmlElement* action_elem, + bool expect_transports, + const ContentParserMap& content_parsers, + const TransportParserMap& trans_parsers, + const CandidateTranslatorMap& translators, + SessionInitiate* init, + ParseError* error) { + init->owns_contents = true; + if (protocol == PROTOCOL_GINGLE) { + if (!ParseGingleContentInfos(action_elem, content_parsers, + &init->contents, error)) + return false; + + if (expect_transports && + !ParseGingleTransportInfos(action_elem, init->contents, + trans_parsers, translators, + &init->transports, error)) + return false; + } else { + if (!ParseJingleContentInfos(action_elem, content_parsers, + &init->contents, error)) + return false; + if (!ParseJingleGroupInfos(action_elem, &init->groups, error)) + return false; + + if (expect_transports && + !ParseJingleTransportInfos(action_elem, init->contents, + trans_parsers, translators, + &init->transports, error)) + return false; + } + + return true; +} + +static bool WriteContentMessage( + SignalingProtocol protocol, + const ContentInfos& contents, + const TransportInfos& tinfos, + const ContentParserMap& content_parsers, + const TransportParserMap& transport_parsers, + const CandidateTranslatorMap& translators, + const ContentGroups& groups, + XmlElements* elems, + WriteError* error) { + if (protocol == PROTOCOL_GINGLE) { + if (!WriteGingleContentInfos(contents, content_parsers, elems, error)) + return false; + + if (!WriteGingleTransportInfos(tinfos, transport_parsers, translators, + elems, error)) + return false; + } else { + if (!WriteJingleContents(contents, content_parsers, + tinfos, transport_parsers, translators, + elems, error)) + return false; + if (!WriteJingleGroupInfo(contents, groups, elems, error)) + return false; + } + + return true; +} + +bool ParseSessionInitiate(SignalingProtocol protocol, + const buzz::XmlElement* action_elem, + const ContentParserMap& content_parsers, + const TransportParserMap& trans_parsers, + const CandidateTranslatorMap& translators, + SessionInitiate* init, + ParseError* error) { + bool expect_transports = true; + return ParseContentMessage(protocol, action_elem, expect_transports, + content_parsers, trans_parsers, translators, + init, error); +} + + +bool WriteSessionInitiate(SignalingProtocol protocol, + const ContentInfos& contents, + const TransportInfos& tinfos, + const ContentParserMap& content_parsers, + const TransportParserMap& transport_parsers, + const CandidateTranslatorMap& translators, + const ContentGroups& groups, + XmlElements* elems, + WriteError* error) { + return WriteContentMessage(protocol, contents, tinfos, + content_parsers, transport_parsers, translators, + groups, + elems, error); +} + +bool ParseSessionAccept(SignalingProtocol protocol, + const buzz::XmlElement* action_elem, + const ContentParserMap& content_parsers, + const TransportParserMap& transport_parsers, + const CandidateTranslatorMap& translators, + SessionAccept* accept, + ParseError* error) { + bool expect_transports = true; + return ParseContentMessage(protocol, action_elem, expect_transports, + content_parsers, transport_parsers, translators, + accept, error); +} + +bool WriteSessionAccept(SignalingProtocol protocol, + const ContentInfos& contents, + const TransportInfos& tinfos, + const ContentParserMap& content_parsers, + const TransportParserMap& transport_parsers, + const CandidateTranslatorMap& translators, + const ContentGroups& groups, + XmlElements* elems, + WriteError* error) { + return WriteContentMessage(protocol, contents, tinfos, + content_parsers, transport_parsers, translators, + groups, + elems, error); +} + +bool ParseSessionTerminate(SignalingProtocol protocol, + const buzz::XmlElement* action_elem, + SessionTerminate* term, + ParseError* error) { + if (protocol == PROTOCOL_GINGLE) { + const buzz::XmlElement* reason_elem = action_elem->FirstElement(); + if (reason_elem != NULL) { + term->reason = reason_elem->Name().LocalPart(); + const buzz::XmlElement *debug_elem = reason_elem->FirstElement(); + if (debug_elem != NULL) { + term->debug_reason = debug_elem->Name().LocalPart(); + } + } + return true; + } else { + const buzz::XmlElement* reason_elem = + action_elem->FirstNamed(QN_JINGLE_REASON); + if (reason_elem) { + reason_elem = reason_elem->FirstElement(); + if (reason_elem) { + term->reason = reason_elem->Name().LocalPart(); + } + } + return true; + } +} + +void WriteSessionTerminate(SignalingProtocol protocol, + const SessionTerminate& term, + XmlElements* elems) { + if (protocol == PROTOCOL_GINGLE) { + elems->push_back(new buzz::XmlElement(buzz::QName(NS_GINGLE, term.reason))); + } else { + if (!term.reason.empty()) { + buzz::XmlElement* reason_elem = new buzz::XmlElement(QN_JINGLE_REASON); + reason_elem->AddElement(new buzz::XmlElement( + buzz::QName(NS_JINGLE, term.reason))); + elems->push_back(reason_elem); + } + } +} + +bool ParseDescriptionInfo(SignalingProtocol protocol, + const buzz::XmlElement* action_elem, + const ContentParserMap& content_parsers, + const TransportParserMap& transport_parsers, + const CandidateTranslatorMap& translators, + DescriptionInfo* description_info, + ParseError* error) { + bool expect_transports = false; + return ParseContentMessage(protocol, action_elem, expect_transports, + content_parsers, transport_parsers, translators, + description_info, error); +} + +bool WriteDescriptionInfo(SignalingProtocol protocol, + const ContentInfos& contents, + const ContentParserMap& content_parsers, + XmlElements* elems, + WriteError* error) { + if (protocol == PROTOCOL_GINGLE) { + return WriteGingleContentInfos(contents, content_parsers, elems, error); + } else { + return WriteJingleContentInfos(contents, content_parsers, elems, error); + } +} + +bool ParseTransportInfos(SignalingProtocol protocol, + const buzz::XmlElement* action_elem, + const ContentInfos& contents, + const TransportParserMap& trans_parsers, + const CandidateTranslatorMap& translators, + TransportInfos* tinfos, + ParseError* error) { + if (protocol == PROTOCOL_GINGLE) { + return ParseGingleTransportInfos( + action_elem, contents, trans_parsers, translators, tinfos, error); + } else { + return ParseJingleTransportInfos( + action_elem, contents, trans_parsers, translators, tinfos, error); + } +} + +bool WriteTransportInfos(SignalingProtocol protocol, + const TransportInfos& tinfos, + const TransportParserMap& trans_parsers, + const CandidateTranslatorMap& translators, + XmlElements* elems, + WriteError* error) { + if (protocol == PROTOCOL_GINGLE) { + return WriteGingleTransportInfos(tinfos, trans_parsers, translators, + elems, error); + } else { + return WriteJingleTransportInfos(tinfos, trans_parsers, translators, + elems, error); + } +} + +bool GetUriTarget(const std::string& prefix, const std::string& str, + std::string* after) { + size_t pos = str.find(prefix); + if (pos == std::string::npos) + return false; + + *after = str.substr(pos + prefix.size(), std::string::npos); + return true; +} + +bool FindSessionRedirect(const buzz::XmlElement* stanza, + SessionRedirect* redirect) { + const buzz::XmlElement* error_elem = GetXmlChild(stanza, LN_ERROR); + if (error_elem == NULL) + return false; + + const buzz::XmlElement* redirect_elem = + error_elem->FirstNamed(QN_GINGLE_REDIRECT); + if (redirect_elem == NULL) + redirect_elem = error_elem->FirstNamed(buzz::QN_STANZA_REDIRECT); + if (redirect_elem == NULL) + return false; + + if (!GetUriTarget(STR_REDIRECT_PREFIX, redirect_elem->BodyText(), + &redirect->target)) + return false; + + return true; +} + +} // namespace cricket diff --git a/webrtc/p2p/base/sessionmessages.h b/webrtc/p2p/base/sessionmessages.h new file mode 100644 index 000000000..7b156d49d --- /dev/null +++ b/webrtc/p2p/base/sessionmessages.h @@ -0,0 +1,226 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_SESSIONMESSAGES_H_ +#define WEBRTC_P2P_BASE_SESSIONMESSAGES_H_ + +#include +#include +#include + +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/parsing.h" +#include "webrtc/p2p/base/sessiondescription.h" // Needed to delete contents. +#include "webrtc/p2p/base/transportinfo.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/base/basictypes.h" + +namespace cricket { + +struct ParseError; +struct WriteError; +class Candidate; +class ContentParser; +class TransportParser; + +typedef std::vector Candidates; +typedef std::map ContentParserMap; +typedef std::map TransportParserMap; + +enum ActionType { + ACTION_UNKNOWN, + + ACTION_SESSION_INITIATE, + ACTION_SESSION_INFO, + ACTION_SESSION_ACCEPT, + ACTION_SESSION_REJECT, + ACTION_SESSION_TERMINATE, + + ACTION_TRANSPORT_INFO, + ACTION_TRANSPORT_ACCEPT, + + ACTION_DESCRIPTION_INFO, +}; + +// Abstraction of a element within an stanza, per XMPP +// standard XEP-166. Can be serialized into multiple protocols, +// including the standard (Jingle) and the draft standard (Gingle). +// In general, used to communicate actions related to a p2p session, +// such accept, initiate, terminate, etc. + +struct SessionMessage { + SessionMessage() : action_elem(NULL), stanza(NULL) {} + + SessionMessage(SignalingProtocol protocol, ActionType type, + const std::string& sid, const std::string& initiator) : + protocol(protocol), type(type), sid(sid), initiator(initiator), + action_elem(NULL), stanza(NULL) {} + + std::string id; + std::string from; + std::string to; + SignalingProtocol protocol; + ActionType type; + std::string sid; // session id + std::string initiator; + + // Used for further parsing when necessary. + // Represents or . + const buzz::XmlElement* action_elem; + // Mostly used for debugging. + const buzz::XmlElement* stanza; +}; + +// TODO: Break up this class so we don't have to typedef it into +// different classes. +struct ContentMessage { + ContentMessage() : owns_contents(false) {} + + ~ContentMessage() { + if (owns_contents) { + for (ContentInfos::iterator content = contents.begin(); + content != contents.end(); content++) { + delete content->description; + } + } + } + + // Caller takes ownership of contents. + ContentInfos ClearContents() { + ContentInfos out; + contents.swap(out); + owns_contents = false; + return out; + } + + bool owns_contents; + ContentInfos contents; + TransportInfos transports; + ContentGroups groups; +}; + +typedef ContentMessage SessionInitiate; +typedef ContentMessage SessionAccept; +// Note that a DescriptionInfo does not have TransportInfos. +typedef ContentMessage DescriptionInfo; + +struct SessionTerminate { + SessionTerminate() {} + + explicit SessionTerminate(const std::string& reason) : + reason(reason) {} + + std::string reason; + std::string debug_reason; +}; + +struct SessionRedirect { + std::string target; +}; + +// Used during parsing and writing to map component to channel name +// and back. This is primarily for converting old G-ICE candidate +// signalling to new ICE candidate classes. +class CandidateTranslator { + public: + virtual bool GetChannelNameFromComponent( + int component, std::string* channel_name) const = 0; + virtual bool GetComponentFromChannelName( + const std::string& channel_name, int* component) const = 0; +}; + +// Content name => translator +typedef std::map CandidateTranslatorMap; + +bool IsSessionMessage(const buzz::XmlElement* stanza); +bool ParseSessionMessage(const buzz::XmlElement* stanza, + SessionMessage* msg, + ParseError* error); +// Will return an error if there is more than one content type. +bool ParseContentType(SignalingProtocol protocol, + const buzz::XmlElement* action_elem, + std::string* content_type, + ParseError* error); +void WriteSessionMessage(const SessionMessage& msg, + const XmlElements& action_elems, + buzz::XmlElement* stanza); +bool ParseSessionInitiate(SignalingProtocol protocol, + const buzz::XmlElement* action_elem, + const ContentParserMap& content_parsers, + const TransportParserMap& transport_parsers, + const CandidateTranslatorMap& translators, + SessionInitiate* init, + ParseError* error); +bool WriteSessionInitiate(SignalingProtocol protocol, + const ContentInfos& contents, + const TransportInfos& tinfos, + const ContentParserMap& content_parsers, + const TransportParserMap& transport_parsers, + const CandidateTranslatorMap& translators, + const ContentGroups& groups, + XmlElements* elems, + WriteError* error); +bool ParseSessionAccept(SignalingProtocol protocol, + const buzz::XmlElement* action_elem, + const ContentParserMap& content_parsers, + const TransportParserMap& transport_parsers, + const CandidateTranslatorMap& translators, + SessionAccept* accept, + ParseError* error); +bool WriteSessionAccept(SignalingProtocol protocol, + const ContentInfos& contents, + const TransportInfos& tinfos, + const ContentParserMap& content_parsers, + const TransportParserMap& transport_parsers, + const CandidateTranslatorMap& translators, + const ContentGroups& groups, + XmlElements* elems, + WriteError* error); +bool ParseSessionTerminate(SignalingProtocol protocol, + const buzz::XmlElement* action_elem, + SessionTerminate* term, + ParseError* error); +void WriteSessionTerminate(SignalingProtocol protocol, + const SessionTerminate& term, + XmlElements* elems); +bool ParseDescriptionInfo(SignalingProtocol protocol, + const buzz::XmlElement* action_elem, + const ContentParserMap& content_parsers, + const TransportParserMap& transport_parsers, + const CandidateTranslatorMap& translators, + DescriptionInfo* description_info, + ParseError* error); +bool WriteDescriptionInfo(SignalingProtocol protocol, + const ContentInfos& contents, + const ContentParserMap& content_parsers, + XmlElements* elems, + WriteError* error); +// Since a TransportInfo is not a transport-info message, and a +// transport-info message is just a collection of TransportInfos, we +// say Parse/Write TransportInfos for transport-info messages. +bool ParseTransportInfos(SignalingProtocol protocol, + const buzz::XmlElement* action_elem, + const ContentInfos& contents, + const TransportParserMap& trans_parsers, + const CandidateTranslatorMap& translators, + TransportInfos* tinfos, + ParseError* error); +bool WriteTransportInfos(SignalingProtocol protocol, + const TransportInfos& tinfos, + const TransportParserMap& trans_parsers, + const CandidateTranslatorMap& translators, + XmlElements* elems, + WriteError* error); +// Handles both Gingle and Jingle syntax. +bool FindSessionRedirect(const buzz::XmlElement* stanza, + SessionRedirect* redirect); +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_SESSIONMESSAGES_H_ diff --git a/webrtc/p2p/base/stun.cc b/webrtc/p2p/base/stun.cc new file mode 100644 index 000000000..60367fa10 --- /dev/null +++ b/webrtc/p2p/base/stun.cc @@ -0,0 +1,915 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/stun.h" + +#include + +#include "webrtc/base/byteorder.h" +#include "webrtc/base/common.h" +#include "webrtc/base/crc32.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/messagedigest.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/stringencode.h" + +using rtc::ByteBuffer; + +namespace cricket { + +const char STUN_ERROR_REASON_TRY_ALTERNATE_SERVER[] = "Try Alternate Server"; +const char STUN_ERROR_REASON_BAD_REQUEST[] = "Bad Request"; +const char STUN_ERROR_REASON_UNAUTHORIZED[] = "Unauthorized"; +const char STUN_ERROR_REASON_FORBIDDEN[] = "Forbidden"; +const char STUN_ERROR_REASON_STALE_CREDENTIALS[] = "Stale Credentials"; +const char STUN_ERROR_REASON_ALLOCATION_MISMATCH[] = "Allocation Mismatch"; +const char STUN_ERROR_REASON_STALE_NONCE[] = "Stale Nonce"; +const char STUN_ERROR_REASON_WRONG_CREDENTIALS[] = "Wrong Credentials"; +const char STUN_ERROR_REASON_UNSUPPORTED_PROTOCOL[] = "Unsupported Protocol"; +const char STUN_ERROR_REASON_ROLE_CONFLICT[] = "Role Conflict"; +const char STUN_ERROR_REASON_SERVER_ERROR[] = "Server Error"; + +const char TURN_MAGIC_COOKIE_VALUE[] = { '\x72', '\xC6', '\x4B', '\xC6' }; +const char EMPTY_TRANSACTION_ID[] = "0000000000000000"; +const uint32 STUN_FINGERPRINT_XOR_VALUE = 0x5354554E; + +// StunMessage + +StunMessage::StunMessage() + : type_(0), + length_(0), + transaction_id_(EMPTY_TRANSACTION_ID) { + ASSERT(IsValidTransactionId(transaction_id_)); + attrs_ = new std::vector(); +} + +StunMessage::~StunMessage() { + for (size_t i = 0; i < attrs_->size(); i++) + delete (*attrs_)[i]; + delete attrs_; +} + +bool StunMessage::IsLegacy() const { + if (transaction_id_.size() == kStunLegacyTransactionIdLength) + return true; + ASSERT(transaction_id_.size() == kStunTransactionIdLength); + return false; +} + +bool StunMessage::SetTransactionID(const std::string& str) { + if (!IsValidTransactionId(str)) { + return false; + } + transaction_id_ = str; + return true; +} + +bool StunMessage::AddAttribute(StunAttribute* attr) { + // Fail any attributes that aren't valid for this type of message. + if (attr->value_type() != GetAttributeValueType(attr->type())) { + return false; + } + attrs_->push_back(attr); + attr->SetOwner(this); + size_t attr_length = attr->length(); + if (attr_length % 4 != 0) { + attr_length += (4 - (attr_length % 4)); + } + length_ += static_cast(attr_length + 4); + return true; +} + +const StunAddressAttribute* StunMessage::GetAddress(int type) const { + switch (type) { + case STUN_ATTR_MAPPED_ADDRESS: { + // Return XOR-MAPPED-ADDRESS when MAPPED-ADDRESS attribute is + // missing. + const StunAttribute* mapped_address = + GetAttribute(STUN_ATTR_MAPPED_ADDRESS); + if (!mapped_address) + mapped_address = GetAttribute(STUN_ATTR_XOR_MAPPED_ADDRESS); + return reinterpret_cast(mapped_address); + } + + default: + return static_cast(GetAttribute(type)); + } +} + +const StunUInt32Attribute* StunMessage::GetUInt32(int type) const { + return static_cast(GetAttribute(type)); +} + +const StunUInt64Attribute* StunMessage::GetUInt64(int type) const { + return static_cast(GetAttribute(type)); +} + +const StunByteStringAttribute* StunMessage::GetByteString(int type) const { + return static_cast(GetAttribute(type)); +} + +const StunErrorCodeAttribute* StunMessage::GetErrorCode() const { + return static_cast( + GetAttribute(STUN_ATTR_ERROR_CODE)); +} + +const StunUInt16ListAttribute* StunMessage::GetUnknownAttributes() const { + return static_cast( + GetAttribute(STUN_ATTR_UNKNOWN_ATTRIBUTES)); +} + +// Verifies a STUN message has a valid MESSAGE-INTEGRITY attribute, using the +// procedure outlined in RFC 5389, section 15.4. +bool StunMessage::ValidateMessageIntegrity(const char* data, size_t size, + const std::string& password) { + // Verifying the size of the message. + if ((size % 4) != 0) { + return false; + } + + // Getting the message length from the STUN header. + uint16 msg_length = rtc::GetBE16(&data[2]); + if (size != (msg_length + kStunHeaderSize)) { + return false; + } + + // Finding Message Integrity attribute in stun message. + size_t current_pos = kStunHeaderSize; + bool has_message_integrity_attr = false; + while (current_pos < size) { + uint16 attr_type, attr_length; + // Getting attribute type and length. + attr_type = rtc::GetBE16(&data[current_pos]); + attr_length = rtc::GetBE16(&data[current_pos + sizeof(attr_type)]); + + // If M-I, sanity check it, and break out. + if (attr_type == STUN_ATTR_MESSAGE_INTEGRITY) { + if (attr_length != kStunMessageIntegritySize || + current_pos + attr_length > size) { + return false; + } + has_message_integrity_attr = true; + break; + } + + // Otherwise, skip to the next attribute. + current_pos += sizeof(attr_type) + sizeof(attr_length) + attr_length; + if ((attr_length % 4) != 0) { + current_pos += (4 - (attr_length % 4)); + } + } + + if (!has_message_integrity_attr) { + return false; + } + + // Getting length of the message to calculate Message Integrity. + size_t mi_pos = current_pos; + rtc::scoped_ptr temp_data(new char[current_pos]); + memcpy(temp_data.get(), data, current_pos); + if (size > mi_pos + kStunAttributeHeaderSize + kStunMessageIntegritySize) { + // Stun message has other attributes after message integrity. + // Adjust the length parameter in stun message to calculate HMAC. + size_t extra_offset = size - + (mi_pos + kStunAttributeHeaderSize + kStunMessageIntegritySize); + size_t new_adjusted_len = size - extra_offset - kStunHeaderSize; + + // Writing new length of the STUN message @ Message Length in temp buffer. + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |0 0| STUN Message Type | Message Length | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + rtc::SetBE16(temp_data.get() + 2, + static_cast(new_adjusted_len)); + } + + char hmac[kStunMessageIntegritySize]; + size_t ret = rtc::ComputeHmac(rtc::DIGEST_SHA_1, + password.c_str(), password.size(), + temp_data.get(), mi_pos, + hmac, sizeof(hmac)); + ASSERT(ret == sizeof(hmac)); + if (ret != sizeof(hmac)) + return false; + + // Comparing the calculated HMAC with the one present in the message. + return memcmp(data + current_pos + kStunAttributeHeaderSize, + hmac, + sizeof(hmac)) == 0; +} + +bool StunMessage::AddMessageIntegrity(const std::string& password) { + return AddMessageIntegrity(password.c_str(), password.size()); +} + +bool StunMessage::AddMessageIntegrity(const char* key, + size_t keylen) { + // Add the attribute with a dummy value. Since this is a known attribute, it + // can't fail. + StunByteStringAttribute* msg_integrity_attr = + new StunByteStringAttribute(STUN_ATTR_MESSAGE_INTEGRITY, + std::string(kStunMessageIntegritySize, '0')); + VERIFY(AddAttribute(msg_integrity_attr)); + + // Calculate the HMAC for the message. + rtc::ByteBuffer buf; + if (!Write(&buf)) + return false; + + int msg_len_for_hmac = static_cast( + buf.Length() - kStunAttributeHeaderSize - msg_integrity_attr->length()); + char hmac[kStunMessageIntegritySize]; + size_t ret = rtc::ComputeHmac(rtc::DIGEST_SHA_1, + key, keylen, + buf.Data(), msg_len_for_hmac, + hmac, sizeof(hmac)); + ASSERT(ret == sizeof(hmac)); + if (ret != sizeof(hmac)) { + LOG(LS_ERROR) << "HMAC computation failed. Message-Integrity " + << "has dummy value."; + return false; + } + + // Insert correct HMAC into the attribute. + msg_integrity_attr->CopyBytes(hmac, sizeof(hmac)); + return true; +} + +// Verifies a message is in fact a STUN message, by performing the checks +// outlined in RFC 5389, section 7.3, including the FINGERPRINT check detailed +// in section 15.5. +bool StunMessage::ValidateFingerprint(const char* data, size_t size) { + // Check the message length. + size_t fingerprint_attr_size = + kStunAttributeHeaderSize + StunUInt32Attribute::SIZE; + if (size % 4 != 0 || size < kStunHeaderSize + fingerprint_attr_size) + return false; + + // Skip the rest if the magic cookie isn't present. + const char* magic_cookie = + data + kStunTransactionIdOffset - kStunMagicCookieLength; + if (rtc::GetBE32(magic_cookie) != kStunMagicCookie) + return false; + + // Check the fingerprint type and length. + const char* fingerprint_attr_data = data + size - fingerprint_attr_size; + if (rtc::GetBE16(fingerprint_attr_data) != STUN_ATTR_FINGERPRINT || + rtc::GetBE16(fingerprint_attr_data + sizeof(uint16)) != + StunUInt32Attribute::SIZE) + return false; + + // Check the fingerprint value. + uint32 fingerprint = + rtc::GetBE32(fingerprint_attr_data + kStunAttributeHeaderSize); + return ((fingerprint ^ STUN_FINGERPRINT_XOR_VALUE) == + rtc::ComputeCrc32(data, size - fingerprint_attr_size)); +} + +bool StunMessage::AddFingerprint() { + // Add the attribute with a dummy value. Since this is a known attribute, + // it can't fail. + StunUInt32Attribute* fingerprint_attr = + new StunUInt32Attribute(STUN_ATTR_FINGERPRINT, 0); + VERIFY(AddAttribute(fingerprint_attr)); + + // Calculate the CRC-32 for the message and insert it. + rtc::ByteBuffer buf; + if (!Write(&buf)) + return false; + + int msg_len_for_crc32 = static_cast( + buf.Length() - kStunAttributeHeaderSize - fingerprint_attr->length()); + uint32 c = rtc::ComputeCrc32(buf.Data(), msg_len_for_crc32); + + // Insert the correct CRC-32, XORed with a constant, into the attribute. + fingerprint_attr->SetValue(c ^ STUN_FINGERPRINT_XOR_VALUE); + return true; +} + +bool StunMessage::Read(ByteBuffer* buf) { + if (!buf->ReadUInt16(&type_)) + return false; + + if (type_ & 0x8000) { + // RTP and RTCP set the MSB of first byte, since first two bits are version, + // and version is always 2 (10). If set, this is not a STUN packet. + return false; + } + + if (!buf->ReadUInt16(&length_)) + return false; + + std::string magic_cookie; + if (!buf->ReadString(&magic_cookie, kStunMagicCookieLength)) + return false; + + std::string transaction_id; + if (!buf->ReadString(&transaction_id, kStunTransactionIdLength)) + return false; + + uint32 magic_cookie_int = + *reinterpret_cast(magic_cookie.data()); + if (rtc::NetworkToHost32(magic_cookie_int) != kStunMagicCookie) { + // If magic cookie is invalid it means that the peer implements + // RFC3489 instead of RFC5389. + transaction_id.insert(0, magic_cookie); + } + ASSERT(IsValidTransactionId(transaction_id)); + 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 = CreateAttribute(attr_type, attr_length); + if (!attr) { + // Skip any unknown or malformed attributes. + if ((attr_length % 4) != 0) { + attr_length += (4 - (attr_length % 4)); + } + if (!buf->Consume(attr_length)) + return false; + } else { + if (!attr->Read(buf)) + return false; + attrs_->push_back(attr); + } + } + + ASSERT(buf->Length() == rest); + return true; +} + +bool StunMessage::Write(ByteBuffer* buf) const { + buf->WriteUInt16(type_); + buf->WriteUInt16(length_); + if (!IsLegacy()) + buf->WriteUInt32(kStunMagicCookie); + buf->WriteString(transaction_id_); + + for (size_t i = 0; i < attrs_->size(); ++i) { + buf->WriteUInt16((*attrs_)[i]->type()); + buf->WriteUInt16(static_cast((*attrs_)[i]->length())); + if (!(*attrs_)[i]->Write(buf)) + return false; + } + + return true; +} + +StunAttributeValueType StunMessage::GetAttributeValueType(int type) const { + switch (type) { + case STUN_ATTR_MAPPED_ADDRESS: return STUN_VALUE_ADDRESS; + case STUN_ATTR_USERNAME: return STUN_VALUE_BYTE_STRING; + case STUN_ATTR_MESSAGE_INTEGRITY: return STUN_VALUE_BYTE_STRING; + case STUN_ATTR_ERROR_CODE: return STUN_VALUE_ERROR_CODE; + case STUN_ATTR_UNKNOWN_ATTRIBUTES: return STUN_VALUE_UINT16_LIST; + case STUN_ATTR_REALM: return STUN_VALUE_BYTE_STRING; + case STUN_ATTR_NONCE: return STUN_VALUE_BYTE_STRING; + case STUN_ATTR_XOR_MAPPED_ADDRESS: return STUN_VALUE_XOR_ADDRESS; + case STUN_ATTR_SOFTWARE: return STUN_VALUE_BYTE_STRING; + case STUN_ATTR_ALTERNATE_SERVER: return STUN_VALUE_ADDRESS; + case STUN_ATTR_FINGERPRINT: return STUN_VALUE_UINT32; + case STUN_ATTR_RETRANSMIT_COUNT: return STUN_VALUE_UINT32; + default: return STUN_VALUE_UNKNOWN; + } +} + +StunAttribute* StunMessage::CreateAttribute(int type, size_t length) /*const*/ { + StunAttributeValueType value_type = GetAttributeValueType(type); + return StunAttribute::Create(value_type, type, + static_cast(length), this); +} + +const StunAttribute* StunMessage::GetAttribute(int type) const { + for (size_t i = 0; i < attrs_->size(); ++i) { + if ((*attrs_)[i]->type() == type) + return (*attrs_)[i]; + } + return NULL; +} + +bool StunMessage::IsValidTransactionId(const std::string& transaction_id) { + return transaction_id.size() == kStunTransactionIdLength || + transaction_id.size() == kStunLegacyTransactionIdLength; +} + +// StunAttribute + +StunAttribute::StunAttribute(uint16 type, uint16 length) + : type_(type), length_(length) { +} + +void StunAttribute::ConsumePadding(rtc::ByteBuffer* buf) const { + int remainder = length_ % 4; + if (remainder > 0) { + buf->Consume(4 - remainder); + } +} + +void StunAttribute::WritePadding(rtc::ByteBuffer* buf) const { + int remainder = length_ % 4; + if (remainder > 0) { + char zeroes[4] = {0}; + buf->WriteBytes(zeroes, 4 - remainder); + } +} + +StunAttribute* StunAttribute::Create(StunAttributeValueType value_type, + uint16 type, uint16 length, + StunMessage* owner) { + switch (value_type) { + case STUN_VALUE_ADDRESS: + return new StunAddressAttribute(type, length); + case STUN_VALUE_XOR_ADDRESS: + return new StunXorAddressAttribute(type, length, owner); + case STUN_VALUE_UINT32: + return new StunUInt32Attribute(type); + case STUN_VALUE_UINT64: + return new StunUInt64Attribute(type); + case STUN_VALUE_BYTE_STRING: + return new StunByteStringAttribute(type, length); + case STUN_VALUE_ERROR_CODE: + return new StunErrorCodeAttribute(type, length); + case STUN_VALUE_UINT16_LIST: + return new StunUInt16ListAttribute(type, length); + default: + return NULL; + } +} + +StunAddressAttribute* StunAttribute::CreateAddress(uint16 type) { + return new StunAddressAttribute(type, 0); +} + +StunXorAddressAttribute* StunAttribute::CreateXorAddress(uint16 type) { + return new StunXorAddressAttribute(type, 0, NULL); +} + +StunUInt64Attribute* StunAttribute::CreateUInt64(uint16 type) { + return new StunUInt64Attribute(type); +} + +StunUInt32Attribute* StunAttribute::CreateUInt32(uint16 type) { + return new StunUInt32Attribute(type); +} + +StunByteStringAttribute* StunAttribute::CreateByteString(uint16 type) { + return new StunByteStringAttribute(type, 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); +} + +StunAddressAttribute::StunAddressAttribute(uint16 type, + const rtc::SocketAddress& addr) + : StunAttribute(type, 0) { + SetAddress(addr); +} + +StunAddressAttribute::StunAddressAttribute(uint16 type, uint16 length) + : StunAttribute(type, length) { +} + +bool StunAddressAttribute::Read(ByteBuffer* buf) { + uint8 dummy; + if (!buf->ReadUInt8(&dummy)) + return false; + + uint8 stun_family; + if (!buf->ReadUInt8(&stun_family)) { + return false; + } + uint16 port; + if (!buf->ReadUInt16(&port)) + return false; + if (stun_family == STUN_ADDRESS_IPV4) { + in_addr v4addr; + if (length() != SIZE_IP4) { + return false; + } + if (!buf->ReadBytes(reinterpret_cast(&v4addr), sizeof(v4addr))) { + return false; + } + rtc::IPAddress ipaddr(v4addr); + SetAddress(rtc::SocketAddress(ipaddr, port)); + } else if (stun_family == STUN_ADDRESS_IPV6) { + in6_addr v6addr; + if (length() != SIZE_IP6) { + return false; + } + if (!buf->ReadBytes(reinterpret_cast(&v6addr), sizeof(v6addr))) { + return false; + } + rtc::IPAddress ipaddr(v6addr); + SetAddress(rtc::SocketAddress(ipaddr, port)); + } else { + return false; + } + return true; +} + +bool StunAddressAttribute::Write(ByteBuffer* buf) const { + StunAddressFamily address_family = family(); + if (address_family == STUN_ADDRESS_UNDEF) { + LOG(LS_ERROR) << "Error writing address attribute: unknown family."; + return false; + } + buf->WriteUInt8(0); + buf->WriteUInt8(address_family); + buf->WriteUInt16(address_.port()); + switch (address_.family()) { + case AF_INET: { + in_addr v4addr = address_.ipaddr().ipv4_address(); + buf->WriteBytes(reinterpret_cast(&v4addr), sizeof(v4addr)); + break; + } + case AF_INET6: { + in6_addr v6addr = address_.ipaddr().ipv6_address(); + buf->WriteBytes(reinterpret_cast(&v6addr), sizeof(v6addr)); + break; + } + } + return true; +} + +StunXorAddressAttribute::StunXorAddressAttribute(uint16 type, + const rtc::SocketAddress& addr) + : StunAddressAttribute(type, addr), owner_(NULL) { +} + +StunXorAddressAttribute::StunXorAddressAttribute(uint16 type, + uint16 length, + StunMessage* owner) + : StunAddressAttribute(type, length), owner_(owner) {} + +rtc::IPAddress StunXorAddressAttribute::GetXoredIP() const { + if (owner_) { + rtc::IPAddress ip = ipaddr(); + switch (ip.family()) { + case AF_INET: { + in_addr v4addr = ip.ipv4_address(); + v4addr.s_addr = + (v4addr.s_addr ^ rtc::HostToNetwork32(kStunMagicCookie)); + return rtc::IPAddress(v4addr); + } + case AF_INET6: { + in6_addr v6addr = ip.ipv6_address(); + const std::string& transaction_id = owner_->transaction_id(); + if (transaction_id.length() == kStunTransactionIdLength) { + uint32 transactionid_as_ints[3]; + memcpy(&transactionid_as_ints[0], transaction_id.c_str(), + transaction_id.length()); + uint32* ip_as_ints = reinterpret_cast(&v6addr.s6_addr); + // Transaction ID is in network byte order, but magic cookie + // is stored in host byte order. + ip_as_ints[0] = + (ip_as_ints[0] ^ rtc::HostToNetwork32(kStunMagicCookie)); + ip_as_ints[1] = (ip_as_ints[1] ^ transactionid_as_ints[0]); + ip_as_ints[2] = (ip_as_ints[2] ^ transactionid_as_ints[1]); + ip_as_ints[3] = (ip_as_ints[3] ^ transactionid_as_ints[2]); + return rtc::IPAddress(v6addr); + } + break; + } + } + } + // Invalid ip family or transaction ID, or missing owner. + // Return an AF_UNSPEC address. + return rtc::IPAddress(); +} + +bool StunXorAddressAttribute::Read(ByteBuffer* buf) { + if (!StunAddressAttribute::Read(buf)) + return false; + uint16 xoredport = port() ^ (kStunMagicCookie >> 16); + rtc::IPAddress xored_ip = GetXoredIP(); + SetAddress(rtc::SocketAddress(xored_ip, xoredport)); + return true; +} + +bool StunXorAddressAttribute::Write(ByteBuffer* buf) const { + StunAddressFamily address_family = family(); + if (address_family == STUN_ADDRESS_UNDEF) { + LOG(LS_ERROR) << "Error writing xor-address attribute: unknown family."; + return false; + } + rtc::IPAddress xored_ip = GetXoredIP(); + if (xored_ip.family() == AF_UNSPEC) { + return false; + } + buf->WriteUInt8(0); + buf->WriteUInt8(family()); + buf->WriteUInt16(port() ^ (kStunMagicCookie >> 16)); + switch (xored_ip.family()) { + case AF_INET: { + in_addr v4addr = xored_ip.ipv4_address(); + buf->WriteBytes(reinterpret_cast(&v4addr), sizeof(v4addr)); + break; + } + case AF_INET6: { + in6_addr v6addr = xored_ip.ipv6_address(); + buf->WriteBytes(reinterpret_cast(&v6addr), sizeof(v6addr)); + break; + } + } + return true; +} + +StunUInt32Attribute::StunUInt32Attribute(uint16 type, uint32 value) + : StunAttribute(type, SIZE), bits_(value) { +} + +StunUInt32Attribute::StunUInt32Attribute(uint16 type) + : StunAttribute(type, SIZE), bits_(0) { +} + +bool StunUInt32Attribute::GetBit(size_t index) const { + ASSERT(index < 32); + return static_cast((bits_ >> index) & 0x1); +} + +void StunUInt32Attribute::SetBit(size_t index, bool value) { + ASSERT(index < 32); + bits_ &= ~(1 << index); + bits_ |= value ? (1 << index) : 0; +} + +bool StunUInt32Attribute::Read(ByteBuffer* buf) { + if (length() != SIZE || !buf->ReadUInt32(&bits_)) + return false; + return true; +} + +bool StunUInt32Attribute::Write(ByteBuffer* buf) const { + buf->WriteUInt32(bits_); + return true; +} + +StunUInt64Attribute::StunUInt64Attribute(uint16 type, uint64 value) + : StunAttribute(type, SIZE), bits_(value) { +} + +StunUInt64Attribute::StunUInt64Attribute(uint16 type) + : StunAttribute(type, SIZE), bits_(0) { +} + +bool StunUInt64Attribute::Read(ByteBuffer* buf) { + if (length() != SIZE || !buf->ReadUInt64(&bits_)) + return false; + return true; +} + +bool StunUInt64Attribute::Write(ByteBuffer* buf) const { + buf->WriteUInt64(bits_); + return true; +} + +StunByteStringAttribute::StunByteStringAttribute(uint16 type) + : StunAttribute(type, 0), bytes_(NULL) { +} + +StunByteStringAttribute::StunByteStringAttribute(uint16 type, + const std::string& str) + : StunAttribute(type, 0), bytes_(NULL) { + CopyBytes(str.c_str(), str.size()); +} + +StunByteStringAttribute::StunByteStringAttribute(uint16 type, + const void* bytes, + size_t length) + : StunAttribute(type, 0), bytes_(NULL) { + CopyBytes(bytes, length); +} + +StunByteStringAttribute::StunByteStringAttribute(uint16 type, uint16 length) + : StunAttribute(type, length), bytes_(NULL) { +} + +StunByteStringAttribute::~StunByteStringAttribute() { + delete [] bytes_; +} + +void StunByteStringAttribute::CopyBytes(const char* bytes) { + CopyBytes(bytes, strlen(bytes)); +} + +void StunByteStringAttribute::CopyBytes(const void* bytes, size_t length) { + char* new_bytes = new char[length]; + memcpy(new_bytes, bytes, length); + SetBytes(new_bytes, length); +} + +uint8 StunByteStringAttribute::GetByte(size_t index) const { + ASSERT(bytes_ != NULL); + ASSERT(index < length()); + return static_cast(bytes_[index]); +} + +void StunByteStringAttribute::SetByte(size_t index, uint8 value) { + ASSERT(bytes_ != NULL); + ASSERT(index < length()); + bytes_[index] = value; +} + +bool StunByteStringAttribute::Read(ByteBuffer* buf) { + bytes_ = new char[length()]; + if (!buf->ReadBytes(bytes_, length())) { + return false; + } + + ConsumePadding(buf); + return true; +} + +bool StunByteStringAttribute::Write(ByteBuffer* buf) const { + buf->WriteBytes(bytes_, length()); + WritePadding(buf); + return true; +} + +void StunByteStringAttribute::SetBytes(char* bytes, size_t length) { + delete [] bytes_; + bytes_ = bytes; + SetLength(static_cast(length)); +} + +StunErrorCodeAttribute::StunErrorCodeAttribute(uint16 type, int code, + const std::string& reason) + : StunAttribute(type, 0) { + SetCode(code); + SetReason(reason); +} + +StunErrorCodeAttribute::StunErrorCodeAttribute(uint16 type, uint16 length) + : StunAttribute(type, length), class_(0), number_(0) { +} + +StunErrorCodeAttribute::~StunErrorCodeAttribute() { +} + +int StunErrorCodeAttribute::code() const { + return class_ * 100 + number_; +} + +void StunErrorCodeAttribute::SetCode(int code) { + class_ = static_cast(code / 100); + number_ = static_cast(code % 100); +} + +void StunErrorCodeAttribute::SetReason(const std::string& reason) { + SetLength(MIN_SIZE + static_cast(reason.size())); + reason_ = reason; +} + +bool StunErrorCodeAttribute::Read(ByteBuffer* buf) { + uint32 val; + if (length() < MIN_SIZE || !buf->ReadUInt32(&val)) + return false; + + if ((val >> 11) != 0) + LOG(LS_ERROR) << "error-code bits not zero"; + + class_ = ((val >> 8) & 0x7); + number_ = (val & 0xff); + + if (!buf->ReadString(&reason_, length() - 4)) + return false; + + ConsumePadding(buf); + return true; +} + +bool StunErrorCodeAttribute::Write(ByteBuffer* buf) const { + buf->WriteUInt32(class_ << 8 | number_); + buf->WriteString(reason_); + WritePadding(buf); + return true; +} + +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(static_cast(attr_types_->size() * 2)); +} + +bool StunUInt16ListAttribute::Read(ByteBuffer* buf) { + if (length() % 2) + return false; + + for (size_t i = 0; i < length() / 2; i++) { + uint16 attr; + if (!buf->ReadUInt16(&attr)) + return false; + attr_types_->push_back(attr); + } + // Padding of these attributes is done in RFC 5389 style. This is + // slightly different from RFC3489, but it shouldn't be important. + // RFC3489 pads out to a 32 bit boundary by duplicating one of the + // entries in the list (not necessarily the last one - it's unspecified). + // RFC5389 pads on the end, and the bytes are always ignored. + ConsumePadding(buf); + return true; +} + +bool StunUInt16ListAttribute::Write(ByteBuffer* buf) const { + for (size_t i = 0; i < attr_types_->size(); ++i) { + buf->WriteUInt16((*attr_types_)[i]); + } + WritePadding(buf); + return true; +} + +int GetStunSuccessResponseType(int req_type) { + return IsStunRequestType(req_type) ? (req_type | 0x100) : -1; +} + +int GetStunErrorResponseType(int req_type) { + return IsStunRequestType(req_type) ? (req_type | 0x110) : -1; +} + +bool IsStunRequestType(int msg_type) { + return ((msg_type & kStunTypeMask) == 0x000); +} + +bool IsStunIndicationType(int msg_type) { + return ((msg_type & kStunTypeMask) == 0x010); +} + +bool IsStunSuccessResponseType(int msg_type) { + return ((msg_type & kStunTypeMask) == 0x100); +} + +bool IsStunErrorResponseType(int msg_type) { + return ((msg_type & kStunTypeMask) == 0x110); +} + +bool ComputeStunCredentialHash(const std::string& username, + const std::string& realm, + const std::string& password, + std::string* hash) { + // http://tools.ietf.org/html/rfc5389#section-15.4 + // long-term credentials will be calculated using the key and key is + // key = MD5(username ":" realm ":" SASLprep(password)) + std::string input = username; + input += ':'; + input += realm; + input += ':'; + input += password; + + char digest[rtc::MessageDigest::kMaxSize]; + size_t size = rtc::ComputeDigest( + rtc::DIGEST_MD5, input.c_str(), input.size(), + digest, sizeof(digest)); + if (size == 0) { + return false; + } + + *hash = std::string(digest, size); + return true; +} + +} // namespace cricket diff --git a/webrtc/p2p/base/stun.h b/webrtc/p2p/base/stun.h new file mode 100644 index 000000000..0f600dbf7 --- /dev/null +++ b/webrtc/p2p/base/stun.h @@ -0,0 +1,632 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_STUN_H_ +#define WEBRTC_P2P_BASE_STUN_H_ + +// This file contains classes for dealing with the STUN protocol, as specified +// in RFC 5389, and its descendants. + +#include +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/bytebuffer.h" +#include "webrtc/base/socketaddress.h" + +namespace cricket { + +// These are the types of STUN messages defined in RFC 5389. +enum StunMessageType { + STUN_BINDING_REQUEST = 0x0001, + STUN_BINDING_INDICATION = 0x0011, + STUN_BINDING_RESPONSE = 0x0101, + STUN_BINDING_ERROR_RESPONSE = 0x0111, +}; + +// These are all known STUN attributes, defined in RFC 5389 and elsewhere. +// Next to each is the name of the class (T is StunTAttribute) that implements +// that type. +// RETRANSMIT_COUNT is the number of outstanding pings without a response at +// the time the packet is generated. +enum StunAttributeType { + STUN_ATTR_MAPPED_ADDRESS = 0x0001, // Address + STUN_ATTR_USERNAME = 0x0006, // ByteString + STUN_ATTR_MESSAGE_INTEGRITY = 0x0008, // ByteString, 20 bytes + STUN_ATTR_ERROR_CODE = 0x0009, // ErrorCode + STUN_ATTR_UNKNOWN_ATTRIBUTES = 0x000a, // UInt16List + STUN_ATTR_REALM = 0x0014, // ByteString + STUN_ATTR_NONCE = 0x0015, // ByteString + STUN_ATTR_XOR_MAPPED_ADDRESS = 0x0020, // XorAddress + STUN_ATTR_SOFTWARE = 0x8022, // ByteString + STUN_ATTR_ALTERNATE_SERVER = 0x8023, // Address + STUN_ATTR_FINGERPRINT = 0x8028, // UInt32 + STUN_ATTR_RETRANSMIT_COUNT = 0xFF00 // UInt32 +}; + +// These are the types of the values associated with the attributes above. +// This allows us to perform some basic validation when reading or adding +// attributes. Note that these values are for our own use, and not defined in +// RFC 5389. +enum StunAttributeValueType { + STUN_VALUE_UNKNOWN = 0, + STUN_VALUE_ADDRESS = 1, + STUN_VALUE_XOR_ADDRESS = 2, + STUN_VALUE_UINT32 = 3, + STUN_VALUE_UINT64 = 4, + STUN_VALUE_BYTE_STRING = 5, + STUN_VALUE_ERROR_CODE = 6, + STUN_VALUE_UINT16_LIST = 7 +}; + +// These are the types of STUN addresses defined in RFC 5389. +enum StunAddressFamily { + // NB: UNDEF is not part of the STUN spec. + STUN_ADDRESS_UNDEF = 0, + STUN_ADDRESS_IPV4 = 1, + STUN_ADDRESS_IPV6 = 2 +}; + +// These are the types of STUN error codes defined in RFC 5389. +enum StunErrorCode { + STUN_ERROR_TRY_ALTERNATE = 300, + STUN_ERROR_BAD_REQUEST = 400, + STUN_ERROR_UNAUTHORIZED = 401, + STUN_ERROR_UNKNOWN_ATTRIBUTE = 420, + STUN_ERROR_STALE_CREDENTIALS = 430, // GICE only + STUN_ERROR_STALE_NONCE = 438, + STUN_ERROR_SERVER_ERROR = 500, + STUN_ERROR_GLOBAL_FAILURE = 600 +}; + +// Strings for the error codes above. +extern const char STUN_ERROR_REASON_TRY_ALTERNATE_SERVER[]; +extern const char STUN_ERROR_REASON_BAD_REQUEST[]; +extern const char STUN_ERROR_REASON_UNAUTHORIZED[]; +extern const char STUN_ERROR_REASON_UNKNOWN_ATTRIBUTE[]; +extern const char STUN_ERROR_REASON_STALE_CREDENTIALS[]; +extern const char STUN_ERROR_REASON_STALE_NONCE[]; +extern const char STUN_ERROR_REASON_SERVER_ERROR[]; + +// The mask used to determine whether a STUN message is a request/response etc. +const uint32 kStunTypeMask = 0x0110; + +// STUN Attribute header length. +const size_t kStunAttributeHeaderSize = 4; + +// Following values correspond to RFC5389. +const size_t kStunHeaderSize = 20; +const size_t kStunTransactionIdOffset = 8; +const size_t kStunTransactionIdLength = 12; +const uint32 kStunMagicCookie = 0x2112A442; +const size_t kStunMagicCookieLength = sizeof(kStunMagicCookie); + +// Following value corresponds to an earlier version of STUN from +// RFC3489. +const size_t kStunLegacyTransactionIdLength = 16; + +// STUN Message Integrity HMAC length. +const size_t kStunMessageIntegritySize = 20; + +class StunAttribute; +class StunAddressAttribute; +class StunXorAddressAttribute; +class StunUInt32Attribute; +class StunUInt64Attribute; +class StunByteStringAttribute; +class StunErrorCodeAttribute; +class StunUInt16ListAttribute; + +// 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(); + virtual ~StunMessage(); + + int type() const { return type_; } + size_t length() const { return length_; } + const std::string& transaction_id() const { return transaction_id_; } + + // Returns true if the message confirms to RFC3489 rather than + // RFC5389. The main difference between two version of the STUN + // protocol is the presence of the magic cookie and different length + // of transaction ID. For outgoing packets version of the protocol + // is determined by the lengths of the transaction ID. + bool IsLegacy() const; + + void SetType(int type) { type_ = static_cast(type); } + bool SetTransactionID(const std::string& str); + + // Gets the desired attribute value, or NULL if no such attribute type exists. + const StunAddressAttribute* GetAddress(int type) const; + const StunUInt32Attribute* GetUInt32(int type) const; + const StunUInt64Attribute* GetUInt64(int type) const; + const StunByteStringAttribute* GetByteString(int type) const; + + // Gets these specific attribute values. + const StunErrorCodeAttribute* GetErrorCode() const; + const StunUInt16ListAttribute* GetUnknownAttributes() const; + + // Takes ownership of the specified attribute, verifies it is of the correct + // type, and adds it to the message. The return value indicates whether this + // was successful. + bool AddAttribute(StunAttribute* attr); + + // Validates that a raw STUN message has a correct MESSAGE-INTEGRITY value. + // This can't currently be done on a StunMessage, since it is affected by + // padding data (which we discard when reading a StunMessage). + static bool ValidateMessageIntegrity(const char* data, size_t size, + const std::string& password); + // Adds a MESSAGE-INTEGRITY attribute that is valid for the current message. + bool AddMessageIntegrity(const std::string& password); + bool AddMessageIntegrity(const char* key, size_t keylen); + + // Verifies that a given buffer is STUN by checking for a correct FINGERPRINT. + static bool ValidateFingerprint(const char* data, size_t size); + + // Adds a FINGERPRINT attribute that is valid for the current message. + bool AddFingerprint(); + + // Parses the STUN packet in the given buffer and records it here. The + // return value indicates whether this was successful. + bool Read(rtc::ByteBuffer* buf); + + // Writes this object into a STUN packet. The return value indicates whether + // this was successful. + bool Write(rtc::ByteBuffer* buf) const; + + // Creates an empty message. Overridable by derived classes. + virtual StunMessage* CreateNew() const { return new StunMessage(); } + + protected: + // Verifies that the given attribute is allowed for this message. + virtual StunAttributeValueType GetAttributeValueType(int type) const; + + private: + StunAttribute* CreateAttribute(int type, size_t length) /* const*/; + const StunAttribute* GetAttribute(int type) const; + static bool IsValidTransactionId(const std::string& transaction_id); + + uint16 type_; + uint16 length_; + std::string transaction_id_; + std::vector* attrs_; +}; + +// Base class for all STUN/TURN attributes. +class StunAttribute { + public: + virtual ~StunAttribute() { + } + + int type() const { return type_; } + size_t length() const { return length_; } + + // Return the type of this attribute. + virtual StunAttributeValueType value_type() const = 0; + + // Only XorAddressAttribute needs this so far. + virtual void SetOwner(StunMessage* owner) {} + + // 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(rtc::ByteBuffer* buf) = 0; + + // Writes the body (not the type or length) to the given buffer. Return + // value is true if successful. + virtual bool Write(rtc::ByteBuffer* buf) const = 0; + + // Creates an attribute object with the given type and smallest length. + static StunAttribute* Create(StunAttributeValueType value_type, uint16 type, + uint16 length, StunMessage* owner); + // TODO: Allow these create functions to take parameters, to reduce + // the amount of work callers need to do to initialize attributes. + static StunAddressAttribute* CreateAddress(uint16 type); + static StunXorAddressAttribute* CreateXorAddress(uint16 type); + static StunUInt32Attribute* CreateUInt32(uint16 type); + static StunUInt64Attribute* CreateUInt64(uint16 type); + static StunByteStringAttribute* CreateByteString(uint16 type); + static StunErrorCodeAttribute* CreateErrorCode(); + static StunUInt16ListAttribute* CreateUnknownAttributes(); + + protected: + StunAttribute(uint16 type, uint16 length); + void SetLength(uint16 length) { length_ = length; } + void WritePadding(rtc::ByteBuffer* buf) const; + void ConsumePadding(rtc::ByteBuffer* buf) const; + + private: + uint16 type_; + uint16 length_; +}; + +// Implements STUN attributes that record an Internet address. +class StunAddressAttribute : public StunAttribute { + public: + static const uint16 SIZE_UNDEF = 0; + static const uint16 SIZE_IP4 = 8; + static const uint16 SIZE_IP6 = 20; + StunAddressAttribute(uint16 type, const rtc::SocketAddress& addr); + StunAddressAttribute(uint16 type, uint16 length); + + virtual StunAttributeValueType value_type() const { + return STUN_VALUE_ADDRESS; + } + + StunAddressFamily family() const { + switch (address_.ipaddr().family()) { + case AF_INET: + return STUN_ADDRESS_IPV4; + case AF_INET6: + return STUN_ADDRESS_IPV6; + } + return STUN_ADDRESS_UNDEF; + } + + const rtc::SocketAddress& GetAddress() const { return address_; } + const rtc::IPAddress& ipaddr() const { return address_.ipaddr(); } + uint16 port() const { return address_.port(); } + + void SetAddress(const rtc::SocketAddress& addr) { + address_ = addr; + EnsureAddressLength(); + } + void SetIP(const rtc::IPAddress& ip) { + address_.SetIP(ip); + EnsureAddressLength(); + } + void SetPort(uint16 port) { address_.SetPort(port); } + + virtual bool Read(rtc::ByteBuffer* buf); + virtual bool Write(rtc::ByteBuffer* buf) const; + + private: + void EnsureAddressLength() { + switch (family()) { + case STUN_ADDRESS_IPV4: { + SetLength(SIZE_IP4); + break; + } + case STUN_ADDRESS_IPV6: { + SetLength(SIZE_IP6); + break; + } + default: { + SetLength(SIZE_UNDEF); + break; + } + } + } + rtc::SocketAddress address_; +}; + +// Implements STUN attributes that record an Internet address. When encoded +// in a STUN message, the address contained in this attribute is XORed with the +// transaction ID of the message. +class StunXorAddressAttribute : public StunAddressAttribute { + public: + StunXorAddressAttribute(uint16 type, const rtc::SocketAddress& addr); + StunXorAddressAttribute(uint16 type, uint16 length, + StunMessage* owner); + + virtual StunAttributeValueType value_type() const { + return STUN_VALUE_XOR_ADDRESS; + } + virtual void SetOwner(StunMessage* owner) { + owner_ = owner; + } + virtual bool Read(rtc::ByteBuffer* buf); + virtual bool Write(rtc::ByteBuffer* buf) const; + + private: + rtc::IPAddress GetXoredIP() const; + StunMessage* owner_; +}; + +// Implements STUN attributes that record a 32-bit integer. +class StunUInt32Attribute : public StunAttribute { + public: + static const uint16 SIZE = 4; + StunUInt32Attribute(uint16 type, uint32 value); + explicit StunUInt32Attribute(uint16 type); + + virtual StunAttributeValueType value_type() const { + return STUN_VALUE_UINT32; + } + + uint32 value() const { return bits_; } + void SetValue(uint32 bits) { bits_ = bits; } + + bool GetBit(size_t index) const; + void SetBit(size_t index, bool value); + + virtual bool Read(rtc::ByteBuffer* buf); + virtual bool Write(rtc::ByteBuffer* buf) const; + + private: + uint32 bits_; +}; + +class StunUInt64Attribute : public StunAttribute { + public: + static const uint16 SIZE = 8; + StunUInt64Attribute(uint16 type, uint64 value); + explicit StunUInt64Attribute(uint16 type); + + virtual StunAttributeValueType value_type() const { + return STUN_VALUE_UINT64; + } + + uint64 value() const { return bits_; } + void SetValue(uint64 bits) { bits_ = bits; } + + virtual bool Read(rtc::ByteBuffer* buf); + virtual bool Write(rtc::ByteBuffer* buf) const; + + private: + uint64 bits_; +}; + +// Implements STUN attributes that record an arbitrary byte string. +class StunByteStringAttribute : public StunAttribute { + public: + explicit StunByteStringAttribute(uint16 type); + StunByteStringAttribute(uint16 type, const std::string& str); + StunByteStringAttribute(uint16 type, const void* bytes, size_t length); + StunByteStringAttribute(uint16 type, uint16 length); + ~StunByteStringAttribute(); + + virtual StunAttributeValueType value_type() const { + return STUN_VALUE_BYTE_STRING; + } + + const char* bytes() const { return bytes_; } + std::string GetString() const { return std::string(bytes_, length()); } + + void CopyBytes(const char* bytes); // uses strlen + void CopyBytes(const void* bytes, size_t length); + + uint8 GetByte(size_t index) const; + void SetByte(size_t index, uint8 value); + + virtual bool Read(rtc::ByteBuffer* buf); + virtual bool Write(rtc::ByteBuffer* buf) const; + + private: + void SetBytes(char* bytes, size_t length); + + char* bytes_; +}; + +// Implements STUN attributes that record an error code. +class StunErrorCodeAttribute : public StunAttribute { + public: + static const uint16 MIN_SIZE = 4; + StunErrorCodeAttribute(uint16 type, int code, const std::string& reason); + StunErrorCodeAttribute(uint16 type, uint16 length); + ~StunErrorCodeAttribute(); + + virtual StunAttributeValueType value_type() const { + return STUN_VALUE_ERROR_CODE; + } + + // The combined error and class, e.g. 0x400. + int code() const; + void SetCode(int code); + + // The individual error components. + int eclass() const { return class_; } + int number() const { return number_; } + const std::string& reason() const { return reason_; } + void SetClass(uint8 eclass) { class_ = eclass; } + void SetNumber(uint8 number) { number_ = number; } + void SetReason(const std::string& reason); + + bool Read(rtc::ByteBuffer* buf); + bool Write(rtc::ByteBuffer* buf) const; + + private: + uint8 class_; + uint8 number_; + std::string reason_; +}; + +// Implements STUN attributes that record a list of attribute names. +class StunUInt16ListAttribute : public StunAttribute { + public: + StunUInt16ListAttribute(uint16 type, uint16 length); + ~StunUInt16ListAttribute(); + + virtual StunAttributeValueType value_type() const { + return STUN_VALUE_UINT16_LIST; + } + + size_t Size() const; + uint16 GetType(int index) const; + void SetType(int index, uint16 value); + void AddType(uint16 value); + + bool Read(rtc::ByteBuffer* buf); + bool Write(rtc::ByteBuffer* buf) const; + + private: + std::vector* attr_types_; +}; + +// Returns the (successful) response type for the given request type. +// Returns -1 if |request_type| is not a valid request type. +int GetStunSuccessResponseType(int request_type); + +// Returns the error response type for the given request type. +// Returns -1 if |request_type| is not a valid request type. +int GetStunErrorResponseType(int request_type); + +// Returns whether a given message is a request type. +bool IsStunRequestType(int msg_type); + +// Returns whether a given message is an indication type. +bool IsStunIndicationType(int msg_type); + +// Returns whether a given response is a success type. +bool IsStunSuccessResponseType(int msg_type); + +// Returns whether a given response is an error type. +bool IsStunErrorResponseType(int msg_type); + +// Computes the STUN long-term credential hash. +bool ComputeStunCredentialHash(const std::string& username, + const std::string& realm, const std::string& password, std::string* hash); + +// TODO: Move the TURN/ICE stuff below out to separate files. +extern const char TURN_MAGIC_COOKIE_VALUE[4]; + +// "GTURN" STUN methods. +// TODO: Rename these methods to GTURN_ to make it clear they aren't +// part of standard STUN/TURN. +enum RelayMessageType { + // For now, using the same defs from TurnMessageType below. + // 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, +}; + +// "GTURN"-specific STUN attributes. +// TODO: Rename these attributes to GTURN_ to avoid conflicts. +enum RelayAttributeType { + STUN_ATTR_LIFETIME = 0x000d, // UInt32 + 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 +}; + +// A "GTURN" STUN message. +class RelayMessage : public StunMessage { + protected: + virtual StunAttributeValueType GetAttributeValueType(int type) const { + switch (type) { + case STUN_ATTR_LIFETIME: return STUN_VALUE_UINT32; + case STUN_ATTR_MAGIC_COOKIE: return STUN_VALUE_BYTE_STRING; + case STUN_ATTR_BANDWIDTH: return STUN_VALUE_UINT32; + case STUN_ATTR_DESTINATION_ADDRESS: return STUN_VALUE_ADDRESS; + case STUN_ATTR_SOURCE_ADDRESS2: return STUN_VALUE_ADDRESS; + case STUN_ATTR_DATA: return STUN_VALUE_BYTE_STRING; + case STUN_ATTR_OPTIONS: return STUN_VALUE_UINT32; + default: return StunMessage::GetAttributeValueType(type); + } + } + virtual StunMessage* CreateNew() const { return new RelayMessage(); } +}; + +// Defined in TURN RFC 5766. +enum TurnMessageType { + STUN_ALLOCATE_REQUEST = 0x0003, + STUN_ALLOCATE_RESPONSE = 0x0103, + STUN_ALLOCATE_ERROR_RESPONSE = 0x0113, + TURN_REFRESH_REQUEST = 0x0004, + TURN_REFRESH_RESPONSE = 0x0104, + TURN_REFRESH_ERROR_RESPONSE = 0x0114, + TURN_SEND_INDICATION = 0x0016, + TURN_DATA_INDICATION = 0x0017, + TURN_CREATE_PERMISSION_REQUEST = 0x0008, + TURN_CREATE_PERMISSION_RESPONSE = 0x0108, + TURN_CREATE_PERMISSION_ERROR_RESPONSE = 0x0118, + TURN_CHANNEL_BIND_REQUEST = 0x0009, + TURN_CHANNEL_BIND_RESPONSE = 0x0109, + TURN_CHANNEL_BIND_ERROR_RESPONSE = 0x0119, +}; + +enum TurnAttributeType { + STUN_ATTR_CHANNEL_NUMBER = 0x000C, // UInt32 + STUN_ATTR_TURN_LIFETIME = 0x000d, // UInt32 + STUN_ATTR_XOR_PEER_ADDRESS = 0x0012, // XorAddress + // TODO(mallinath) - Uncomment after RelayAttributes are renamed. + // STUN_ATTR_DATA = 0x0013, // ByteString + STUN_ATTR_XOR_RELAYED_ADDRESS = 0x0016, // XorAddress + STUN_ATTR_EVEN_PORT = 0x0018, // ByteString, 1 byte. + STUN_ATTR_REQUESTED_TRANSPORT = 0x0019, // UInt32 + STUN_ATTR_DONT_FRAGMENT = 0x001A, // No content, Length = 0 + STUN_ATTR_RESERVATION_TOKEN = 0x0022, // ByteString, 8 bytes. + // TODO(mallinath) - Rename STUN_ATTR_TURN_LIFETIME to STUN_ATTR_LIFETIME and + // STUN_ATTR_TURN_DATA to STUN_ATTR_DATA. Also rename RelayMessage attributes + // by appending G to attribute name. +}; + +// RFC 5766-defined errors. +enum TurnErrorType { + STUN_ERROR_FORBIDDEN = 403, + STUN_ERROR_ALLOCATION_MISMATCH = 437, + STUN_ERROR_WRONG_CREDENTIALS = 441, + STUN_ERROR_UNSUPPORTED_PROTOCOL = 442 +}; +extern const char STUN_ERROR_REASON_FORBIDDEN[]; +extern const char STUN_ERROR_REASON_ALLOCATION_MISMATCH[]; +extern const char STUN_ERROR_REASON_WRONG_CREDENTIALS[]; +extern const char STUN_ERROR_REASON_UNSUPPORTED_PROTOCOL[]; +class TurnMessage : public StunMessage { + protected: + virtual StunAttributeValueType GetAttributeValueType(int type) const { + switch (type) { + case STUN_ATTR_CHANNEL_NUMBER: return STUN_VALUE_UINT32; + case STUN_ATTR_TURN_LIFETIME: return STUN_VALUE_UINT32; + case STUN_ATTR_XOR_PEER_ADDRESS: return STUN_VALUE_XOR_ADDRESS; + case STUN_ATTR_DATA: return STUN_VALUE_BYTE_STRING; + case STUN_ATTR_XOR_RELAYED_ADDRESS: return STUN_VALUE_XOR_ADDRESS; + case STUN_ATTR_EVEN_PORT: return STUN_VALUE_BYTE_STRING; + case STUN_ATTR_REQUESTED_TRANSPORT: return STUN_VALUE_UINT32; + case STUN_ATTR_DONT_FRAGMENT: return STUN_VALUE_BYTE_STRING; + case STUN_ATTR_RESERVATION_TOKEN: return STUN_VALUE_BYTE_STRING; + default: return StunMessage::GetAttributeValueType(type); + } + } + virtual StunMessage* CreateNew() const { return new TurnMessage(); } +}; + +// RFC 5245 ICE STUN attributes. +enum IceAttributeType { + STUN_ATTR_PRIORITY = 0x0024, // UInt32 + STUN_ATTR_USE_CANDIDATE = 0x0025, // No content, Length = 0 + STUN_ATTR_ICE_CONTROLLED = 0x8029, // UInt64 + STUN_ATTR_ICE_CONTROLLING = 0x802A // UInt64 +}; + +// RFC 5245-defined errors. +enum IceErrorCode { + STUN_ERROR_ROLE_CONFLICT = 487, +}; +extern const char STUN_ERROR_REASON_ROLE_CONFLICT[]; + +// A RFC 5245 ICE STUN message. +class IceMessage : public StunMessage { + protected: + virtual StunAttributeValueType GetAttributeValueType(int type) const { + switch (type) { + case STUN_ATTR_PRIORITY: return STUN_VALUE_UINT32; + case STUN_ATTR_USE_CANDIDATE: return STUN_VALUE_BYTE_STRING; + case STUN_ATTR_ICE_CONTROLLED: return STUN_VALUE_UINT64; + case STUN_ATTR_ICE_CONTROLLING: return STUN_VALUE_UINT64; + default: return StunMessage::GetAttributeValueType(type); + } + } + virtual StunMessage* CreateNew() const { return new IceMessage(); } +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_STUN_H_ diff --git a/webrtc/p2p/base/stun_unittest.cc b/webrtc/p2p/base/stun_unittest.cc new file mode 100644 index 000000000..396beb6af --- /dev/null +++ b/webrtc/p2p/base/stun_unittest.cc @@ -0,0 +1,1402 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/p2p/base/stun.h" +#include "webrtc/base/bytebuffer.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/messagedigest.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/socketaddress.h" + +namespace cricket { + +class StunTest : public ::testing::Test { + protected: + void CheckStunHeader(const StunMessage& msg, StunMessageType expected_type, + size_t expected_length) { + ASSERT_EQ(expected_type, msg.type()); + ASSERT_EQ(expected_length, msg.length()); + } + + void CheckStunTransactionID(const StunMessage& msg, + const unsigned char* expectedID, size_t length) { + ASSERT_EQ(length, msg.transaction_id().size()); + ASSERT_EQ(length == kStunTransactionIdLength + 4, msg.IsLegacy()); + ASSERT_EQ(length == kStunTransactionIdLength, !msg.IsLegacy()); + ASSERT_EQ(0, memcmp(msg.transaction_id().c_str(), expectedID, length)); + } + + void CheckStunAddressAttribute(const StunAddressAttribute* addr, + StunAddressFamily expected_family, + int expected_port, + rtc::IPAddress expected_address) { + ASSERT_EQ(expected_family, addr->family()); + ASSERT_EQ(expected_port, addr->port()); + + if (addr->family() == STUN_ADDRESS_IPV4) { + in_addr v4_address = expected_address.ipv4_address(); + in_addr stun_address = addr->ipaddr().ipv4_address(); + ASSERT_EQ(0, memcmp(&v4_address, &stun_address, sizeof(stun_address))); + } else if (addr->family() == STUN_ADDRESS_IPV6) { + in6_addr v6_address = expected_address.ipv6_address(); + in6_addr stun_address = addr->ipaddr().ipv6_address(); + ASSERT_EQ(0, memcmp(&v6_address, &stun_address, sizeof(stun_address))); + } else { + ASSERT_TRUE(addr->family() == STUN_ADDRESS_IPV6 || + addr->family() == STUN_ADDRESS_IPV4); + } + } + + size_t ReadStunMessageTestCase(StunMessage* msg, + const unsigned char* testcase, + size_t size) { + const char* input = reinterpret_cast(testcase); + rtc::ByteBuffer buf(input, size); + if (msg->Read(&buf)) { + // Returns the size the stun message should report itself as being + return (size - 20); + } else { + return 0; + } + } +}; + + +// Sample STUN packets with various attributes +// Gathered by wiresharking pjproject's pjnath test programs +// pjproject available at www.pjsip.org + +static const unsigned char kStunMessageWithIPv6MappedAddress[] = { + 0x00, 0x01, 0x00, 0x18, // message header + 0x21, 0x12, 0xa4, 0x42, // transaction id + 0x29, 0x1f, 0xcd, 0x7c, + 0xba, 0x58, 0xab, 0xd7, + 0xf2, 0x41, 0x01, 0x00, + 0x00, 0x01, 0x00, 0x14, // Address type (mapped), length + 0x00, 0x02, 0xb8, 0x81, // family (IPv6), port + 0x24, 0x01, 0xfa, 0x00, // an IPv6 address + 0x00, 0x04, 0x10, 0x00, + 0xbe, 0x30, 0x5b, 0xff, + 0xfe, 0xe5, 0x00, 0xc3 +}; + +static const unsigned char kStunMessageWithIPv4MappedAddress[] = { + 0x01, 0x01, 0x00, 0x0c, // binding response, length 12 + 0x21, 0x12, 0xa4, 0x42, // magic cookie + 0x29, 0x1f, 0xcd, 0x7c, // transaction ID + 0xba, 0x58, 0xab, 0xd7, + 0xf2, 0x41, 0x01, 0x00, + 0x00, 0x01, 0x00, 0x08, // Mapped, 8 byte length + 0x00, 0x01, 0x9d, 0xfc, // AF_INET, unxor-ed port + 0xac, 0x17, 0x44, 0xe6 // IPv4 address +}; + +// Test XOR-mapped IP addresses: +static const unsigned char kStunMessageWithIPv6XorMappedAddress[] = { + 0x01, 0x01, 0x00, 0x18, // message header (binding response) + 0x21, 0x12, 0xa4, 0x42, // magic cookie (rfc5389) + 0xe3, 0xa9, 0x46, 0xe1, // transaction ID + 0x7c, 0x00, 0xc2, 0x62, + 0x54, 0x08, 0x01, 0x00, + 0x00, 0x20, 0x00, 0x14, // Address Type (XOR), length + 0x00, 0x02, 0xcb, 0x5b, // family, XOR-ed port + 0x05, 0x13, 0x5e, 0x42, // XOR-ed IPv6 address + 0xe3, 0xad, 0x56, 0xe1, + 0xc2, 0x30, 0x99, 0x9d, + 0xaa, 0xed, 0x01, 0xc3 +}; + +static const unsigned char kStunMessageWithIPv4XorMappedAddress[] = { + 0x01, 0x01, 0x00, 0x0c, // message header (binding response) + 0x21, 0x12, 0xa4, 0x42, // magic cookie + 0x29, 0x1f, 0xcd, 0x7c, // transaction ID + 0xba, 0x58, 0xab, 0xd7, + 0xf2, 0x41, 0x01, 0x00, + 0x00, 0x20, 0x00, 0x08, // address type (xor), length + 0x00, 0x01, 0xfc, 0xb5, // family (AF_INET), XOR-ed port + 0x8d, 0x05, 0xe0, 0xa4 // IPv4 address +}; + +// ByteString Attribute (username) +static const unsigned char kStunMessageWithByteStringAttribute[] = { + 0x00, 0x01, 0x00, 0x0c, + 0x21, 0x12, 0xa4, 0x42, + 0xe3, 0xa9, 0x46, 0xe1, + 0x7c, 0x00, 0xc2, 0x62, + 0x54, 0x08, 0x01, 0x00, + 0x00, 0x06, 0x00, 0x08, // username attribute (length 8) + 0x61, 0x62, 0x63, 0x64, // abcdefgh + 0x65, 0x66, 0x67, 0x68 +}; + +// Message with an unknown but comprehensible optional attribute. +// Parsing should succeed despite this unknown attribute. +static const unsigned char kStunMessageWithUnknownAttribute[] = { + 0x00, 0x01, 0x00, 0x14, + 0x21, 0x12, 0xa4, 0x42, + 0xe3, 0xa9, 0x46, 0xe1, + 0x7c, 0x00, 0xc2, 0x62, + 0x54, 0x08, 0x01, 0x00, + 0x00, 0xaa, 0x00, 0x07, // Unknown attribute, length 7 (needs padding!) + 0x61, 0x62, 0x63, 0x64, // abcdefg + padding + 0x65, 0x66, 0x67, 0x00, + 0x00, 0x06, 0x00, 0x03, // Followed by a known attribute we can + 0x61, 0x62, 0x63, 0x00 // check for (username of length 3) +}; + +// ByteString Attribute (username) with padding byte +static const unsigned char kStunMessageWithPaddedByteStringAttribute[] = { + 0x00, 0x01, 0x00, 0x08, + 0x21, 0x12, 0xa4, 0x42, + 0xe3, 0xa9, 0x46, 0xe1, + 0x7c, 0x00, 0xc2, 0x62, + 0x54, 0x08, 0x01, 0x00, + 0x00, 0x06, 0x00, 0x03, // username attribute (length 3) + 0x61, 0x62, 0x63, 0xcc // abc +}; + +// Message with an Unknown Attributes (uint16 list) attribute. +static const unsigned char kStunMessageWithUInt16ListAttribute[] = { + 0x00, 0x01, 0x00, 0x0c, + 0x21, 0x12, 0xa4, 0x42, + 0xe3, 0xa9, 0x46, 0xe1, + 0x7c, 0x00, 0xc2, 0x62, + 0x54, 0x08, 0x01, 0x00, + 0x00, 0x0a, 0x00, 0x06, // username attribute (length 6) + 0x00, 0x01, 0x10, 0x00, // three attributes plus padding + 0xAB, 0xCU, 0xBE, 0xEF +}; + +// Error response message (unauthorized) +static const unsigned char kStunMessageWithErrorAttribute[] = { + 0x01, 0x11, 0x00, 0x14, + 0x21, 0x12, 0xa4, 0x42, + 0x29, 0x1f, 0xcd, 0x7c, + 0xba, 0x58, 0xab, 0xd7, + 0xf2, 0x41, 0x01, 0x00, + 0x00, 0x09, 0x00, 0x10, + 0x00, 0x00, 0x04, 0x01, + 0x55, 0x6e, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x65, 0x64 +}; + +// Sample messages with an invalid length Field + +// The actual length in bytes of the invalid messages (including STUN header) +static const int kRealLengthOfInvalidLengthTestCases = 32; + +static const unsigned char kStunMessageWithZeroLength[] = { + 0x00, 0x01, 0x00, 0x00, // length of 0 (last 2 bytes) + 0x21, 0x12, 0xA4, 0x42, // magic cookie + '0', '1', '2', '3', // transaction id + '4', '5', '6', '7', + '8', '9', 'a', 'b', + 0x00, 0x20, 0x00, 0x08, // xor mapped address + 0x00, 0x01, 0x21, 0x1F, + 0x21, 0x12, 0xA4, 0x53, +}; + +static const unsigned char kStunMessageWithExcessLength[] = { + 0x00, 0x01, 0x00, 0x55, // length of 85 + 0x21, 0x12, 0xA4, 0x42, // magic cookie + '0', '1', '2', '3', // transaction id + '4', '5', '6', '7', + '8', '9', 'a', 'b', + 0x00, 0x20, 0x00, 0x08, // xor mapped address + 0x00, 0x01, 0x21, 0x1F, + 0x21, 0x12, 0xA4, 0x53, +}; + +static const unsigned char kStunMessageWithSmallLength[] = { + 0x00, 0x01, 0x00, 0x03, // length of 3 + 0x21, 0x12, 0xA4, 0x42, // magic cookie + '0', '1', '2', '3', // transaction id + '4', '5', '6', '7', + '8', '9', 'a', 'b', + 0x00, 0x20, 0x00, 0x08, // xor mapped address + 0x00, 0x01, 0x21, 0x1F, + 0x21, 0x12, 0xA4, 0x53, +}; + +// RTCP packet, for testing we correctly ignore non stun packet types. +// V=2, P=false, RC=0, Type=200, Len=6, Sender-SSRC=85, etc +static const unsigned char kRtcpPacket[] = { + 0x80, 0xc8, 0x00, 0x06, 0x00, 0x00, 0x00, 0x55, + 0xce, 0xa5, 0x18, 0x3a, 0x39, 0xcc, 0x7d, 0x09, + 0x23, 0xed, 0x19, 0x07, 0x00, 0x00, 0x01, 0x56, + 0x00, 0x03, 0x73, 0x50, +}; + +// RFC5769 Test Vectors +// Software name (request): "STUN test client" (without quotes) +// Software name (response): "test vector" (without quotes) +// Username: "evtj:h6vY" (without quotes) +// Password: "VOkJxbRl1RmTxUk/WvJxBt" (without quotes) +static const unsigned char kRfc5769SampleMsgTransactionId[] = { + 0xb7, 0xe7, 0xa7, 0x01, 0xbc, 0x34, 0xd6, 0x86, 0xfa, 0x87, 0xdf, 0xae +}; +static const char kRfc5769SampleMsgClientSoftware[] = "STUN test client"; +static const char kRfc5769SampleMsgServerSoftware[] = "test vector"; +static const char kRfc5769SampleMsgUsername[] = "evtj:h6vY"; +static const char kRfc5769SampleMsgPassword[] = "VOkJxbRl1RmTxUk/WvJxBt"; +static const rtc::SocketAddress kRfc5769SampleMsgMappedAddress( + "192.0.2.1", 32853); +static const rtc::SocketAddress kRfc5769SampleMsgIPv6MappedAddress( + "2001:db8:1234:5678:11:2233:4455:6677", 32853); + +static const unsigned char kRfc5769SampleMsgWithAuthTransactionId[] = { + 0x78, 0xad, 0x34, 0x33, 0xc6, 0xad, 0x72, 0xc0, 0x29, 0xda, 0x41, 0x2e +}; +static const char kRfc5769SampleMsgWithAuthUsername[] = + "\xe3\x83\x9e\xe3\x83\x88\xe3\x83\xaa\xe3\x83\x83\xe3\x82\xaf\xe3\x82\xb9"; +static const char kRfc5769SampleMsgWithAuthPassword[] = "TheMatrIX"; +static const char kRfc5769SampleMsgWithAuthNonce[] = + "f//499k954d6OL34oL9FSTvy64sA"; +static const char kRfc5769SampleMsgWithAuthRealm[] = "example.org"; + +// 2.1. Sample Request +static const unsigned char kRfc5769SampleRequest[] = { + 0x00, 0x01, 0x00, 0x58, // Request type and message length + 0x21, 0x12, 0xa4, 0x42, // Magic cookie + 0xb7, 0xe7, 0xa7, 0x01, // } + 0xbc, 0x34, 0xd6, 0x86, // } Transaction ID + 0xfa, 0x87, 0xdf, 0xae, // } + 0x80, 0x22, 0x00, 0x10, // SOFTWARE attribute header + 0x53, 0x54, 0x55, 0x4e, // } + 0x20, 0x74, 0x65, 0x73, // } User-agent... + 0x74, 0x20, 0x63, 0x6c, // } ...name + 0x69, 0x65, 0x6e, 0x74, // } + 0x00, 0x24, 0x00, 0x04, // PRIORITY attribute header + 0x6e, 0x00, 0x01, 0xff, // ICE priority value + 0x80, 0x29, 0x00, 0x08, // ICE-CONTROLLED attribute header + 0x93, 0x2f, 0xf9, 0xb1, // } Pseudo-random tie breaker... + 0x51, 0x26, 0x3b, 0x36, // } ...for ICE control + 0x00, 0x06, 0x00, 0x09, // USERNAME attribute header + 0x65, 0x76, 0x74, 0x6a, // } + 0x3a, 0x68, 0x36, 0x76, // } Username (9 bytes) and padding (3 bytes) + 0x59, 0x20, 0x20, 0x20, // } + 0x00, 0x08, 0x00, 0x14, // MESSAGE-INTEGRITY attribute header + 0x9a, 0xea, 0xa7, 0x0c, // } + 0xbf, 0xd8, 0xcb, 0x56, // } + 0x78, 0x1e, 0xf2, 0xb5, // } HMAC-SHA1 fingerprint + 0xb2, 0xd3, 0xf2, 0x49, // } + 0xc1, 0xb5, 0x71, 0xa2, // } + 0x80, 0x28, 0x00, 0x04, // FINGERPRINT attribute header + 0xe5, 0x7a, 0x3b, 0xcf // CRC32 fingerprint +}; + +// 2.2. Sample IPv4 Response +static const unsigned char kRfc5769SampleResponse[] = { + 0x01, 0x01, 0x00, 0x3c, // Response type and message length + 0x21, 0x12, 0xa4, 0x42, // Magic cookie + 0xb7, 0xe7, 0xa7, 0x01, // } + 0xbc, 0x34, 0xd6, 0x86, // } Transaction ID + 0xfa, 0x87, 0xdf, 0xae, // } + 0x80, 0x22, 0x00, 0x0b, // SOFTWARE attribute header + 0x74, 0x65, 0x73, 0x74, // } + 0x20, 0x76, 0x65, 0x63, // } UTF-8 server name + 0x74, 0x6f, 0x72, 0x20, // } + 0x00, 0x20, 0x00, 0x08, // XOR-MAPPED-ADDRESS attribute header + 0x00, 0x01, 0xa1, 0x47, // Address family (IPv4) and xor'd mapped port + 0xe1, 0x12, 0xa6, 0x43, // Xor'd mapped IPv4 address + 0x00, 0x08, 0x00, 0x14, // MESSAGE-INTEGRITY attribute header + 0x2b, 0x91, 0xf5, 0x99, // } + 0xfd, 0x9e, 0x90, 0xc3, // } + 0x8c, 0x74, 0x89, 0xf9, // } HMAC-SHA1 fingerprint + 0x2a, 0xf9, 0xba, 0x53, // } + 0xf0, 0x6b, 0xe7, 0xd7, // } + 0x80, 0x28, 0x00, 0x04, // FINGERPRINT attribute header + 0xc0, 0x7d, 0x4c, 0x96 // CRC32 fingerprint +}; + +// 2.3. Sample IPv6 Response +static const unsigned char kRfc5769SampleResponseIPv6[] = { + 0x01, 0x01, 0x00, 0x48, // Response type and message length + 0x21, 0x12, 0xa4, 0x42, // Magic cookie + 0xb7, 0xe7, 0xa7, 0x01, // } + 0xbc, 0x34, 0xd6, 0x86, // } Transaction ID + 0xfa, 0x87, 0xdf, 0xae, // } + 0x80, 0x22, 0x00, 0x0b, // SOFTWARE attribute header + 0x74, 0x65, 0x73, 0x74, // } + 0x20, 0x76, 0x65, 0x63, // } UTF-8 server name + 0x74, 0x6f, 0x72, 0x20, // } + 0x00, 0x20, 0x00, 0x14, // XOR-MAPPED-ADDRESS attribute header + 0x00, 0x02, 0xa1, 0x47, // Address family (IPv6) and xor'd mapped port. + 0x01, 0x13, 0xa9, 0xfa, // } + 0xa5, 0xd3, 0xf1, 0x79, // } Xor'd mapped IPv6 address + 0xbc, 0x25, 0xf4, 0xb5, // } + 0xbe, 0xd2, 0xb9, 0xd9, // } + 0x00, 0x08, 0x00, 0x14, // MESSAGE-INTEGRITY attribute header + 0xa3, 0x82, 0x95, 0x4e, // } + 0x4b, 0xe6, 0x7b, 0xf1, // } + 0x17, 0x84, 0xc9, 0x7c, // } HMAC-SHA1 fingerprint + 0x82, 0x92, 0xc2, 0x75, // } + 0xbf, 0xe3, 0xed, 0x41, // } + 0x80, 0x28, 0x00, 0x04, // FINGERPRINT attribute header + 0xc8, 0xfb, 0x0b, 0x4c // CRC32 fingerprint +}; + +// 2.4. Sample Request with Long-Term Authentication +static const unsigned char kRfc5769SampleRequestLongTermAuth[] = { + 0x00, 0x01, 0x00, 0x60, // Request type and message length + 0x21, 0x12, 0xa4, 0x42, // Magic cookie + 0x78, 0xad, 0x34, 0x33, // } + 0xc6, 0xad, 0x72, 0xc0, // } Transaction ID + 0x29, 0xda, 0x41, 0x2e, // } + 0x00, 0x06, 0x00, 0x12, // USERNAME attribute header + 0xe3, 0x83, 0x9e, 0xe3, // } + 0x83, 0x88, 0xe3, 0x83, // } + 0xaa, 0xe3, 0x83, 0x83, // } Username value (18 bytes) and padding (2 bytes) + 0xe3, 0x82, 0xaf, 0xe3, // } + 0x82, 0xb9, 0x00, 0x00, // } + 0x00, 0x15, 0x00, 0x1c, // NONCE attribute header + 0x66, 0x2f, 0x2f, 0x34, // } + 0x39, 0x39, 0x6b, 0x39, // } + 0x35, 0x34, 0x64, 0x36, // } + 0x4f, 0x4c, 0x33, 0x34, // } Nonce value + 0x6f, 0x4c, 0x39, 0x46, // } + 0x53, 0x54, 0x76, 0x79, // } + 0x36, 0x34, 0x73, 0x41, // } + 0x00, 0x14, 0x00, 0x0b, // REALM attribute header + 0x65, 0x78, 0x61, 0x6d, // } + 0x70, 0x6c, 0x65, 0x2e, // } Realm value (11 bytes) and padding (1 byte) + 0x6f, 0x72, 0x67, 0x00, // } + 0x00, 0x08, 0x00, 0x14, // MESSAGE-INTEGRITY attribute header + 0xf6, 0x70, 0x24, 0x65, // } + 0x6d, 0xd6, 0x4a, 0x3e, // } + 0x02, 0xb8, 0xe0, 0x71, // } HMAC-SHA1 fingerprint + 0x2e, 0x85, 0xc9, 0xa2, // } + 0x8c, 0xa8, 0x96, 0x66 // } +}; + +// Length parameter is changed to 0x38 from 0x58. +// AddMessageIntegrity will add MI information and update the length param +// accordingly. +static const unsigned char kRfc5769SampleRequestWithoutMI[] = { + 0x00, 0x01, 0x00, 0x38, // Request type and message length + 0x21, 0x12, 0xa4, 0x42, // Magic cookie + 0xb7, 0xe7, 0xa7, 0x01, // } + 0xbc, 0x34, 0xd6, 0x86, // } Transaction ID + 0xfa, 0x87, 0xdf, 0xae, // } + 0x80, 0x22, 0x00, 0x10, // SOFTWARE attribute header + 0x53, 0x54, 0x55, 0x4e, // } + 0x20, 0x74, 0x65, 0x73, // } User-agent... + 0x74, 0x20, 0x63, 0x6c, // } ...name + 0x69, 0x65, 0x6e, 0x74, // } + 0x00, 0x24, 0x00, 0x04, // PRIORITY attribute header + 0x6e, 0x00, 0x01, 0xff, // ICE priority value + 0x80, 0x29, 0x00, 0x08, // ICE-CONTROLLED attribute header + 0x93, 0x2f, 0xf9, 0xb1, // } Pseudo-random tie breaker... + 0x51, 0x26, 0x3b, 0x36, // } ...for ICE control + 0x00, 0x06, 0x00, 0x09, // USERNAME attribute header + 0x65, 0x76, 0x74, 0x6a, // } + 0x3a, 0x68, 0x36, 0x76, // } Username (9 bytes) and padding (3 bytes) + 0x59, 0x20, 0x20, 0x20 // } +}; + +// This HMAC differs from the RFC 5769 SampleRequest message. This differs +// because spec uses 0x20 for the padding where as our implementation uses 0. +static const unsigned char kCalculatedHmac1[] = { + 0x79, 0x07, 0xc2, 0xd2, // } + 0xed, 0xbf, 0xea, 0x48, // } + 0x0e, 0x4c, 0x76, 0xd8, // } HMAC-SHA1 fingerprint + 0x29, 0x62, 0xd5, 0xc3, // } + 0x74, 0x2a, 0xf9, 0xe3 // } +}; + +// Length parameter is changed to 0x1c from 0x3c. +// AddMessageIntegrity will add MI information and update the length param +// accordingly. +static const unsigned char kRfc5769SampleResponseWithoutMI[] = { + 0x01, 0x01, 0x00, 0x1c, // Response type and message length + 0x21, 0x12, 0xa4, 0x42, // Magic cookie + 0xb7, 0xe7, 0xa7, 0x01, // } + 0xbc, 0x34, 0xd6, 0x86, // } Transaction ID + 0xfa, 0x87, 0xdf, 0xae, // } + 0x80, 0x22, 0x00, 0x0b, // SOFTWARE attribute header + 0x74, 0x65, 0x73, 0x74, // } + 0x20, 0x76, 0x65, 0x63, // } UTF-8 server name + 0x74, 0x6f, 0x72, 0x20, // } + 0x00, 0x20, 0x00, 0x08, // XOR-MAPPED-ADDRESS attribute header + 0x00, 0x01, 0xa1, 0x47, // Address family (IPv4) and xor'd mapped port + 0xe1, 0x12, 0xa6, 0x43 // Xor'd mapped IPv4 address +}; + +// This HMAC differs from the RFC 5769 SampleResponse message. This differs +// because spec uses 0x20 for the padding where as our implementation uses 0. +static const unsigned char kCalculatedHmac2[] = { + 0x5d, 0x6b, 0x58, 0xbe, // } + 0xad, 0x94, 0xe0, 0x7e, // } + 0xef, 0x0d, 0xfc, 0x12, // } HMAC-SHA1 fingerprint + 0x82, 0xa2, 0xbd, 0x08, // } + 0x43, 0x14, 0x10, 0x28 // } +}; + +// A transaction ID without the 'magic cookie' portion +// pjnat's test programs use this transaction ID a lot. +const unsigned char kTestTransactionId1[] = { 0x029, 0x01f, 0x0cd, 0x07c, + 0x0ba, 0x058, 0x0ab, 0x0d7, + 0x0f2, 0x041, 0x001, 0x000 }; + +// They use this one sometimes too. +const unsigned char kTestTransactionId2[] = { 0x0e3, 0x0a9, 0x046, 0x0e1, + 0x07c, 0x000, 0x0c2, 0x062, + 0x054, 0x008, 0x001, 0x000 }; + +const in6_addr kIPv6TestAddress1 = { { { 0x24, 0x01, 0xfa, 0x00, + 0x00, 0x04, 0x10, 0x00, + 0xbe, 0x30, 0x5b, 0xff, + 0xfe, 0xe5, 0x00, 0xc3 } } }; +const in6_addr kIPv6TestAddress2 = { { { 0x24, 0x01, 0xfa, 0x00, + 0x00, 0x04, 0x10, 0x12, + 0x06, 0x0c, 0xce, 0xff, + 0xfe, 0x1f, 0x61, 0xa4 } } }; + +#ifdef WEBRTC_POSIX +const in_addr kIPv4TestAddress1 = { 0xe64417ac }; +#elif defined WEBRTC_WIN +// Windows in_addr has a union with a uchar[] array first. +const in_addr kIPv4TestAddress1 = { { 0x0ac, 0x017, 0x044, 0x0e6 } }; +#endif +const char kTestUserName1[] = "abcdefgh"; +const char kTestUserName2[] = "abc"; +const char kTestErrorReason[] = "Unauthorized"; +const int kTestErrorClass = 4; +const int kTestErrorNumber = 1; +const int kTestErrorCode = 401; + +const int kTestMessagePort1 = 59977; +const int kTestMessagePort2 = 47233; +const int kTestMessagePort3 = 56743; +const int kTestMessagePort4 = 40444; + +#define ReadStunMessage(X, Y) ReadStunMessageTestCase(X, Y, sizeof(Y)); + +// Test that the GetStun*Type and IsStun*Type methods work as expected. +TEST_F(StunTest, MessageTypes) { + EXPECT_EQ(STUN_BINDING_RESPONSE, + GetStunSuccessResponseType(STUN_BINDING_REQUEST)); + EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, + GetStunErrorResponseType(STUN_BINDING_REQUEST)); + EXPECT_EQ(-1, GetStunSuccessResponseType(STUN_BINDING_INDICATION)); + EXPECT_EQ(-1, GetStunSuccessResponseType(STUN_BINDING_RESPONSE)); + EXPECT_EQ(-1, GetStunSuccessResponseType(STUN_BINDING_ERROR_RESPONSE)); + EXPECT_EQ(-1, GetStunErrorResponseType(STUN_BINDING_INDICATION)); + EXPECT_EQ(-1, GetStunErrorResponseType(STUN_BINDING_RESPONSE)); + EXPECT_EQ(-1, GetStunErrorResponseType(STUN_BINDING_ERROR_RESPONSE)); + + int types[] = { + STUN_BINDING_REQUEST, STUN_BINDING_INDICATION, + STUN_BINDING_RESPONSE, STUN_BINDING_ERROR_RESPONSE + }; + for (int i = 0; i < ARRAY_SIZE(types); ++i) { + EXPECT_EQ(i == 0, IsStunRequestType(types[i])); + EXPECT_EQ(i == 1, IsStunIndicationType(types[i])); + EXPECT_EQ(i == 2, IsStunSuccessResponseType(types[i])); + EXPECT_EQ(i == 3, IsStunErrorResponseType(types[i])); + EXPECT_EQ(1, types[i] & 0xFEEF); + } +} + +TEST_F(StunTest, ReadMessageWithIPv4AddressAttribute) { + StunMessage msg; + size_t size = ReadStunMessage(&msg, kStunMessageWithIPv4MappedAddress); + CheckStunHeader(msg, STUN_BINDING_RESPONSE, size); + CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength); + + const StunAddressAttribute* addr = msg.GetAddress(STUN_ATTR_MAPPED_ADDRESS); + rtc::IPAddress test_address(kIPv4TestAddress1); + CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV4, + kTestMessagePort4, test_address); +} + +TEST_F(StunTest, ReadMessageWithIPv4XorAddressAttribute) { + StunMessage msg; + StunMessage msg2; + size_t size = ReadStunMessage(&msg, kStunMessageWithIPv4XorMappedAddress); + CheckStunHeader(msg, STUN_BINDING_RESPONSE, size); + CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength); + + const StunAddressAttribute* addr = + msg.GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS); + rtc::IPAddress test_address(kIPv4TestAddress1); + CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV4, + kTestMessagePort3, test_address); +} + +TEST_F(StunTest, ReadMessageWithIPv6AddressAttribute) { + StunMessage msg; + size_t size = ReadStunMessage(&msg, kStunMessageWithIPv6MappedAddress); + CheckStunHeader(msg, STUN_BINDING_REQUEST, size); + CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength); + + rtc::IPAddress test_address(kIPv6TestAddress1); + + const StunAddressAttribute* addr = msg.GetAddress(STUN_ATTR_MAPPED_ADDRESS); + CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV6, + kTestMessagePort2, test_address); +} + +TEST_F(StunTest, ReadMessageWithInvalidAddressAttribute) { + StunMessage msg; + size_t size = ReadStunMessage(&msg, kStunMessageWithIPv6MappedAddress); + CheckStunHeader(msg, STUN_BINDING_REQUEST, size); + CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength); + + rtc::IPAddress test_address(kIPv6TestAddress1); + + const StunAddressAttribute* addr = msg.GetAddress(STUN_ATTR_MAPPED_ADDRESS); + CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV6, + kTestMessagePort2, test_address); +} + +TEST_F(StunTest, ReadMessageWithIPv6XorAddressAttribute) { + StunMessage msg; + size_t size = ReadStunMessage(&msg, kStunMessageWithIPv6XorMappedAddress); + + rtc::IPAddress test_address(kIPv6TestAddress1); + + CheckStunHeader(msg, STUN_BINDING_RESPONSE, size); + CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength); + + const StunAddressAttribute* addr = + msg.GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS); + CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV6, + kTestMessagePort1, test_address); +} + +// Read the RFC5389 fields from the RFC5769 sample STUN request. +TEST_F(StunTest, ReadRfc5769RequestMessage) { + StunMessage msg; + size_t size = ReadStunMessage(&msg, kRfc5769SampleRequest); + CheckStunHeader(msg, STUN_BINDING_REQUEST, size); + CheckStunTransactionID(msg, kRfc5769SampleMsgTransactionId, + kStunTransactionIdLength); + + const StunByteStringAttribute* software = + msg.GetByteString(STUN_ATTR_SOFTWARE); + ASSERT_TRUE(software != NULL); + EXPECT_EQ(kRfc5769SampleMsgClientSoftware, software->GetString()); + + const StunByteStringAttribute* username = + msg.GetByteString(STUN_ATTR_USERNAME); + ASSERT_TRUE(username != NULL); + EXPECT_EQ(kRfc5769SampleMsgUsername, username->GetString()); + + // Actual M-I value checked in a later test. + ASSERT_TRUE(msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL); + + // Fingerprint checked in a later test, but double-check the value here. + const StunUInt32Attribute* fingerprint = + msg.GetUInt32(STUN_ATTR_FINGERPRINT); + ASSERT_TRUE(fingerprint != NULL); + EXPECT_EQ(0xe57a3bcf, fingerprint->value()); +} + +// Read the RFC5389 fields from the RFC5769 sample STUN response. +TEST_F(StunTest, ReadRfc5769ResponseMessage) { + StunMessage msg; + size_t size = ReadStunMessage(&msg, kRfc5769SampleResponse); + CheckStunHeader(msg, STUN_BINDING_RESPONSE, size); + CheckStunTransactionID(msg, kRfc5769SampleMsgTransactionId, + kStunTransactionIdLength); + + const StunByteStringAttribute* software = + msg.GetByteString(STUN_ATTR_SOFTWARE); + ASSERT_TRUE(software != NULL); + EXPECT_EQ(kRfc5769SampleMsgServerSoftware, software->GetString()); + + const StunAddressAttribute* mapped_address = + msg.GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS); + ASSERT_TRUE(mapped_address != NULL); + EXPECT_EQ(kRfc5769SampleMsgMappedAddress, mapped_address->GetAddress()); + + // Actual M-I and fingerprint checked in later tests. + ASSERT_TRUE(msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL); + ASSERT_TRUE(msg.GetUInt32(STUN_ATTR_FINGERPRINT) != NULL); +} + +// Read the RFC5389 fields from the RFC5769 sample STUN response for IPv6. +TEST_F(StunTest, ReadRfc5769ResponseMessageIPv6) { + StunMessage msg; + size_t size = ReadStunMessage(&msg, kRfc5769SampleResponseIPv6); + CheckStunHeader(msg, STUN_BINDING_RESPONSE, size); + CheckStunTransactionID(msg, kRfc5769SampleMsgTransactionId, + kStunTransactionIdLength); + + const StunByteStringAttribute* software = + msg.GetByteString(STUN_ATTR_SOFTWARE); + ASSERT_TRUE(software != NULL); + EXPECT_EQ(kRfc5769SampleMsgServerSoftware, software->GetString()); + + const StunAddressAttribute* mapped_address = + msg.GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS); + ASSERT_TRUE(mapped_address != NULL); + EXPECT_EQ(kRfc5769SampleMsgIPv6MappedAddress, mapped_address->GetAddress()); + + // Actual M-I and fingerprint checked in later tests. + ASSERT_TRUE(msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL); + ASSERT_TRUE(msg.GetUInt32(STUN_ATTR_FINGERPRINT) != NULL); +} + +// Read the RFC5389 fields from the RFC5769 sample STUN response with auth. +TEST_F(StunTest, ReadRfc5769RequestMessageLongTermAuth) { + StunMessage msg; + size_t size = ReadStunMessage(&msg, kRfc5769SampleRequestLongTermAuth); + CheckStunHeader(msg, STUN_BINDING_REQUEST, size); + CheckStunTransactionID(msg, kRfc5769SampleMsgWithAuthTransactionId, + kStunTransactionIdLength); + + const StunByteStringAttribute* username = + msg.GetByteString(STUN_ATTR_USERNAME); + ASSERT_TRUE(username != NULL); + EXPECT_EQ(kRfc5769SampleMsgWithAuthUsername, username->GetString()); + + const StunByteStringAttribute* nonce = + msg.GetByteString(STUN_ATTR_NONCE); + ASSERT_TRUE(nonce != NULL); + EXPECT_EQ(kRfc5769SampleMsgWithAuthNonce, nonce->GetString()); + + const StunByteStringAttribute* realm = + msg.GetByteString(STUN_ATTR_REALM); + ASSERT_TRUE(realm != NULL); + EXPECT_EQ(kRfc5769SampleMsgWithAuthRealm, realm->GetString()); + + // No fingerprint, actual M-I checked in later tests. + ASSERT_TRUE(msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL); + ASSERT_TRUE(msg.GetUInt32(STUN_ATTR_FINGERPRINT) == NULL); +} + +// The RFC3489 packet in this test is the same as +// kStunMessageWithIPv4MappedAddress, but with a different value where the +// magic cookie was. +TEST_F(StunTest, ReadLegacyMessage) { + unsigned char rfc3489_packet[sizeof(kStunMessageWithIPv4MappedAddress)]; + memcpy(rfc3489_packet, kStunMessageWithIPv4MappedAddress, + sizeof(kStunMessageWithIPv4MappedAddress)); + // Overwrite the magic cookie here. + memcpy(&rfc3489_packet[4], "ABCD", 4); + + StunMessage msg; + size_t size = ReadStunMessage(&msg, rfc3489_packet); + CheckStunHeader(msg, STUN_BINDING_RESPONSE, size); + CheckStunTransactionID(msg, &rfc3489_packet[4], kStunTransactionIdLength + 4); + + const StunAddressAttribute* addr = msg.GetAddress(STUN_ATTR_MAPPED_ADDRESS); + rtc::IPAddress test_address(kIPv4TestAddress1); + CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV4, + kTestMessagePort4, test_address); +} + +TEST_F(StunTest, SetIPv6XorAddressAttributeOwner) { + StunMessage msg; + StunMessage msg2; + size_t size = ReadStunMessage(&msg, kStunMessageWithIPv6XorMappedAddress); + + rtc::IPAddress test_address(kIPv6TestAddress1); + + CheckStunHeader(msg, STUN_BINDING_RESPONSE, size); + CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength); + + const StunAddressAttribute* addr = + msg.GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS); + CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV6, + kTestMessagePort1, test_address); + + // Owner with a different transaction ID. + msg2.SetTransactionID("ABCDABCDABCD"); + StunXorAddressAttribute addr2(STUN_ATTR_XOR_MAPPED_ADDRESS, 20, NULL); + addr2.SetIP(addr->ipaddr()); + addr2.SetPort(addr->port()); + addr2.SetOwner(&msg2); + // The internal IP address shouldn't change. + ASSERT_EQ(addr2.ipaddr(), addr->ipaddr()); + + rtc::ByteBuffer correct_buf; + rtc::ByteBuffer wrong_buf; + EXPECT_TRUE(addr->Write(&correct_buf)); + EXPECT_TRUE(addr2.Write(&wrong_buf)); + // But when written out, the buffers should look different. + ASSERT_NE(0, + memcmp(correct_buf.Data(), wrong_buf.Data(), wrong_buf.Length())); + // And when reading a known good value, the address should be wrong. + addr2.Read(&correct_buf); + ASSERT_NE(addr->ipaddr(), addr2.ipaddr()); + addr2.SetIP(addr->ipaddr()); + addr2.SetPort(addr->port()); + // Try writing with no owner at all, should fail and write nothing. + addr2.SetOwner(NULL); + ASSERT_EQ(addr2.ipaddr(), addr->ipaddr()); + wrong_buf.Consume(wrong_buf.Length()); + EXPECT_FALSE(addr2.Write(&wrong_buf)); + ASSERT_EQ(0U, wrong_buf.Length()); +} + +TEST_F(StunTest, SetIPv4XorAddressAttributeOwner) { + // Unlike the IPv6XorAddressAttributeOwner test, IPv4 XOR address attributes + // should _not_ be affected by a change in owner. IPv4 XOR address uses the + // magic cookie value which is fixed. + StunMessage msg; + StunMessage msg2; + size_t size = ReadStunMessage(&msg, kStunMessageWithIPv4XorMappedAddress); + + rtc::IPAddress test_address(kIPv4TestAddress1); + + CheckStunHeader(msg, STUN_BINDING_RESPONSE, size); + CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength); + + const StunAddressAttribute* addr = + msg.GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS); + CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV4, + kTestMessagePort3, test_address); + + // Owner with a different transaction ID. + msg2.SetTransactionID("ABCDABCDABCD"); + StunXorAddressAttribute addr2(STUN_ATTR_XOR_MAPPED_ADDRESS, 20, NULL); + addr2.SetIP(addr->ipaddr()); + addr2.SetPort(addr->port()); + addr2.SetOwner(&msg2); + // The internal IP address shouldn't change. + ASSERT_EQ(addr2.ipaddr(), addr->ipaddr()); + + rtc::ByteBuffer correct_buf; + rtc::ByteBuffer wrong_buf; + EXPECT_TRUE(addr->Write(&correct_buf)); + EXPECT_TRUE(addr2.Write(&wrong_buf)); + // The same address data should be written. + ASSERT_EQ(0, + memcmp(correct_buf.Data(), wrong_buf.Data(), wrong_buf.Length())); + // And an attribute should be able to un-XOR an address belonging to a message + // with a different transaction ID. + EXPECT_TRUE(addr2.Read(&correct_buf)); + ASSERT_EQ(addr->ipaddr(), addr2.ipaddr()); + + // However, no owner is still an error, should fail and write nothing. + addr2.SetOwner(NULL); + ASSERT_EQ(addr2.ipaddr(), addr->ipaddr()); + wrong_buf.Consume(wrong_buf.Length()); + EXPECT_FALSE(addr2.Write(&wrong_buf)); +} + +TEST_F(StunTest, CreateIPv6AddressAttribute) { + rtc::IPAddress test_ip(kIPv6TestAddress2); + + StunAddressAttribute* addr = + StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS); + rtc::SocketAddress test_addr(test_ip, kTestMessagePort2); + addr->SetAddress(test_addr); + + CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV6, + kTestMessagePort2, test_ip); + delete addr; +} + +TEST_F(StunTest, CreateIPv4AddressAttribute) { + struct in_addr test_in_addr; + test_in_addr.s_addr = 0xBEB0B0BE; + rtc::IPAddress test_ip(test_in_addr); + + StunAddressAttribute* addr = + StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS); + rtc::SocketAddress test_addr(test_ip, kTestMessagePort2); + addr->SetAddress(test_addr); + + CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV4, + kTestMessagePort2, test_ip); + delete addr; +} + +// Test that we don't care what order we set the parts of an address +TEST_F(StunTest, CreateAddressInArbitraryOrder) { + StunAddressAttribute* addr = + StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS); + // Port first + addr->SetPort(kTestMessagePort1); + addr->SetIP(rtc::IPAddress(kIPv4TestAddress1)); + ASSERT_EQ(kTestMessagePort1, addr->port()); + ASSERT_EQ(rtc::IPAddress(kIPv4TestAddress1), addr->ipaddr()); + + StunAddressAttribute* addr2 = + StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS); + // IP first + addr2->SetIP(rtc::IPAddress(kIPv4TestAddress1)); + addr2->SetPort(kTestMessagePort2); + ASSERT_EQ(kTestMessagePort2, addr2->port()); + ASSERT_EQ(rtc::IPAddress(kIPv4TestAddress1), addr2->ipaddr()); + + delete addr; + delete addr2; +} + +TEST_F(StunTest, WriteMessageWithIPv6AddressAttribute) { + StunMessage msg; + size_t size = sizeof(kStunMessageWithIPv6MappedAddress); + + rtc::IPAddress test_ip(kIPv6TestAddress1); + + msg.SetType(STUN_BINDING_REQUEST); + msg.SetTransactionID( + std::string(reinterpret_cast(kTestTransactionId1), + kStunTransactionIdLength)); + CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength); + + StunAddressAttribute* addr = + StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS); + rtc::SocketAddress test_addr(test_ip, kTestMessagePort2); + addr->SetAddress(test_addr); + EXPECT_TRUE(msg.AddAttribute(addr)); + + CheckStunHeader(msg, STUN_BINDING_REQUEST, (size - 20)); + + rtc::ByteBuffer out; + EXPECT_TRUE(msg.Write(&out)); + ASSERT_EQ(out.Length(), sizeof(kStunMessageWithIPv6MappedAddress)); + int len1 = static_cast(out.Length()); + std::string bytes; + out.ReadString(&bytes, len1); + ASSERT_EQ(0, memcmp(bytes.c_str(), kStunMessageWithIPv6MappedAddress, len1)); +} + +TEST_F(StunTest, WriteMessageWithIPv4AddressAttribute) { + StunMessage msg; + size_t size = sizeof(kStunMessageWithIPv4MappedAddress); + + rtc::IPAddress test_ip(kIPv4TestAddress1); + + msg.SetType(STUN_BINDING_RESPONSE); + msg.SetTransactionID( + std::string(reinterpret_cast(kTestTransactionId1), + kStunTransactionIdLength)); + CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength); + + StunAddressAttribute* addr = + StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS); + rtc::SocketAddress test_addr(test_ip, kTestMessagePort4); + addr->SetAddress(test_addr); + EXPECT_TRUE(msg.AddAttribute(addr)); + + CheckStunHeader(msg, STUN_BINDING_RESPONSE, (size - 20)); + + rtc::ByteBuffer out; + EXPECT_TRUE(msg.Write(&out)); + ASSERT_EQ(out.Length(), sizeof(kStunMessageWithIPv4MappedAddress)); + int len1 = static_cast(out.Length()); + std::string bytes; + out.ReadString(&bytes, len1); + ASSERT_EQ(0, memcmp(bytes.c_str(), kStunMessageWithIPv4MappedAddress, len1)); +} + +TEST_F(StunTest, WriteMessageWithIPv6XorAddressAttribute) { + StunMessage msg; + size_t size = sizeof(kStunMessageWithIPv6XorMappedAddress); + + rtc::IPAddress test_ip(kIPv6TestAddress1); + + msg.SetType(STUN_BINDING_RESPONSE); + msg.SetTransactionID( + std::string(reinterpret_cast(kTestTransactionId2), + kStunTransactionIdLength)); + CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength); + + StunAddressAttribute* addr = + StunAttribute::CreateXorAddress(STUN_ATTR_XOR_MAPPED_ADDRESS); + rtc::SocketAddress test_addr(test_ip, kTestMessagePort1); + addr->SetAddress(test_addr); + EXPECT_TRUE(msg.AddAttribute(addr)); + + CheckStunHeader(msg, STUN_BINDING_RESPONSE, (size - 20)); + + rtc::ByteBuffer out; + EXPECT_TRUE(msg.Write(&out)); + ASSERT_EQ(out.Length(), sizeof(kStunMessageWithIPv6XorMappedAddress)); + int len1 = static_cast(out.Length()); + std::string bytes; + out.ReadString(&bytes, len1); + ASSERT_EQ(0, + memcmp(bytes.c_str(), kStunMessageWithIPv6XorMappedAddress, len1)); +} + +TEST_F(StunTest, WriteMessageWithIPv4XoreAddressAttribute) { + StunMessage msg; + size_t size = sizeof(kStunMessageWithIPv4XorMappedAddress); + + rtc::IPAddress test_ip(kIPv4TestAddress1); + + msg.SetType(STUN_BINDING_RESPONSE); + msg.SetTransactionID( + std::string(reinterpret_cast(kTestTransactionId1), + kStunTransactionIdLength)); + CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength); + + StunAddressAttribute* addr = + StunAttribute::CreateXorAddress(STUN_ATTR_XOR_MAPPED_ADDRESS); + rtc::SocketAddress test_addr(test_ip, kTestMessagePort3); + addr->SetAddress(test_addr); + EXPECT_TRUE(msg.AddAttribute(addr)); + + CheckStunHeader(msg, STUN_BINDING_RESPONSE, (size - 20)); + + rtc::ByteBuffer out; + EXPECT_TRUE(msg.Write(&out)); + ASSERT_EQ(out.Length(), sizeof(kStunMessageWithIPv4XorMappedAddress)); + int len1 = static_cast(out.Length()); + std::string bytes; + out.ReadString(&bytes, len1); + ASSERT_EQ(0, + memcmp(bytes.c_str(), kStunMessageWithIPv4XorMappedAddress, len1)); +} + +TEST_F(StunTest, ReadByteStringAttribute) { + StunMessage msg; + size_t size = ReadStunMessage(&msg, kStunMessageWithByteStringAttribute); + + CheckStunHeader(msg, STUN_BINDING_REQUEST, size); + CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength); + const StunByteStringAttribute* username = + msg.GetByteString(STUN_ATTR_USERNAME); + ASSERT_TRUE(username != NULL); + EXPECT_EQ(kTestUserName1, username->GetString()); +} + +TEST_F(StunTest, ReadPaddedByteStringAttribute) { + StunMessage msg; + size_t size = ReadStunMessage(&msg, + kStunMessageWithPaddedByteStringAttribute); + ASSERT_NE(0U, size); + CheckStunHeader(msg, STUN_BINDING_REQUEST, size); + CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength); + const StunByteStringAttribute* username = + msg.GetByteString(STUN_ATTR_USERNAME); + ASSERT_TRUE(username != NULL); + EXPECT_EQ(kTestUserName2, username->GetString()); +} + +TEST_F(StunTest, ReadErrorCodeAttribute) { + StunMessage msg; + size_t size = ReadStunMessage(&msg, kStunMessageWithErrorAttribute); + + CheckStunHeader(msg, STUN_BINDING_ERROR_RESPONSE, size); + CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength); + const StunErrorCodeAttribute* errorcode = msg.GetErrorCode(); + ASSERT_TRUE(errorcode != NULL); + EXPECT_EQ(kTestErrorClass, errorcode->eclass()); + EXPECT_EQ(kTestErrorNumber, errorcode->number()); + EXPECT_EQ(kTestErrorReason, errorcode->reason()); + EXPECT_EQ(kTestErrorCode, errorcode->code()); +} + +TEST_F(StunTest, ReadMessageWithAUInt16ListAttribute) { + StunMessage msg; + size_t size = ReadStunMessage(&msg, kStunMessageWithUInt16ListAttribute); + CheckStunHeader(msg, STUN_BINDING_REQUEST, size); + const StunUInt16ListAttribute* types = msg.GetUnknownAttributes(); + ASSERT_TRUE(types != NULL); + EXPECT_EQ(3U, types->Size()); + EXPECT_EQ(0x1U, types->GetType(0)); + EXPECT_EQ(0x1000U, types->GetType(1)); + EXPECT_EQ(0xAB0CU, types->GetType(2)); +} + +TEST_F(StunTest, ReadMessageWithAnUnknownAttribute) { + StunMessage msg; + size_t size = ReadStunMessage(&msg, kStunMessageWithUnknownAttribute); + CheckStunHeader(msg, STUN_BINDING_REQUEST, size); + + // Parsing should have succeeded and there should be a USERNAME attribute + const StunByteStringAttribute* username = + msg.GetByteString(STUN_ATTR_USERNAME); + ASSERT_TRUE(username != NULL); + EXPECT_EQ(kTestUserName2, username->GetString()); +} + +TEST_F(StunTest, WriteMessageWithAnErrorCodeAttribute) { + StunMessage msg; + size_t size = sizeof(kStunMessageWithErrorAttribute); + + msg.SetType(STUN_BINDING_ERROR_RESPONSE); + msg.SetTransactionID( + std::string(reinterpret_cast(kTestTransactionId1), + kStunTransactionIdLength)); + CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength); + StunErrorCodeAttribute* errorcode = StunAttribute::CreateErrorCode(); + errorcode->SetCode(kTestErrorCode); + errorcode->SetReason(kTestErrorReason); + EXPECT_TRUE(msg.AddAttribute(errorcode)); + CheckStunHeader(msg, STUN_BINDING_ERROR_RESPONSE, (size - 20)); + + rtc::ByteBuffer out; + EXPECT_TRUE(msg.Write(&out)); + ASSERT_EQ(size, out.Length()); + // No padding. + ASSERT_EQ(0, memcmp(out.Data(), kStunMessageWithErrorAttribute, size)); +} + +TEST_F(StunTest, WriteMessageWithAUInt16ListAttribute) { + StunMessage msg; + size_t size = sizeof(kStunMessageWithUInt16ListAttribute); + + msg.SetType(STUN_BINDING_REQUEST); + msg.SetTransactionID( + std::string(reinterpret_cast(kTestTransactionId2), + kStunTransactionIdLength)); + CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength); + StunUInt16ListAttribute* list = StunAttribute::CreateUnknownAttributes(); + list->AddType(0x1U); + list->AddType(0x1000U); + list->AddType(0xAB0CU); + EXPECT_TRUE(msg.AddAttribute(list)); + CheckStunHeader(msg, STUN_BINDING_REQUEST, (size - 20)); + + rtc::ByteBuffer out; + EXPECT_TRUE(msg.Write(&out)); + ASSERT_EQ(size, out.Length()); + // Check everything up to the padding. + ASSERT_EQ(0, + memcmp(out.Data(), kStunMessageWithUInt16ListAttribute, size - 2)); +} + +// Test that we fail to read messages with invalid lengths. +void CheckFailureToRead(const unsigned char* testcase, size_t length) { + StunMessage msg; + const char* input = reinterpret_cast(testcase); + rtc::ByteBuffer buf(input, length); + ASSERT_FALSE(msg.Read(&buf)); +} + +TEST_F(StunTest, FailToReadInvalidMessages) { + CheckFailureToRead(kStunMessageWithZeroLength, + kRealLengthOfInvalidLengthTestCases); + CheckFailureToRead(kStunMessageWithSmallLength, + kRealLengthOfInvalidLengthTestCases); + CheckFailureToRead(kStunMessageWithExcessLength, + kRealLengthOfInvalidLengthTestCases); +} + +// Test that we properly fail to read a non-STUN message. +TEST_F(StunTest, FailToReadRtcpPacket) { + CheckFailureToRead(kRtcpPacket, sizeof(kRtcpPacket)); +} + +// Check our STUN message validation code against the RFC5769 test messages. +TEST_F(StunTest, ValidateMessageIntegrity) { + // Try the messages from RFC 5769. + EXPECT_TRUE(StunMessage::ValidateMessageIntegrity( + reinterpret_cast(kRfc5769SampleRequest), + sizeof(kRfc5769SampleRequest), + kRfc5769SampleMsgPassword)); + EXPECT_FALSE(StunMessage::ValidateMessageIntegrity( + reinterpret_cast(kRfc5769SampleRequest), + sizeof(kRfc5769SampleRequest), + "InvalidPassword")); + + EXPECT_TRUE(StunMessage::ValidateMessageIntegrity( + reinterpret_cast(kRfc5769SampleResponse), + sizeof(kRfc5769SampleResponse), + kRfc5769SampleMsgPassword)); + EXPECT_FALSE(StunMessage::ValidateMessageIntegrity( + reinterpret_cast(kRfc5769SampleResponse), + sizeof(kRfc5769SampleResponse), + "InvalidPassword")); + + EXPECT_TRUE(StunMessage::ValidateMessageIntegrity( + reinterpret_cast(kRfc5769SampleResponseIPv6), + sizeof(kRfc5769SampleResponseIPv6), + kRfc5769SampleMsgPassword)); + EXPECT_FALSE(StunMessage::ValidateMessageIntegrity( + reinterpret_cast(kRfc5769SampleResponseIPv6), + sizeof(kRfc5769SampleResponseIPv6), + "InvalidPassword")); + + // We first need to compute the key for the long-term authentication HMAC. + std::string key; + ComputeStunCredentialHash(kRfc5769SampleMsgWithAuthUsername, + kRfc5769SampleMsgWithAuthRealm, kRfc5769SampleMsgWithAuthPassword, &key); + EXPECT_TRUE(StunMessage::ValidateMessageIntegrity( + reinterpret_cast(kRfc5769SampleRequestLongTermAuth), + sizeof(kRfc5769SampleRequestLongTermAuth), key)); + EXPECT_FALSE(StunMessage::ValidateMessageIntegrity( + reinterpret_cast(kRfc5769SampleRequestLongTermAuth), + sizeof(kRfc5769SampleRequestLongTermAuth), + "InvalidPassword")); + + // Try some edge cases. + EXPECT_FALSE(StunMessage::ValidateMessageIntegrity( + reinterpret_cast(kStunMessageWithZeroLength), + sizeof(kStunMessageWithZeroLength), + kRfc5769SampleMsgPassword)); + EXPECT_FALSE(StunMessage::ValidateMessageIntegrity( + reinterpret_cast(kStunMessageWithExcessLength), + sizeof(kStunMessageWithExcessLength), + kRfc5769SampleMsgPassword)); + EXPECT_FALSE(StunMessage::ValidateMessageIntegrity( + reinterpret_cast(kStunMessageWithSmallLength), + sizeof(kStunMessageWithSmallLength), + kRfc5769SampleMsgPassword)); + + // Test that munging a single bit anywhere in the message causes the + // message-integrity check to fail, unless it is after the M-I attribute. + char buf[sizeof(kRfc5769SampleRequest)]; + memcpy(buf, kRfc5769SampleRequest, sizeof(kRfc5769SampleRequest)); + for (size_t i = 0; i < sizeof(buf); ++i) { + buf[i] ^= 0x01; + if (i > 0) + buf[i - 1] ^= 0x01; + EXPECT_EQ(i >= sizeof(buf) - 8, StunMessage::ValidateMessageIntegrity( + buf, sizeof(buf), kRfc5769SampleMsgPassword)); + } +} + +// Validate that we generate correct MESSAGE-INTEGRITY attributes. +// Note the use of IceMessage instead of StunMessage; this is necessary because +// the RFC5769 test messages used include attributes not found in basic STUN. +TEST_F(StunTest, AddMessageIntegrity) { + IceMessage msg; + rtc::ByteBuffer buf( + reinterpret_cast(kRfc5769SampleRequestWithoutMI), + sizeof(kRfc5769SampleRequestWithoutMI)); + EXPECT_TRUE(msg.Read(&buf)); + EXPECT_TRUE(msg.AddMessageIntegrity(kRfc5769SampleMsgPassword)); + const StunByteStringAttribute* mi_attr = + msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY); + EXPECT_EQ(20U, mi_attr->length()); + EXPECT_EQ(0, memcmp( + mi_attr->bytes(), kCalculatedHmac1, sizeof(kCalculatedHmac1))); + + rtc::ByteBuffer buf1; + EXPECT_TRUE(msg.Write(&buf1)); + EXPECT_TRUE(StunMessage::ValidateMessageIntegrity( + reinterpret_cast(buf1.Data()), buf1.Length(), + kRfc5769SampleMsgPassword)); + + IceMessage msg2; + rtc::ByteBuffer buf2( + reinterpret_cast(kRfc5769SampleResponseWithoutMI), + sizeof(kRfc5769SampleResponseWithoutMI)); + EXPECT_TRUE(msg2.Read(&buf2)); + EXPECT_TRUE(msg2.AddMessageIntegrity(kRfc5769SampleMsgPassword)); + const StunByteStringAttribute* mi_attr2 = + msg2.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY); + EXPECT_EQ(20U, mi_attr2->length()); + EXPECT_EQ( + 0, memcmp(mi_attr2->bytes(), kCalculatedHmac2, sizeof(kCalculatedHmac2))); + + rtc::ByteBuffer buf3; + EXPECT_TRUE(msg2.Write(&buf3)); + EXPECT_TRUE(StunMessage::ValidateMessageIntegrity( + reinterpret_cast(buf3.Data()), buf3.Length(), + kRfc5769SampleMsgPassword)); +} + +// Check our STUN message validation code against the RFC5769 test messages. +TEST_F(StunTest, ValidateFingerprint) { + EXPECT_TRUE(StunMessage::ValidateFingerprint( + reinterpret_cast(kRfc5769SampleRequest), + sizeof(kRfc5769SampleRequest))); + EXPECT_TRUE(StunMessage::ValidateFingerprint( + reinterpret_cast(kRfc5769SampleResponse), + sizeof(kRfc5769SampleResponse))); + EXPECT_TRUE(StunMessage::ValidateFingerprint( + reinterpret_cast(kRfc5769SampleResponseIPv6), + sizeof(kRfc5769SampleResponseIPv6))); + + EXPECT_FALSE(StunMessage::ValidateFingerprint( + reinterpret_cast(kStunMessageWithZeroLength), + sizeof(kStunMessageWithZeroLength))); + EXPECT_FALSE(StunMessage::ValidateFingerprint( + reinterpret_cast(kStunMessageWithExcessLength), + sizeof(kStunMessageWithExcessLength))); + EXPECT_FALSE(StunMessage::ValidateFingerprint( + reinterpret_cast(kStunMessageWithSmallLength), + sizeof(kStunMessageWithSmallLength))); + + // Test that munging a single bit anywhere in the message causes the + // fingerprint check to fail. + char buf[sizeof(kRfc5769SampleRequest)]; + memcpy(buf, kRfc5769SampleRequest, sizeof(kRfc5769SampleRequest)); + for (size_t i = 0; i < sizeof(buf); ++i) { + buf[i] ^= 0x01; + if (i > 0) + buf[i - 1] ^= 0x01; + EXPECT_FALSE(StunMessage::ValidateFingerprint(buf, sizeof(buf))); + } + // Put them all back to normal and the check should pass again. + buf[sizeof(buf) - 1] ^= 0x01; + EXPECT_TRUE(StunMessage::ValidateFingerprint(buf, sizeof(buf))); +} + +TEST_F(StunTest, AddFingerprint) { + IceMessage msg; + rtc::ByteBuffer buf( + reinterpret_cast(kRfc5769SampleRequestWithoutMI), + sizeof(kRfc5769SampleRequestWithoutMI)); + EXPECT_TRUE(msg.Read(&buf)); + EXPECT_TRUE(msg.AddFingerprint()); + + rtc::ByteBuffer buf1; + EXPECT_TRUE(msg.Write(&buf1)); + EXPECT_TRUE(StunMessage::ValidateFingerprint( + reinterpret_cast(buf1.Data()), buf1.Length())); +} + +// Sample "GTURN" relay message. +static const unsigned char kRelayMessage[] = { + 0x00, 0x01, 0x00, 88, // message header + 0x21, 0x12, 0xA4, 0x42, // magic cookie + '0', '1', '2', '3', // transaction id + '4', '5', '6', '7', + '8', '9', 'a', 'b', + 0x00, 0x01, 0x00, 8, // mapped address + 0x00, 0x01, 0x00, 13, + 0x00, 0x00, 0x00, 17, + 0x00, 0x06, 0x00, 12, // username + 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', + 0x00, 0x0d, 0x00, 4, // lifetime + 0x00, 0x00, 0x00, 11, + 0x00, 0x0f, 0x00, 4, // magic cookie + 0x72, 0xc6, 0x4b, 0xc6, + 0x00, 0x10, 0x00, 4, // bandwidth + 0x00, 0x00, 0x00, 6, + 0x00, 0x11, 0x00, 8, // destination address + 0x00, 0x01, 0x00, 13, + 0x00, 0x00, 0x00, 17, + 0x00, 0x12, 0x00, 8, // source address 2 + 0x00, 0x01, 0x00, 13, + 0x00, 0x00, 0x00, 17, + 0x00, 0x13, 0x00, 7, // data + 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 0 // DATA must be padded per rfc5766. +}; + +// Test that we can read the GTURN-specific fields. +TEST_F(StunTest, ReadRelayMessage) { + RelayMessage msg, msg2; + + const char* input = reinterpret_cast(kRelayMessage); + size_t size = sizeof(kRelayMessage); + rtc::ByteBuffer buf(input, size); + EXPECT_TRUE(msg.Read(&buf)); + + EXPECT_EQ(STUN_BINDING_REQUEST, msg.type()); + EXPECT_EQ(size - 20, msg.length()); + EXPECT_EQ("0123456789ab", msg.transaction_id()); + + msg2.SetType(STUN_BINDING_REQUEST); + msg2.SetTransactionID("0123456789ab"); + + in_addr legacy_in_addr; + legacy_in_addr.s_addr = htonl(17U); + rtc::IPAddress legacy_ip(legacy_in_addr); + + const StunAddressAttribute* addr = msg.GetAddress(STUN_ATTR_MAPPED_ADDRESS); + ASSERT_TRUE(addr != NULL); + EXPECT_EQ(1, addr->family()); + EXPECT_EQ(13, addr->port()); + EXPECT_EQ(legacy_ip, addr->ipaddr()); + + StunAddressAttribute* addr2 = + StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS); + addr2->SetPort(13); + addr2->SetIP(legacy_ip); + EXPECT_TRUE(msg2.AddAttribute(addr2)); + + const StunByteStringAttribute* bytes = msg.GetByteString(STUN_ATTR_USERNAME); + ASSERT_TRUE(bytes != NULL); + EXPECT_EQ(12U, bytes->length()); + EXPECT_EQ("abcdefghijkl", bytes->GetString()); + + StunByteStringAttribute* bytes2 = + StunAttribute::CreateByteString(STUN_ATTR_USERNAME); + bytes2->CopyBytes("abcdefghijkl"); + EXPECT_TRUE(msg2.AddAttribute(bytes2)); + + const StunUInt32Attribute* uval = msg.GetUInt32(STUN_ATTR_LIFETIME); + ASSERT_TRUE(uval != NULL); + EXPECT_EQ(11U, uval->value()); + + StunUInt32Attribute* uval2 = StunAttribute::CreateUInt32(STUN_ATTR_LIFETIME); + uval2->SetValue(11); + EXPECT_TRUE(msg2.AddAttribute(uval2)); + + bytes = msg.GetByteString(STUN_ATTR_MAGIC_COOKIE); + ASSERT_TRUE(bytes != NULL); + EXPECT_EQ(4U, bytes->length()); + EXPECT_EQ(0, + memcmp(bytes->bytes(), + TURN_MAGIC_COOKIE_VALUE, + sizeof(TURN_MAGIC_COOKIE_VALUE))); + + bytes2 = StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE); + bytes2->CopyBytes(reinterpret_cast(TURN_MAGIC_COOKIE_VALUE), + sizeof(TURN_MAGIC_COOKIE_VALUE)); + EXPECT_TRUE(msg2.AddAttribute(bytes2)); + + uval = msg.GetUInt32(STUN_ATTR_BANDWIDTH); + ASSERT_TRUE(uval != NULL); + EXPECT_EQ(6U, uval->value()); + + uval2 = StunAttribute::CreateUInt32(STUN_ATTR_BANDWIDTH); + uval2->SetValue(6); + EXPECT_TRUE(msg2.AddAttribute(uval2)); + + addr = msg.GetAddress(STUN_ATTR_DESTINATION_ADDRESS); + ASSERT_TRUE(addr != NULL); + EXPECT_EQ(1, addr->family()); + EXPECT_EQ(13, addr->port()); + EXPECT_EQ(legacy_ip, addr->ipaddr()); + + addr2 = StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS); + addr2->SetPort(13); + addr2->SetIP(legacy_ip); + EXPECT_TRUE(msg2.AddAttribute(addr2)); + + addr = msg.GetAddress(STUN_ATTR_SOURCE_ADDRESS2); + ASSERT_TRUE(addr != NULL); + EXPECT_EQ(1, addr->family()); + EXPECT_EQ(13, addr->port()); + EXPECT_EQ(legacy_ip, addr->ipaddr()); + + addr2 = StunAttribute::CreateAddress(STUN_ATTR_SOURCE_ADDRESS2); + addr2->SetPort(13); + addr2->SetIP(legacy_ip); + EXPECT_TRUE(msg2.AddAttribute(addr2)); + + bytes = msg.GetByteString(STUN_ATTR_DATA); + ASSERT_TRUE(bytes != NULL); + EXPECT_EQ(7U, bytes->length()); + EXPECT_EQ("abcdefg", bytes->GetString()); + + bytes2 = StunAttribute::CreateByteString(STUN_ATTR_DATA); + bytes2->CopyBytes("abcdefg"); + EXPECT_TRUE(msg2.AddAttribute(bytes2)); + + rtc::ByteBuffer out; + EXPECT_TRUE(msg.Write(&out)); + EXPECT_EQ(size, out.Length()); + size_t len1 = out.Length(); + std::string outstring; + out.ReadString(&outstring, len1); + EXPECT_EQ(0, memcmp(outstring.c_str(), input, len1)); + + rtc::ByteBuffer out2; + EXPECT_TRUE(msg2.Write(&out2)); + EXPECT_EQ(size, out2.Length()); + size_t len2 = out2.Length(); + std::string outstring2; + out2.ReadString(&outstring2, len2); + EXPECT_EQ(0, memcmp(outstring2.c_str(), input, len2)); +} + +} // namespace cricket diff --git a/webrtc/p2p/base/stunport.cc b/webrtc/p2p/base/stunport.cc new file mode 100644 index 000000000..ec6232a6b --- /dev/null +++ b/webrtc/p2p/base/stunport.cc @@ -0,0 +1,451 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/stunport.h" + +#include "webrtc/p2p/base/common.h" +#include "webrtc/p2p/base/portallocator.h" +#include "webrtc/p2p/base/stun.h" +#include "webrtc/base/common.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/nethelpers.h" + +namespace cricket { + +// TODO: Move these to a common place (used in relayport too) +const int KEEPALIVE_DELAY = 10 * 1000; // 10 seconds - sort timeouts +const int RETRY_DELAY = 50; // 50ms, from ICE spec +const int RETRY_TIMEOUT = 50 * 1000; // ICE says 50 secs + +// Handles a binding request sent to the STUN server. +class StunBindingRequest : public StunRequest { + public: + StunBindingRequest(UDPPort* port, bool keep_alive, + const rtc::SocketAddress& addr) + : port_(port), keep_alive_(keep_alive), server_addr_(addr) { + start_time_ = rtc::Time(); + } + + virtual ~StunBindingRequest() { + } + + const rtc::SocketAddress& server_addr() const { return server_addr_; } + + 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(LS_ERROR) << "Binding response missing mapped address."; + } else if (addr_attr->family() != STUN_ADDRESS_IPV4 && + addr_attr->family() != STUN_ADDRESS_IPV6) { + LOG(LS_ERROR) << "Binding address has bad family"; + } else { + rtc::SocketAddress addr(addr_attr->ipaddr(), addr_attr->port()); + port_->OnStunBindingRequestSucceeded(server_addr_, addr); + } + + // We will do a keep-alive regardless of whether this request succeeds. + // This should have almost no impact on network usage. + if (keep_alive_) { + port_->requests_.SendDelayed( + new StunBindingRequest(port_, true, server_addr_), + port_->stun_keepalive_delay()); + } + } + + virtual void OnErrorResponse(StunMessage* response) { + const StunErrorCodeAttribute* attr = response->GetErrorCode(); + if (!attr) { + LOG(LS_ERROR) << "Bad allocate response error code"; + } else { + LOG(LS_ERROR) << "Binding error response:" + << " class=" << attr->eclass() + << " number=" << attr->number() + << " reason='" << attr->reason() << "'"; + } + + port_->OnStunBindingOrResolveRequestFailed(server_addr_); + + if (keep_alive_ + && (rtc::TimeSince(start_time_) <= RETRY_TIMEOUT)) { + port_->requests_.SendDelayed( + new StunBindingRequest(port_, true, server_addr_), + port_->stun_keepalive_delay()); + } + } + + virtual void OnTimeout() { + LOG(LS_ERROR) << "Binding request timed out from " + << port_->GetLocalAddress().ToSensitiveString() + << " (" << port_->Network()->name() << ")"; + + port_->OnStunBindingOrResolveRequestFailed(server_addr_); + + if (keep_alive_ + && (rtc::TimeSince(start_time_) <= RETRY_TIMEOUT)) { + port_->requests_.SendDelayed( + new StunBindingRequest(port_, true, server_addr_), + RETRY_DELAY); + } + } + + private: + UDPPort* port_; + bool keep_alive_; + const rtc::SocketAddress server_addr_; + uint32 start_time_; +}; + +UDPPort::AddressResolver::AddressResolver( + rtc::PacketSocketFactory* factory) + : socket_factory_(factory) {} + +UDPPort::AddressResolver::~AddressResolver() { + for (ResolverMap::iterator it = resolvers_.begin(); + it != resolvers_.end(); ++it) { + it->second->Destroy(true); + } +} + +void UDPPort::AddressResolver::Resolve( + const rtc::SocketAddress& address) { + if (resolvers_.find(address) != resolvers_.end()) + return; + + rtc::AsyncResolverInterface* resolver = + socket_factory_->CreateAsyncResolver(); + resolvers_.insert( + std::pair( + address, resolver)); + + resolver->SignalDone.connect(this, + &UDPPort::AddressResolver::OnResolveResult); + + resolver->Start(address); +} + +bool UDPPort::AddressResolver::GetResolvedAddress( + const rtc::SocketAddress& input, + int family, + rtc::SocketAddress* output) const { + ResolverMap::const_iterator it = resolvers_.find(input); + if (it == resolvers_.end()) + return false; + + return it->second->GetResolvedAddress(family, output); +} + +void UDPPort::AddressResolver::OnResolveResult( + rtc::AsyncResolverInterface* resolver) { + for (ResolverMap::iterator it = resolvers_.begin(); + it != resolvers_.end(); ++it) { + if (it->second == resolver) { + SignalDone(it->first, resolver->GetError()); + return; + } + } +} + +UDPPort::UDPPort(rtc::Thread* thread, + rtc::PacketSocketFactory* factory, + rtc::Network* network, + rtc::AsyncPacketSocket* socket, + const std::string& username, const std::string& password) + : Port(thread, factory, network, socket->GetLocalAddress().ipaddr(), + username, password), + requests_(thread), + socket_(socket), + error_(0), + ready_(false), + stun_keepalive_delay_(KEEPALIVE_DELAY) { +} + +UDPPort::UDPPort(rtc::Thread* thread, + rtc::PacketSocketFactory* factory, + rtc::Network* network, + const rtc::IPAddress& ip, int min_port, int max_port, + const std::string& username, const std::string& password) + : Port(thread, LOCAL_PORT_TYPE, factory, network, ip, min_port, max_port, + username, password), + requests_(thread), + socket_(NULL), + error_(0), + ready_(false), + stun_keepalive_delay_(KEEPALIVE_DELAY) { +} + +bool UDPPort::Init() { + if (!SharedSocket()) { + ASSERT(socket_ == NULL); + socket_ = socket_factory()->CreateUdpSocket( + rtc::SocketAddress(ip(), 0), min_port(), max_port()); + if (!socket_) { + LOG_J(LS_WARNING, this) << "UDP socket creation failed"; + return false; + } + socket_->SignalReadPacket.connect(this, &UDPPort::OnReadPacket); + } + socket_->SignalReadyToSend.connect(this, &UDPPort::OnReadyToSend); + socket_->SignalAddressReady.connect(this, &UDPPort::OnLocalAddressReady); + requests_.SignalSendPacket.connect(this, &UDPPort::OnSendPacket); + return true; +} + +UDPPort::~UDPPort() { + if (!SharedSocket()) + delete socket_; +} + +void UDPPort::PrepareAddress() { + ASSERT(requests_.empty()); + if (socket_->GetState() == rtc::AsyncPacketSocket::STATE_BOUND) { + OnLocalAddressReady(socket_, socket_->GetLocalAddress()); + } +} + +void UDPPort::MaybePrepareStunCandidate() { + // Sending binding request to the STUN server if address is available to + // prepare STUN candidate. + if (!server_addresses_.empty()) { + SendStunBindingRequests(); + } else { + // Port is done allocating candidates. + MaybeSetPortCompleteOrError(); + } +} + +Connection* UDPPort::CreateConnection(const Candidate& address, + CandidateOrigin origin) { + if (address.protocol() != "udp") + return NULL; + + if (!IsCompatibleAddress(address.address())) { + return NULL; + } + + if (SharedSocket() && Candidates()[0].type() != LOCAL_PORT_TYPE) { + ASSERT(false); + return NULL; + } + + Connection* conn = new ProxyConnection(this, 0, address); + AddConnection(conn); + return conn; +} + +int UDPPort::SendTo(const void* data, size_t size, + const rtc::SocketAddress& addr, + const rtc::PacketOptions& options, + bool payload) { + int sent = socket_->SendTo(data, size, addr, options); + if (sent < 0) { + error_ = socket_->GetError(); + LOG_J(LS_ERROR, this) << "UDP send of " << size + << " bytes failed with error " << error_; + } + return sent; +} + +int UDPPort::SetOption(rtc::Socket::Option opt, int value) { + return socket_->SetOption(opt, value); +} + +int UDPPort::GetOption(rtc::Socket::Option opt, int* value) { + return socket_->GetOption(opt, value); +} + +int UDPPort::GetError() { + return error_; +} + +void UDPPort::OnLocalAddressReady(rtc::AsyncPacketSocket* socket, + const rtc::SocketAddress& address) { + AddAddress(address, address, rtc::SocketAddress(), + UDP_PROTOCOL_NAME, "", LOCAL_PORT_TYPE, + ICE_TYPE_PREFERENCE_HOST, 0, false); + MaybePrepareStunCandidate(); +} + +void UDPPort::OnReadPacket( + rtc::AsyncPacketSocket* socket, const char* data, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time) { + ASSERT(socket == socket_); + ASSERT(!remote_addr.IsUnresolved()); + + // Look for a response from the STUN server. + // Even if the response doesn't match one of our outstanding requests, we + // will eat it because it might be a response to a retransmitted packet, and + // we already cleared the request when we got the first response. + if (server_addresses_.find(remote_addr) != server_addresses_.end()) { + requests_.CheckResponse(data, size); + return; + } + + if (Connection* conn = GetConnection(remote_addr)) { + conn->OnReadPacket(data, size, packet_time); + } else { + Port::OnReadPacket(data, size, remote_addr, PROTO_UDP); + } +} + +void UDPPort::OnReadyToSend(rtc::AsyncPacketSocket* socket) { + Port::OnReadyToSend(); +} + +void UDPPort::SendStunBindingRequests() { + // We will keep pinging the stun server to make sure our NAT pin-hole stays + // open during the call. + ASSERT(requests_.empty()); + + for (ServerAddresses::const_iterator it = server_addresses_.begin(); + it != server_addresses_.end(); ++it) { + SendStunBindingRequest(*it); + } +} + +void UDPPort::ResolveStunAddress(const rtc::SocketAddress& stun_addr) { + if (!resolver_) { + resolver_.reset(new AddressResolver(socket_factory())); + resolver_->SignalDone.connect(this, &UDPPort::OnResolveResult); + } + + resolver_->Resolve(stun_addr); +} + +void UDPPort::OnResolveResult(const rtc::SocketAddress& input, + int error) { + ASSERT(resolver_.get() != NULL); + + rtc::SocketAddress resolved; + if (error != 0 || + !resolver_->GetResolvedAddress(input, ip().family(), &resolved)) { + LOG_J(LS_WARNING, this) << "StunPort: stun host lookup received error " + << error; + OnStunBindingOrResolveRequestFailed(input); + return; + } + + server_addresses_.erase(input); + + if (server_addresses_.find(resolved) == server_addresses_.end()) { + server_addresses_.insert(resolved); + SendStunBindingRequest(resolved); + } +} + +void UDPPort::SendStunBindingRequest( + const rtc::SocketAddress& stun_addr) { + if (stun_addr.IsUnresolved()) { + ResolveStunAddress(stun_addr); + + } else if (socket_->GetState() == rtc::AsyncPacketSocket::STATE_BOUND) { + // Check if |server_addr_| is compatible with the port's ip. + if (IsCompatibleAddress(stun_addr)) { + requests_.Send(new StunBindingRequest(this, true, stun_addr)); + } else { + // Since we can't send stun messages to the server, we should mark this + // port ready. + LOG(LS_WARNING) << "STUN server address is incompatible."; + OnStunBindingOrResolveRequestFailed(stun_addr); + } + } +} + +void UDPPort::OnStunBindingRequestSucceeded( + const rtc::SocketAddress& stun_server_addr, + const rtc::SocketAddress& stun_reflected_addr) { + if (bind_request_succeeded_servers_.find(stun_server_addr) != + bind_request_succeeded_servers_.end()) { + return; + } + bind_request_succeeded_servers_.insert(stun_server_addr); + + // If socket is shared and |stun_reflected_addr| is equal to local socket + // address, or if the same address has been added by another STUN server, + // then discarding the stun address. + // For STUN, related address is the local socket address. + if ((!SharedSocket() || stun_reflected_addr != socket_->GetLocalAddress()) && + !HasCandidateWithAddress(stun_reflected_addr)) { + + rtc::SocketAddress related_address = socket_->GetLocalAddress(); + if (!(candidate_filter() & CF_HOST)) { + // If candidate filter doesn't have CF_HOST specified, empty raddr to + // avoid local address leakage. + related_address = rtc::EmptySocketAddressWithFamily( + related_address.family()); + } + + AddAddress(stun_reflected_addr, socket_->GetLocalAddress(), + related_address, UDP_PROTOCOL_NAME, "", + STUN_PORT_TYPE, ICE_TYPE_PREFERENCE_SRFLX, 0, false); + } + MaybeSetPortCompleteOrError(); +} + +void UDPPort::OnStunBindingOrResolveRequestFailed( + const rtc::SocketAddress& stun_server_addr) { + if (bind_request_failed_servers_.find(stun_server_addr) != + bind_request_failed_servers_.end()) { + return; + } + bind_request_failed_servers_.insert(stun_server_addr); + MaybeSetPortCompleteOrError(); +} + +void UDPPort::MaybeSetPortCompleteOrError() { + if (ready_) + return; + + // Do not set port ready if we are still waiting for bind responses. + const size_t servers_done_bind_request = bind_request_failed_servers_.size() + + bind_request_succeeded_servers_.size(); + if (server_addresses_.size() != servers_done_bind_request) { + return; + } + + // Setting ready status. + ready_ = true; + + // The port is "completed" if there is no stun server provided, or the bind + // request succeeded for any stun server, or the socket is shared. + if (server_addresses_.empty() || + bind_request_succeeded_servers_.size() > 0 || + SharedSocket()) { + SignalPortComplete(this); + } else { + SignalPortError(this); + } +} + +// TODO: merge this with SendTo above. +void UDPPort::OnSendPacket(const void* data, size_t size, StunRequest* req) { + StunBindingRequest* sreq = static_cast(req); + rtc::PacketOptions options(DefaultDscpValue()); + if (socket_->SendTo(data, size, sreq->server_addr(), options) < 0) + PLOG(LERROR, socket_->GetError()) << "sendto"; +} + +bool UDPPort::HasCandidateWithAddress(const rtc::SocketAddress& addr) const { + const std::vector& existing_candidates = Candidates(); + std::vector::const_iterator it = existing_candidates.begin(); + for (; it != existing_candidates.end(); ++it) { + if (it->address() == addr) + return true; + } + return false; +} + +} // namespace cricket diff --git a/webrtc/p2p/base/stunport.h b/webrtc/p2p/base/stunport.h new file mode 100644 index 000000000..eda7bb900 --- /dev/null +++ b/webrtc/p2p/base/stunport.h @@ -0,0 +1,238 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_STUNPORT_H_ +#define WEBRTC_P2P_BASE_STUNPORT_H_ + +#include + +#include "webrtc/p2p/base/port.h" +#include "webrtc/p2p/base/stunrequest.h" +#include "webrtc/base/asyncpacketsocket.h" + +// TODO(mallinath) - Rename stunport.cc|h to udpport.cc|h. +namespace rtc { +class AsyncResolver; +class SignalThread; +} + +namespace cricket { + +// Communicates using the address on the outside of a NAT. +class UDPPort : public Port { + public: + static UDPPort* Create(rtc::Thread* thread, + rtc::PacketSocketFactory* factory, + rtc::Network* network, + rtc::AsyncPacketSocket* socket, + const std::string& username, + const std::string& password) { + UDPPort* port = new UDPPort(thread, factory, network, socket, + username, password); + if (!port->Init()) { + delete port; + port = NULL; + } + return port; + } + + static UDPPort* Create(rtc::Thread* thread, + rtc::PacketSocketFactory* factory, + rtc::Network* network, + const rtc::IPAddress& ip, + int min_port, int max_port, + const std::string& username, + const std::string& password) { + UDPPort* port = new UDPPort(thread, factory, network, + ip, min_port, max_port, + username, password); + if (!port->Init()) { + delete port; + port = NULL; + } + return port; + } + virtual ~UDPPort(); + + rtc::SocketAddress GetLocalAddress() const { + return socket_->GetLocalAddress(); + } + + const ServerAddresses& server_addresses() const { + return server_addresses_; + } + void + set_server_addresses(const ServerAddresses& addresses) { + server_addresses_ = addresses; + } + + virtual void PrepareAddress(); + + virtual Connection* CreateConnection(const Candidate& address, + CandidateOrigin origin); + virtual int SetOption(rtc::Socket::Option opt, int value); + virtual int GetOption(rtc::Socket::Option opt, int* value); + virtual int GetError(); + + virtual bool HandleIncomingPacket( + rtc::AsyncPacketSocket* socket, const char* data, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time) { + // All packets given to UDP port will be consumed. + OnReadPacket(socket, data, size, remote_addr, packet_time); + return true; + } + + void set_stun_keepalive_delay(int delay) { + stun_keepalive_delay_ = delay; + } + int stun_keepalive_delay() const { + return stun_keepalive_delay_; + } + + protected: + UDPPort(rtc::Thread* thread, rtc::PacketSocketFactory* factory, + rtc::Network* network, const rtc::IPAddress& ip, + int min_port, int max_port, + const std::string& username, const std::string& password); + + UDPPort(rtc::Thread* thread, rtc::PacketSocketFactory* factory, + rtc::Network* network, rtc::AsyncPacketSocket* socket, + const std::string& username, const std::string& password); + + bool Init(); + + virtual int SendTo(const void* data, size_t size, + const rtc::SocketAddress& addr, + const rtc::PacketOptions& options, + bool payload); + + void OnLocalAddressReady(rtc::AsyncPacketSocket* socket, + const rtc::SocketAddress& address); + void OnReadPacket(rtc::AsyncPacketSocket* socket, + const char* data, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time); + + void OnReadyToSend(rtc::AsyncPacketSocket* socket); + + // This method will send STUN binding request if STUN server address is set. + void MaybePrepareStunCandidate(); + + void SendStunBindingRequests(); + + private: + // A helper class which can be called repeatedly to resolve multiple + // addresses, as opposed to rtc::AsyncResolverInterface, which can only + // resolve one address per instance. + class AddressResolver : public sigslot::has_slots<> { + public: + explicit AddressResolver(rtc::PacketSocketFactory* factory); + ~AddressResolver(); + + void Resolve(const rtc::SocketAddress& address); + bool GetResolvedAddress(const rtc::SocketAddress& input, + int family, + rtc::SocketAddress* output) const; + + // The signal is sent when resolving the specified address is finished. The + // first argument is the input address, the second argument is the error + // or 0 if it succeeded. + sigslot::signal2 SignalDone; + + private: + typedef std::map ResolverMap; + + void OnResolveResult(rtc::AsyncResolverInterface* resolver); + + rtc::PacketSocketFactory* socket_factory_; + ResolverMap resolvers_; + }; + + // DNS resolution of the STUN server. + void ResolveStunAddress(const rtc::SocketAddress& stun_addr); + void OnResolveResult(const rtc::SocketAddress& input, int error); + + void SendStunBindingRequest(const rtc::SocketAddress& stun_addr); + + // Below methods handles binding request responses. + void OnStunBindingRequestSucceeded( + const rtc::SocketAddress& stun_server_addr, + const rtc::SocketAddress& stun_reflected_addr); + void OnStunBindingOrResolveRequestFailed( + const rtc::SocketAddress& stun_server_addr); + + // Sends STUN requests to the server. + void OnSendPacket(const void* data, size_t size, StunRequest* req); + + // TODO(mallinaht) - Move this up to cricket::Port when SignalAddressReady is + // changed to SignalPortReady. + void MaybeSetPortCompleteOrError(); + + bool HasCandidateWithAddress(const rtc::SocketAddress& addr) const; + + ServerAddresses server_addresses_; + ServerAddresses bind_request_succeeded_servers_; + ServerAddresses bind_request_failed_servers_; + StunRequestManager requests_; + rtc::AsyncPacketSocket* socket_; + int error_; + rtc::scoped_ptr resolver_; + bool ready_; + int stun_keepalive_delay_; + + friend class StunBindingRequest; +}; + +class StunPort : public UDPPort { + public: + static StunPort* Create( + rtc::Thread* thread, + rtc::PacketSocketFactory* factory, + rtc::Network* network, + const rtc::IPAddress& ip, + int min_port, int max_port, + const std::string& username, + const std::string& password, + const ServerAddresses& servers) { + StunPort* port = new StunPort(thread, factory, network, + ip, min_port, max_port, + username, password, servers); + if (!port->Init()) { + delete port; + port = NULL; + } + return port; + } + + virtual ~StunPort() {} + + virtual void PrepareAddress() { + SendStunBindingRequests(); + } + + protected: + StunPort(rtc::Thread* thread, rtc::PacketSocketFactory* factory, + rtc::Network* network, const rtc::IPAddress& ip, + int min_port, int max_port, + const std::string& username, const std::string& password, + const ServerAddresses& servers) + : UDPPort(thread, factory, network, ip, min_port, max_port, username, + password) { + // UDPPort will set these to local udp, updating these to STUN. + set_type(STUN_PORT_TYPE); + set_server_addresses(servers); + } +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_STUNPORT_H_ diff --git a/webrtc/p2p/base/stunport_unittest.cc b/webrtc/p2p/base/stunport_unittest.cc new file mode 100644 index 000000000..81b680863 --- /dev/null +++ b/webrtc/p2p/base/stunport_unittest.cc @@ -0,0 +1,283 @@ +/* + * Copyright 2009 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/stunport.h" +#include "webrtc/p2p/base/teststunserver.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/ssladapter.h" +#include "webrtc/base/virtualsocketserver.h" + +using cricket::ServerAddresses; +using rtc::SocketAddress; + +static const SocketAddress kLocalAddr("127.0.0.1", 0); +static const SocketAddress kStunAddr1("127.0.0.1", 5000); +static const SocketAddress kStunAddr2("127.0.0.1", 4000); +static const SocketAddress kBadAddr("0.0.0.1", 5000); +static const SocketAddress kStunHostnameAddr("localhost", 5000); +static const SocketAddress kBadHostnameAddr("not-a-real-hostname", 5000); +static const int kTimeoutMs = 10000; +// stun prio = 100 << 24 | 30 (IPV4) << 8 | 256 - 0 +static const uint32 kStunCandidatePriority = 1677729535; + +// Tests connecting a StunPort to a fake STUN server (cricket::StunServer) +// TODO: Use a VirtualSocketServer here. We have to use a +// PhysicalSocketServer right now since DNS is not part of SocketServer yet. +class StunPortTest : public testing::Test, + public sigslot::has_slots<> { + public: + StunPortTest() + : pss_(new rtc::PhysicalSocketServer), + ss_(new rtc::VirtualSocketServer(pss_.get())), + ss_scope_(ss_.get()), + network_("unittest", "unittest", rtc::IPAddress(INADDR_ANY), 32), + socket_factory_(rtc::Thread::Current()), + stun_server_1_(cricket::TestStunServer::Create( + rtc::Thread::Current(), kStunAddr1)), + stun_server_2_(cricket::TestStunServer::Create( + rtc::Thread::Current(), kStunAddr2)), + done_(false), error_(false), stun_keepalive_delay_(0) { + } + + const cricket::Port* port() const { return stun_port_.get(); } + bool done() const { return done_; } + bool error() const { return error_; } + + void CreateStunPort(const rtc::SocketAddress& server_addr) { + ServerAddresses stun_servers; + stun_servers.insert(server_addr); + CreateStunPort(stun_servers); + } + + void CreateStunPort(const ServerAddresses& stun_servers) { + stun_port_.reset(cricket::StunPort::Create( + rtc::Thread::Current(), &socket_factory_, &network_, + kLocalAddr.ipaddr(), 0, 0, rtc::CreateRandomString(16), + rtc::CreateRandomString(22), stun_servers)); + stun_port_->set_stun_keepalive_delay(stun_keepalive_delay_); + stun_port_->SignalPortComplete.connect(this, + &StunPortTest::OnPortComplete); + stun_port_->SignalPortError.connect(this, + &StunPortTest::OnPortError); + } + + void CreateSharedStunPort(const rtc::SocketAddress& server_addr) { + socket_.reset(socket_factory_.CreateUdpSocket( + rtc::SocketAddress(kLocalAddr.ipaddr(), 0), 0, 0)); + ASSERT_TRUE(socket_ != NULL); + socket_->SignalReadPacket.connect(this, &StunPortTest::OnReadPacket); + stun_port_.reset(cricket::UDPPort::Create( + rtc::Thread::Current(), &socket_factory_, + &network_, socket_.get(), + rtc::CreateRandomString(16), rtc::CreateRandomString(22))); + ASSERT_TRUE(stun_port_ != NULL); + ServerAddresses stun_servers; + stun_servers.insert(server_addr); + stun_port_->set_server_addresses(stun_servers); + stun_port_->SignalPortComplete.connect(this, + &StunPortTest::OnPortComplete); + stun_port_->SignalPortError.connect(this, + &StunPortTest::OnPortError); + } + + void PrepareAddress() { + stun_port_->PrepareAddress(); + } + + void OnReadPacket(rtc::AsyncPacketSocket* socket, const char* data, + size_t size, const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time) { + stun_port_->HandleIncomingPacket( + socket, data, size, remote_addr, rtc::PacketTime()); + } + + void SendData(const char* data, size_t len) { + stun_port_->HandleIncomingPacket( + socket_.get(), data, len, rtc::SocketAddress("22.22.22.22", 0), + rtc::PacketTime()); + } + + protected: + static void SetUpTestCase() { + // Ensure the RNG is inited. + rtc::InitRandom(NULL, 0); + + } + + void OnPortComplete(cricket::Port* port) { + ASSERT_FALSE(done_); + done_ = true; + error_ = false; + } + void OnPortError(cricket::Port* port) { + done_ = true; + error_ = true; + } + void SetKeepaliveDelay(int delay) { + stun_keepalive_delay_ = delay; + } + + cricket::TestStunServer* stun_server_1() { + return stun_server_1_.get(); + } + cricket::TestStunServer* stun_server_2() { + return stun_server_2_.get(); + } + + private: + rtc::scoped_ptr pss_; + rtc::scoped_ptr ss_; + rtc::SocketServerScope ss_scope_; + rtc::Network network_; + rtc::BasicPacketSocketFactory socket_factory_; + rtc::scoped_ptr stun_port_; + rtc::scoped_ptr stun_server_1_; + rtc::scoped_ptr stun_server_2_; + rtc::scoped_ptr socket_; + bool done_; + bool error_; + int stun_keepalive_delay_; +}; + +// Test that we can create a STUN port +TEST_F(StunPortTest, TestBasic) { + CreateStunPort(kStunAddr1); + EXPECT_EQ("stun", port()->Type()); + EXPECT_EQ(0U, port()->Candidates().size()); +} + +// Test that we can get an address from a STUN server. +TEST_F(StunPortTest, TestPrepareAddress) { + CreateStunPort(kStunAddr1); + PrepareAddress(); + EXPECT_TRUE_WAIT(done(), kTimeoutMs); + ASSERT_EQ(1U, port()->Candidates().size()); + EXPECT_TRUE(kLocalAddr.EqualIPs(port()->Candidates()[0].address())); + + // TODO: Add IPv6 tests here, once either physicalsocketserver supports + // IPv6, or this test is changed to use VirtualSocketServer. +} + +// Test that we fail properly if we can't get an address. +TEST_F(StunPortTest, TestPrepareAddressFail) { + CreateStunPort(kBadAddr); + PrepareAddress(); + EXPECT_TRUE_WAIT(done(), kTimeoutMs); + EXPECT_TRUE(error()); + EXPECT_EQ(0U, port()->Candidates().size()); +} + +// Test that we can get an address from a STUN server specified by a hostname. +TEST_F(StunPortTest, TestPrepareAddressHostname) { + CreateStunPort(kStunHostnameAddr); + PrepareAddress(); + EXPECT_TRUE_WAIT(done(), kTimeoutMs); + ASSERT_EQ(1U, port()->Candidates().size()); + EXPECT_TRUE(kLocalAddr.EqualIPs(port()->Candidates()[0].address())); + EXPECT_EQ(kStunCandidatePriority, port()->Candidates()[0].priority()); +} + +// Test that we handle hostname lookup failures properly. +TEST_F(StunPortTest, TestPrepareAddressHostnameFail) { + CreateStunPort(kBadHostnameAddr); + PrepareAddress(); + EXPECT_TRUE_WAIT(done(), kTimeoutMs); + EXPECT_TRUE(error()); + EXPECT_EQ(0U, port()->Candidates().size()); +} + +// This test verifies keepalive response messages don't result in +// additional candidate generation. +TEST_F(StunPortTest, TestKeepAliveResponse) { + SetKeepaliveDelay(500); // 500ms of keepalive delay. + CreateStunPort(kStunHostnameAddr); + PrepareAddress(); + EXPECT_TRUE_WAIT(done(), kTimeoutMs); + ASSERT_EQ(1U, port()->Candidates().size()); + EXPECT_TRUE(kLocalAddr.EqualIPs(port()->Candidates()[0].address())); + // Waiting for 1 seond, which will allow us to process + // response for keepalive binding request. 500 ms is the keepalive delay. + rtc::Thread::Current()->ProcessMessages(1000); + ASSERT_EQ(1U, port()->Candidates().size()); +} + +// Test that a local candidate can be generated using a shared socket. +TEST_F(StunPortTest, TestSharedSocketPrepareAddress) { + CreateSharedStunPort(kStunAddr1); + PrepareAddress(); + EXPECT_TRUE_WAIT(done(), kTimeoutMs); + ASSERT_EQ(1U, port()->Candidates().size()); + EXPECT_TRUE(kLocalAddr.EqualIPs(port()->Candidates()[0].address())); +} + +// Test that we still a get a local candidate with invalid stun server hostname. +// Also verifing that UDPPort can receive packets when stun address can't be +// resolved. +TEST_F(StunPortTest, TestSharedSocketPrepareAddressInvalidHostname) { + CreateSharedStunPort(kBadHostnameAddr); + PrepareAddress(); + EXPECT_TRUE_WAIT(done(), kTimeoutMs); + ASSERT_EQ(1U, port()->Candidates().size()); + EXPECT_TRUE(kLocalAddr.EqualIPs(port()->Candidates()[0].address())); + + // Send data to port after it's ready. This is to make sure, UDP port can + // handle data with unresolved stun server address. + std::string data = "some random data, sending to cricket::Port."; + SendData(data.c_str(), data.length()); + // No crash is success. +} + +// Test that the same address is added only once if two STUN servers are in use. +TEST_F(StunPortTest, TestNoDuplicatedAddressWithTwoStunServers) { + ServerAddresses stun_servers; + stun_servers.insert(kStunAddr1); + stun_servers.insert(kStunAddr2); + CreateStunPort(stun_servers); + EXPECT_EQ("stun", port()->Type()); + PrepareAddress(); + EXPECT_TRUE_WAIT(done(), kTimeoutMs); + EXPECT_EQ(1U, port()->Candidates().size()); +} + +// Test that candidates can be allocated for multiple STUN servers, one of which +// is not reachable. +TEST_F(StunPortTest, TestMultipleStunServersWithBadServer) { + ServerAddresses stun_servers; + stun_servers.insert(kStunAddr1); + stun_servers.insert(kBadAddr); + CreateStunPort(stun_servers); + EXPECT_EQ("stun", port()->Type()); + PrepareAddress(); + EXPECT_TRUE_WAIT(done(), kTimeoutMs); + EXPECT_EQ(1U, port()->Candidates().size()); +} + +// Test that two candidates are allocated if the two STUN servers return +// different mapped addresses. +TEST_F(StunPortTest, TestTwoCandidatesWithTwoStunServersAcrossNat) { + const SocketAddress kStunMappedAddr1("77.77.77.77", 0); + const SocketAddress kStunMappedAddr2("88.77.77.77", 0); + stun_server_1()->set_fake_stun_addr(kStunMappedAddr1); + stun_server_2()->set_fake_stun_addr(kStunMappedAddr2); + + ServerAddresses stun_servers; + stun_servers.insert(kStunAddr1); + stun_servers.insert(kStunAddr2); + CreateStunPort(stun_servers); + EXPECT_EQ("stun", port()->Type()); + PrepareAddress(); + EXPECT_TRUE_WAIT(done(), kTimeoutMs); + EXPECT_EQ(2U, port()->Candidates().size()); +} diff --git a/webrtc/p2p/base/stunrequest.cc b/webrtc/p2p/base/stunrequest.cc new file mode 100644 index 000000000..65eb027f4 --- /dev/null +++ b/webrtc/p2p/base/stunrequest.cc @@ -0,0 +1,193 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/stunrequest.h" + +#include "webrtc/base/common.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" + +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(rtc::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()); + request->Construct(); + 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) { + // StunRequest destructor calls Remove() which deletes requests + // from |requests_|. + delete 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() == GetStunSuccessResponseType(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 " + << GetStunSuccessResponseType(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 + kStunTransactionIdOffset, kStunTransactionIdLength); + + RequestMap::iterator iter = requests_.find(id); + if (iter == requests_.end()) + return false; + + // Parse the STUN message and continue processing as usual. + + rtc::ByteBuffer buf(data, size); + rtc::scoped_ptr response(iter->second->msg_->CreateNew()); + if (!response->Read(&buf)) + return false; + + return CheckResponse(response.get()); +} + +StunRequest::StunRequest() + : count_(0), timeout_(false), manager_(0), + msg_(new StunMessage()), tstamp_(0) { + msg_->SetTransactionID( + rtc::CreateRandomString(kStunTransactionIdLength)); +} + +StunRequest::StunRequest(StunMessage* request) + : count_(0), timeout_(false), manager_(0), + msg_(request), tstamp_(0) { + msg_->SetTransactionID( + rtc::CreateRandomString(kStunTransactionIdLength)); +} + +StunRequest::~StunRequest() { + ASSERT(manager_ != NULL); + if (manager_) { + manager_->Remove(this); + manager_->thread_->Clear(this); + } + delete msg_; +} + +void StunRequest::Construct() { + if (msg_->type() == 0) { + Prepare(msg_); + ASSERT(msg_->type() != 0); + } +} + +int StunRequest::type() { + ASSERT(msg_ != NULL); + return msg_->type(); +} + +const StunMessage* StunRequest::msg() const { + return msg_; +} + +uint32 StunRequest::Elapsed() const { + return rtc::TimeSince(tstamp_); +} + + +void StunRequest::set_manager(StunRequestManager* manager) { + ASSERT(!manager_); + manager_ = manager; +} + +void StunRequest::OnMessage(rtc::Message* pmsg) { + ASSERT(manager_ != NULL); + ASSERT(pmsg->message_id == MSG_STUN_SEND); + + if (timeout_) { + OnTimeout(); + delete this; + return; + } + + tstamp_ = rtc::Time(); + + rtc::ByteBuffer buf; + msg_->Write(&buf); + manager_->SignalSendPacket(buf.Data(), buf.Length(), this); + + int delay = GetNextDelay(); + manager_->thread_->PostDelayed(delay, this, MSG_STUN_SEND, NULL); +} + +int StunRequest::GetNextDelay() { + int delay = DELAY_UNIT * rtc::_min(1 << count_, DELAY_MAX_FACTOR); + count_ += 1; + if (count_ == MAX_SENDS) + timeout_ = true; + return delay; +} + +} // namespace cricket diff --git a/webrtc/p2p/base/stunrequest.h b/webrtc/p2p/base/stunrequest.h new file mode 100644 index 000000000..5fefc2ff9 --- /dev/null +++ b/webrtc/p2p/base/stunrequest.h @@ -0,0 +1,116 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_STUNREQUEST_H_ +#define WEBRTC_P2P_BASE_STUNREQUEST_H_ + +#include +#include +#include "webrtc/p2p/base/stun.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/thread.h" + +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(rtc::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); + + bool empty() { return requests_.empty(); } + + // Raised when there are bytes to be sent. + sigslot::signal3 SignalSendPacket; + +private: + typedef std::map RequestMap; + + rtc::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 rtc::MessageHandler { +public: + StunRequest(); + StunRequest(StunMessage* request); + virtual ~StunRequest(); + + // Causes our wrapped StunMessage to be Prepared + void Construct(); + + // 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 msg_->transaction_id(); } + + // Returns the STUN type of the request message. + int type(); + + // Returns a const pointer to |msg_|. + const StunMessage* msg() const; + + // Time elapsed since last send (in ms) + uint32 Elapsed() const; + +protected: + int count_; + bool timeout_; + + // Fills in a request object to be sent. Note that request's 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: + void set_manager(StunRequestManager* manager); + + // Handles messages for sending and timeout. + void OnMessage(rtc::Message* pmsg); + + StunRequestManager* manager_; + StunMessage* msg_; + uint32 tstamp_; + + friend class StunRequestManager; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_STUNREQUEST_H_ diff --git a/webrtc/p2p/base/stunrequest_unittest.cc b/webrtc/p2p/base/stunrequest_unittest.cc new file mode 100644 index 000000000..3ff6cbaf7 --- /dev/null +++ b/webrtc/p2p/base/stunrequest_unittest.cc @@ -0,0 +1,203 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/stunrequest.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/ssladapter.h" +#include "webrtc/base/timeutils.h" + +using namespace cricket; + +class StunRequestTest : public testing::Test, + public sigslot::has_slots<> { + public: + StunRequestTest() + : manager_(rtc::Thread::Current()), + request_count_(0), response_(NULL), + success_(false), failure_(false), timeout_(false) { + manager_.SignalSendPacket.connect(this, &StunRequestTest::OnSendPacket); + } + + void OnSendPacket(const void* data, size_t size, StunRequest* req) { + request_count_++; + } + + void OnResponse(StunMessage* res) { + response_ = res; + success_ = true; + } + void OnErrorResponse(StunMessage* res) { + response_ = res; + failure_ = true; + } + void OnTimeout() { + timeout_ = true; + } + + protected: + static StunMessage* CreateStunMessage(StunMessageType type, + StunMessage* req) { + StunMessage* msg = new StunMessage(); + msg->SetType(type); + if (req) { + msg->SetTransactionID(req->transaction_id()); + } + return msg; + } + static int TotalDelay(int sends) { + int total = 0; + for (int i = 0; i < sends; i++) { + if (i < 4) + total += 100 << i; + else + total += 1600; + } + return total; + } + + StunRequestManager manager_; + int request_count_; + StunMessage* response_; + bool success_; + bool failure_; + bool timeout_; +}; + +// Forwards results to the test class. +class StunRequestThunker : public StunRequest { + public: + StunRequestThunker(StunMessage* msg, StunRequestTest* test) + : StunRequest(msg), test_(test) {} + explicit StunRequestThunker(StunRequestTest* test) : test_(test) {} + private: + virtual void OnResponse(StunMessage* res) { + test_->OnResponse(res); + } + virtual void OnErrorResponse(StunMessage* res) { + test_->OnErrorResponse(res); + } + virtual void OnTimeout() { + test_->OnTimeout(); + } + + virtual void Prepare(StunMessage* request) { + request->SetType(STUN_BINDING_REQUEST); + } + + StunRequestTest* test_; +}; + +// Test handling of a normal binding response. +TEST_F(StunRequestTest, TestSuccess) { + StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL); + + manager_.Send(new StunRequestThunker(req, this)); + StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, req); + EXPECT_TRUE(manager_.CheckResponse(res)); + + EXPECT_TRUE(response_ == res); + EXPECT_TRUE(success_); + EXPECT_FALSE(failure_); + EXPECT_FALSE(timeout_); + delete res; +} + +// Test handling of an error binding response. +TEST_F(StunRequestTest, TestError) { + StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL); + + manager_.Send(new StunRequestThunker(req, this)); + StunMessage* res = CreateStunMessage(STUN_BINDING_ERROR_RESPONSE, req); + EXPECT_TRUE(manager_.CheckResponse(res)); + + EXPECT_TRUE(response_ == res); + EXPECT_FALSE(success_); + EXPECT_TRUE(failure_); + EXPECT_FALSE(timeout_); + delete res; +} + +// Test handling of a binding response with the wrong transaction id. +TEST_F(StunRequestTest, TestUnexpected) { + StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL); + + manager_.Send(new StunRequestThunker(req, this)); + StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, NULL); + EXPECT_FALSE(manager_.CheckResponse(res)); + + EXPECT_TRUE(response_ == NULL); + EXPECT_FALSE(success_); + EXPECT_FALSE(failure_); + EXPECT_FALSE(timeout_); + delete res; +} + +// Test that requests are sent at the right times, and that the 9th request +// (sent at 7900 ms) can be properly replied to. +TEST_F(StunRequestTest, TestBackoff) { + StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL); + + uint32 start = rtc::Time(); + manager_.Send(new StunRequestThunker(req, this)); + StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, req); + for (int i = 0; i < 9; ++i) { + while (request_count_ == i) + rtc::Thread::Current()->ProcessMessages(1); + int32 elapsed = rtc::TimeSince(start); + LOG(LS_INFO) << "STUN request #" << (i + 1) + << " sent at " << elapsed << " ms"; + EXPECT_GE(TotalDelay(i + 1), elapsed); + } + EXPECT_TRUE(manager_.CheckResponse(res)); + + EXPECT_TRUE(response_ == res); + EXPECT_TRUE(success_); + EXPECT_FALSE(failure_); + EXPECT_FALSE(timeout_); + delete res; +} + +// Test that we timeout properly if no response is received in 9500 ms. +TEST_F(StunRequestTest, TestTimeout) { + StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL); + StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, req); + + manager_.Send(new StunRequestThunker(req, this)); + rtc::Thread::Current()->ProcessMessages(10000); // > STUN timeout + EXPECT_FALSE(manager_.CheckResponse(res)); + + EXPECT_TRUE(response_ == NULL); + EXPECT_FALSE(success_); + EXPECT_FALSE(failure_); + EXPECT_TRUE(timeout_); + delete res; +} + +// Regression test for specific crash where we receive a response with the +// same id as a request that doesn't have an underlying StunMessage yet. +TEST_F(StunRequestTest, TestNoEmptyRequest) { + StunRequestThunker* request = new StunRequestThunker(this); + + manager_.SendDelayed(request, 100); + + StunMessage dummy_req; + dummy_req.SetTransactionID(request->id()); + StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, &dummy_req); + + EXPECT_TRUE(manager_.CheckResponse(res)); + + EXPECT_TRUE(response_ == res); + EXPECT_TRUE(success_); + EXPECT_FALSE(failure_); + EXPECT_FALSE(timeout_); + delete res; +} diff --git a/webrtc/p2p/base/stunserver.cc b/webrtc/p2p/base/stunserver.cc new file mode 100644 index 000000000..fbc316bd4 --- /dev/null +++ b/webrtc/p2p/base/stunserver.cc @@ -0,0 +1,99 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/stunserver.h" + +#include "webrtc/base/bytebuffer.h" +#include "webrtc/base/logging.h" + +namespace cricket { + +StunServer::StunServer(rtc::AsyncUDPSocket* socket) : socket_(socket) { + socket_->SignalReadPacket.connect(this, &StunServer::OnPacket); +} + +StunServer::~StunServer() { + socket_->SignalReadPacket.disconnect(this); +} + +void StunServer::OnPacket( + rtc::AsyncPacketSocket* socket, const char* buf, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time) { + // Parse the STUN message; eat any messages that fail to parse. + rtc::ByteBuffer bbuf(buf, size); + StunMessage msg; + if (!msg.Read(&bbuf)) { + return; + } + + // TODO: If unknown non-optional (<= 0x7fff) attributes are found, send a + // 420 "Unknown Attribute" response. + + // Send the message to the appropriate handler function. + switch (msg.type()) { + case STUN_BINDING_REQUEST: + OnBindingRequest(&msg, remote_addr); + break; + + default: + SendErrorResponse(msg, remote_addr, 600, "Operation Not Supported"); + } +} + +void StunServer::OnBindingRequest( + StunMessage* msg, const rtc::SocketAddress& remote_addr) { + StunMessage response; + GetStunBindReqponse(msg, remote_addr, &response); + SendResponse(response, remote_addr); +} + +void StunServer::SendErrorResponse( + const StunMessage& msg, const rtc::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->SetCode(error_code); + err_code->SetReason(error_desc); + err_msg.AddAttribute(err_code); + + SendResponse(err_msg, addr); +} + +void StunServer::SendResponse( + const StunMessage& msg, const rtc::SocketAddress& addr) { + rtc::ByteBuffer buf; + msg.Write(&buf); + rtc::PacketOptions options; + if (socket_->SendTo(buf.Data(), buf.Length(), addr, options) < 0) + LOG_ERR(LS_ERROR) << "sendto"; +} + +void StunServer::GetStunBindReqponse(StunMessage* request, + const rtc::SocketAddress& remote_addr, + StunMessage* response) const { + response->SetType(STUN_BINDING_RESPONSE); + response->SetTransactionID(request->transaction_id()); + + // Tell the user the address that we received their request from. + StunAddressAttribute* mapped_addr; + if (!request->IsLegacy()) { + mapped_addr = StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS); + } else { + mapped_addr = StunAttribute::CreateXorAddress(STUN_ATTR_XOR_MAPPED_ADDRESS); + } + mapped_addr->SetAddress(remote_addr); + response->AddAttribute(mapped_addr); +} + +} // namespace cricket diff --git a/webrtc/p2p/base/stunserver.h b/webrtc/p2p/base/stunserver.h new file mode 100644 index 000000000..a7eeab154 --- /dev/null +++ b/webrtc/p2p/base/stunserver.h @@ -0,0 +1,66 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_STUNSERVER_H_ +#define WEBRTC_P2P_BASE_STUNSERVER_H_ + +#include "webrtc/p2p/base/stun.h" +#include "webrtc/base/asyncudpsocket.h" +#include "webrtc/base/scoped_ptr.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. + explicit StunServer(rtc::AsyncUDPSocket* socket); + // Removes the STUN server from the socket and deletes the socket. + ~StunServer(); + + protected: + // Slot for AsyncSocket.PacketRead: + void OnPacket( + rtc::AsyncPacketSocket* socket, const char* buf, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time); + + // Handlers for the different types of STUN/TURN requests: + virtual void OnBindingRequest(StunMessage* msg, + const rtc::SocketAddress& addr); + void OnAllocateRequest(StunMessage* msg, + const rtc::SocketAddress& addr); + void OnSharedSecretRequest(StunMessage* msg, + const rtc::SocketAddress& addr); + void OnSendRequest(StunMessage* msg, + const rtc::SocketAddress& addr); + + // Sends an error response to the given message back to the user. + void SendErrorResponse( + const StunMessage& msg, const rtc::SocketAddress& addr, + int error_code, const char* error_desc); + + // Sends the given message to the appropriate destination. + void SendResponse(const StunMessage& msg, + const rtc::SocketAddress& addr); + + // A helper method to compose a STUN binding response. + void GetStunBindReqponse(StunMessage* request, + const rtc::SocketAddress& remote_addr, + StunMessage* response) const; + + private: + rtc::scoped_ptr socket_; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_STUNSERVER_H_ diff --git a/webrtc/p2p/base/stunserver_unittest.cc b/webrtc/p2p/base/stunserver_unittest.cc new file mode 100644 index 000000000..7266eae86 --- /dev/null +++ b/webrtc/p2p/base/stunserver_unittest.cc @@ -0,0 +1,109 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/p2p/base/stunserver.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/testclient.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/virtualsocketserver.h" + +using namespace cricket; + +static const rtc::SocketAddress server_addr("99.99.99.1", 3478); +static const rtc::SocketAddress client_addr("1.2.3.4", 1234); + +class StunServerTest : public testing::Test { + public: + StunServerTest() + : pss_(new rtc::PhysicalSocketServer), + ss_(new rtc::VirtualSocketServer(pss_.get())), + worker_(ss_.get()) { + } + virtual void SetUp() { + server_.reset(new StunServer( + rtc::AsyncUDPSocket::Create(ss_.get(), server_addr))); + client_.reset(new rtc::TestClient( + rtc::AsyncUDPSocket::Create(ss_.get(), client_addr))); + + worker_.Start(); + } + void Send(const StunMessage& msg) { + rtc::ByteBuffer buf; + msg.Write(&buf); + Send(buf.Data(), static_cast(buf.Length())); + } + void Send(const char* buf, int len) { + client_->SendTo(buf, len, server_addr); + } + StunMessage* Receive() { + StunMessage* msg = NULL; + rtc::TestClient::Packet* packet = client_->NextPacket(); + if (packet) { + rtc::ByteBuffer buf(packet->buf, packet->size); + msg = new StunMessage(); + msg->Read(&buf); + delete packet; + } + return msg; + } + private: + rtc::scoped_ptr pss_; + rtc::scoped_ptr ss_; + rtc::Thread worker_; + rtc::scoped_ptr server_; + rtc::scoped_ptr client_; +}; + +// Disable for TSan v2, see +// https://code.google.com/p/webrtc/issues/detail?id=2517 for details. +#if !defined(THREAD_SANITIZER) + +TEST_F(StunServerTest, TestGood) { + StunMessage req; + std::string transaction_id = "0123456789ab"; + req.SetType(STUN_BINDING_REQUEST); + req.SetTransactionID(transaction_id); + Send(req); + + StunMessage* msg = Receive(); + ASSERT_TRUE(msg != NULL); + EXPECT_EQ(STUN_BINDING_RESPONSE, msg->type()); + EXPECT_EQ(req.transaction_id(), msg->transaction_id()); + + const StunAddressAttribute* mapped_addr = + msg->GetAddress(STUN_ATTR_MAPPED_ADDRESS); + EXPECT_TRUE(mapped_addr != NULL); + EXPECT_EQ(1, mapped_addr->family()); + EXPECT_EQ(client_addr.port(), mapped_addr->port()); + if (mapped_addr->ipaddr() != client_addr.ipaddr()) { + LOG(LS_WARNING) << "Warning: mapped IP (" + << mapped_addr->ipaddr() + << ") != local IP (" << client_addr.ipaddr() + << ")"; + } + + delete msg; +} + +#endif // if !defined(THREAD_SANITIZER) + +TEST_F(StunServerTest, TestBad) { + const char* bad = "this is a completely nonsensical message whose only " + "purpose is to make the parser go 'ack'. it doesn't " + "look anything like a normal stun message"; + Send(bad, static_cast(strlen(bad))); + + StunMessage* msg = Receive(); + ASSERT_TRUE(msg == NULL); +} diff --git a/webrtc/p2p/base/tcpport.cc b/webrtc/p2p/base/tcpport.cc new file mode 100644 index 000000000..be3068be8 --- /dev/null +++ b/webrtc/p2p/base/tcpport.cc @@ -0,0 +1,321 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/tcpport.h" + +#include "webrtc/p2p/base/common.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" + +namespace cricket { + +TCPPort::TCPPort(rtc::Thread* thread, + rtc::PacketSocketFactory* factory, + rtc::Network* network, const rtc::IPAddress& ip, + int min_port, int max_port, const std::string& username, + const std::string& password, bool allow_listen) + : Port(thread, LOCAL_PORT_TYPE, factory, network, ip, min_port, max_port, + username, password), + incoming_only_(false), + allow_listen_(allow_listen), + socket_(NULL), + error_(0) { + // TODO(mallinath) - Set preference value as per RFC 6544. + // http://b/issue?id=7141794 +} + +bool TCPPort::Init() { + if (allow_listen_) { + // Treat failure to create or bind a TCP socket as fatal. This + // should never happen. + socket_ = socket_factory()->CreateServerTcpSocket( + rtc::SocketAddress(ip(), 0), min_port(), max_port(), + false /* ssl */); + if (!socket_) { + LOG_J(LS_ERROR, this) << "TCP socket creation failed."; + return false; + } + socket_->SignalNewConnection.connect(this, &TCPPort::OnNewConnection); + socket_->SignalAddressReady.connect(this, &TCPPort::OnAddressReady); + } + return true; +} + +TCPPort::~TCPPort() { + delete socket_; + std::list::iterator it; + for (it = incoming_.begin(); it != incoming_.end(); ++it) + delete it->socket; + incoming_.clear(); +} + +Connection* TCPPort::CreateConnection(const Candidate& address, + CandidateOrigin origin) { + // We only support TCP protocols + if ((address.protocol() != TCP_PROTOCOL_NAME) && + (address.protocol() != SSLTCP_PROTOCOL_NAME)) { + return NULL; + } + + if (address.tcptype() == TCPTYPE_ACTIVE_STR || + (address.tcptype().empty() && address.address().port() == 0)) { + // It's active only candidate, we should not try to create connections + // for these candidates. + return NULL; + } + + // We can't accept TCP connections incoming on other ports + if (origin == ORIGIN_OTHER_PORT) + return NULL; + + // Check if we are allowed to make outgoing TCP connections + if (incoming_only_ && (origin == ORIGIN_MESSAGE)) + return NULL; + + // We don't know how to act as an ssl server yet + if ((address.protocol() == SSLTCP_PROTOCOL_NAME) && + (origin == ORIGIN_THIS_PORT)) { + return NULL; + } + + if (!IsCompatibleAddress(address.address())) { + return NULL; + } + + TCPConnection* conn = NULL; + if (rtc::AsyncPacketSocket* 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() { + if (socket_) { + // If socket isn't bound yet the address will be added in + // OnAddressReady(). Socket may be in the CLOSED state if Listen() + // failed, we still want to add the socket address. + LOG(LS_VERBOSE) << "Preparing TCP address, current state: " + << socket_->GetState(); + if (socket_->GetState() == rtc::AsyncPacketSocket::STATE_BOUND || + socket_->GetState() == rtc::AsyncPacketSocket::STATE_CLOSED) + AddAddress(socket_->GetLocalAddress(), socket_->GetLocalAddress(), + rtc::SocketAddress(), + TCP_PROTOCOL_NAME, TCPTYPE_PASSIVE_STR, LOCAL_PORT_TYPE, + ICE_TYPE_PREFERENCE_HOST_TCP, 0, true); + } else { + LOG_J(LS_INFO, this) << "Not listening due to firewall restrictions."; + // Note: We still add the address, since otherwise the remote side won't + // recognize our incoming TCP connections. + AddAddress(rtc::SocketAddress(ip(), 0), + rtc::SocketAddress(ip(), 0), rtc::SocketAddress(), + TCP_PROTOCOL_NAME, TCPTYPE_ACTIVE_STR, LOCAL_PORT_TYPE, + ICE_TYPE_PREFERENCE_HOST_TCP, 0, true); + } +} + +int TCPPort::SendTo(const void* data, size_t size, + const rtc::SocketAddress& addr, + const rtc::PacketOptions& options, + bool payload) { + rtc::AsyncPacketSocket * socket = NULL; + if (TCPConnection * conn = static_cast(GetConnection(addr))) { + socket = conn->socket(); + } else { + socket = GetIncoming(addr); + } + if (!socket) { + LOG_J(LS_ERROR, this) << "Attempted to send to an unknown destination, " + << addr.ToSensitiveString(); + return -1; // TODO: Set error_ + } + + int sent = socket->Send(data, size, options); + if (sent < 0) { + error_ = socket->GetError(); + LOG_J(LS_ERROR, this) << "TCP send of " << size + << " bytes failed with error " << error_; + } + return sent; +} + +int TCPPort::GetOption(rtc::Socket::Option opt, int* value) { + if (socket_) { + return socket_->GetOption(opt, value); + } else { + return SOCKET_ERROR; + } +} + +int TCPPort::SetOption(rtc::Socket::Option opt, int value) { + if (socket_) { + return socket_->SetOption(opt, value); + } else { + return SOCKET_ERROR; + } +} + +int TCPPort::GetError() { + return error_; +} + +void TCPPort::OnNewConnection(rtc::AsyncPacketSocket* socket, + rtc::AsyncPacketSocket* new_socket) { + ASSERT(socket == socket_); + + Incoming incoming; + incoming.addr = new_socket->GetRemoteAddress(); + incoming.socket = new_socket; + incoming.socket->SignalReadPacket.connect(this, &TCPPort::OnReadPacket); + incoming.socket->SignalReadyToSend.connect(this, &TCPPort::OnReadyToSend); + + LOG_J(LS_VERBOSE, this) << "Accepted connection from " + << incoming.addr.ToSensitiveString(); + incoming_.push_back(incoming); +} + +rtc::AsyncPacketSocket* TCPPort::GetIncoming( + const rtc::SocketAddress& addr, bool remove) { + rtc::AsyncPacketSocket* socket = NULL; + 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(rtc::AsyncPacketSocket* socket, + const char* data, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time) { + Port::OnReadPacket(data, size, remote_addr, PROTO_TCP); +} + +void TCPPort::OnReadyToSend(rtc::AsyncPacketSocket* socket) { + Port::OnReadyToSend(); +} + +void TCPPort::OnAddressReady(rtc::AsyncPacketSocket* socket, + const rtc::SocketAddress& address) { + AddAddress(address, address, rtc::SocketAddress(), + TCP_PROTOCOL_NAME, TCPTYPE_PASSIVE_STR, LOCAL_PORT_TYPE, + ICE_TYPE_PREFERENCE_HOST_TCP, 0, true); +} + +TCPConnection::TCPConnection(TCPPort* port, const Candidate& candidate, + rtc::AsyncPacketSocket* socket) + : Connection(port, 0, candidate), socket_(socket), error_(0) { + bool outgoing = (socket_ == NULL); + if (outgoing) { + // TODO: Handle failures here (unlikely since TCP). + int opts = (candidate.protocol() == SSLTCP_PROTOCOL_NAME) ? + rtc::PacketSocketFactory::OPT_SSLTCP : 0; + socket_ = port->socket_factory()->CreateClientTcpSocket( + rtc::SocketAddress(port->ip(), 0), + candidate.address(), port->proxy(), port->user_agent(), opts); + if (socket_) { + LOG_J(LS_VERBOSE, this) << "Connecting from " + << socket_->GetLocalAddress().ToSensitiveString() + << " to " + << candidate.address().ToSensitiveString(); + set_connected(false); + socket_->SignalConnect.connect(this, &TCPConnection::OnConnect); + } else { + LOG_J(LS_WARNING, this) << "Failed to create connection to " + << candidate.address().ToSensitiveString(); + } + } else { + // Incoming connections should match the network address. + ASSERT(socket_->GetLocalAddress().ipaddr() == port->ip()); + } + + if (socket_) { + socket_->SignalReadPacket.connect(this, &TCPConnection::OnReadPacket); + socket_->SignalReadyToSend.connect(this, &TCPConnection::OnReadyToSend); + socket_->SignalClose.connect(this, &TCPConnection::OnClose); + } +} + +TCPConnection::~TCPConnection() { + delete socket_; +} + +int TCPConnection::Send(const void* data, size_t size, + const rtc::PacketOptions& options) { + if (!socket_) { + error_ = ENOTCONN; + return SOCKET_ERROR; + } + + if (write_state() != STATE_WRITABLE) { + // TODO: Should STATE_WRITE_TIMEOUT return a non-blocking error? + error_ = EWOULDBLOCK; + return SOCKET_ERROR; + } + int sent = socket_->Send(data, size, options); + if (sent < 0) { + error_ = socket_->GetError(); + } else { + send_rate_tracker_.Update(sent); + } + return sent; +} + +int TCPConnection::GetError() { + return error_; +} + +void TCPConnection::OnConnect(rtc::AsyncPacketSocket* socket) { + ASSERT(socket == socket_); + // Do not use this connection if the socket bound to a different address than + // the one we asked for. This is seen in Chrome, where TCP sockets cannot be + // given a binding address, and the platform is expected to pick the + // correct local address. + if (socket->GetLocalAddress().ipaddr() == port()->ip()) { + LOG_J(LS_VERBOSE, this) << "Connection established to " + << socket->GetRemoteAddress().ToSensitiveString(); + set_connected(true); + } else { + LOG_J(LS_WARNING, this) << "Dropping connection as TCP socket bound to a " + << "different address from the local candidate."; + socket_->Close(); + } +} + +void TCPConnection::OnClose(rtc::AsyncPacketSocket* socket, int error) { + ASSERT(socket == socket_); + LOG_J(LS_VERBOSE, this) << "Connection closed with error " << error; + set_connected(false); + set_write_state(STATE_WRITE_TIMEOUT); +} + +void TCPConnection::OnReadPacket( + rtc::AsyncPacketSocket* socket, const char* data, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time) { + ASSERT(socket == socket_); + Connection::OnReadPacket(data, size, packet_time); +} + +void TCPConnection::OnReadyToSend(rtc::AsyncPacketSocket* socket) { + ASSERT(socket == socket_); + Connection::OnReadyToSend(); +} + +} // namespace cricket diff --git a/webrtc/p2p/base/tcpport.h b/webrtc/p2p/base/tcpport.h new file mode 100644 index 000000000..43e49366d --- /dev/null +++ b/webrtc/p2p/base/tcpport.h @@ -0,0 +1,136 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_TCPPORT_H_ +#define WEBRTC_P2P_BASE_TCPPORT_H_ + +#include +#include +#include "webrtc/p2p/base/port.h" +#include "webrtc/base/asyncpacketsocket.h" + +namespace cricket { + +class TCPConnection; + +// 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: + static TCPPort* Create(rtc::Thread* thread, + rtc::PacketSocketFactory* factory, + rtc::Network* network, + const rtc::IPAddress& ip, + int min_port, int max_port, + const std::string& username, + const std::string& password, + bool allow_listen) { + TCPPort* port = new TCPPort(thread, factory, network, + ip, min_port, max_port, + username, password, allow_listen); + if (!port->Init()) { + delete port; + port = NULL; + } + return port; + } + virtual ~TCPPort(); + + virtual Connection* CreateConnection(const Candidate& address, + CandidateOrigin origin); + + virtual void PrepareAddress(); + + virtual int GetOption(rtc::Socket::Option opt, int* value); + virtual int SetOption(rtc::Socket::Option opt, int value); + virtual int GetError(); + + protected: + TCPPort(rtc::Thread* thread, rtc::PacketSocketFactory* factory, + rtc::Network* network, const rtc::IPAddress& ip, + int min_port, int max_port, const std::string& username, + const std::string& password, bool allow_listen); + bool Init(); + + // Handles sending using the local TCP socket. + virtual int SendTo(const void* data, size_t size, + const rtc::SocketAddress& addr, + const rtc::PacketOptions& options, + bool payload); + + // Accepts incoming TCP connection. + void OnNewConnection(rtc::AsyncPacketSocket* socket, + rtc::AsyncPacketSocket* new_socket); + + private: + struct Incoming { + rtc::SocketAddress addr; + rtc::AsyncPacketSocket* socket; + }; + + rtc::AsyncPacketSocket* GetIncoming( + const rtc::SocketAddress& addr, bool remove = false); + + // Receives packet signal from the local TCP Socket. + void OnReadPacket(rtc::AsyncPacketSocket* socket, + const char* data, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time); + + void OnReadyToSend(rtc::AsyncPacketSocket* socket); + + void OnAddressReady(rtc::AsyncPacketSocket* socket, + const rtc::SocketAddress& address); + + // TODO: Is this still needed? + bool incoming_only_; + bool allow_listen_; + rtc::AsyncPacketSocket* socket_; + int error_; + std::list incoming_; + + friend class TCPConnection; +}; + +class TCPConnection : public Connection { + public: + // Connection is outgoing unless socket is specified + TCPConnection(TCPPort* port, const Candidate& candidate, + rtc::AsyncPacketSocket* socket = 0); + virtual ~TCPConnection(); + + virtual int Send(const void* data, size_t size, + const rtc::PacketOptions& options); + virtual int GetError(); + + rtc::AsyncPacketSocket* socket() { return socket_; } + + private: + void OnConnect(rtc::AsyncPacketSocket* socket); + void OnClose(rtc::AsyncPacketSocket* socket, int error); + void OnReadPacket(rtc::AsyncPacketSocket* socket, + const char* data, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time); + void OnReadyToSend(rtc::AsyncPacketSocket* socket); + + rtc::AsyncPacketSocket* socket_; + int error_; + + friend class TCPPort; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_TCPPORT_H_ diff --git a/webrtc/p2p/base/testrelayserver.h b/webrtc/p2p/base/testrelayserver.h new file mode 100644 index 000000000..87cb9e5dc --- /dev/null +++ b/webrtc/p2p/base/testrelayserver.h @@ -0,0 +1,101 @@ +/* + * Copyright 2008 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_TESTRELAYSERVER_H_ +#define WEBRTC_P2P_BASE_TESTRELAYSERVER_H_ + +#include "webrtc/p2p/base/relayserver.h" +#include "webrtc/base/asynctcpsocket.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/socketadapters.h" +#include "webrtc/base/thread.h" + +namespace cricket { + +// A test relay server. Useful for unit tests. +class TestRelayServer : public sigslot::has_slots<> { + public: + TestRelayServer(rtc::Thread* thread, + const rtc::SocketAddress& udp_int_addr, + const rtc::SocketAddress& udp_ext_addr, + const rtc::SocketAddress& tcp_int_addr, + const rtc::SocketAddress& tcp_ext_addr, + const rtc::SocketAddress& ssl_int_addr, + const rtc::SocketAddress& ssl_ext_addr) + : server_(thread) { + server_.AddInternalSocket(rtc::AsyncUDPSocket::Create( + thread->socketserver(), udp_int_addr)); + server_.AddExternalSocket(rtc::AsyncUDPSocket::Create( + thread->socketserver(), udp_ext_addr)); + + tcp_int_socket_.reset(CreateListenSocket(thread, tcp_int_addr)); + tcp_ext_socket_.reset(CreateListenSocket(thread, tcp_ext_addr)); + ssl_int_socket_.reset(CreateListenSocket(thread, ssl_int_addr)); + ssl_ext_socket_.reset(CreateListenSocket(thread, ssl_ext_addr)); + } + int GetConnectionCount() const { + return server_.GetConnectionCount(); + } + rtc::SocketAddressPair GetConnection(int connection) const { + return server_.GetConnection(connection); + } + bool HasConnection(const rtc::SocketAddress& address) const { + return server_.HasConnection(address); + } + + private: + rtc::AsyncSocket* CreateListenSocket(rtc::Thread* thread, + const rtc::SocketAddress& addr) { + rtc::AsyncSocket* socket = + thread->socketserver()->CreateAsyncSocket(addr.family(), SOCK_STREAM); + socket->Bind(addr); + socket->Listen(5); + socket->SignalReadEvent.connect(this, &TestRelayServer::OnAccept); + return socket; + } + void OnAccept(rtc::AsyncSocket* socket) { + bool external = (socket == tcp_ext_socket_.get() || + socket == ssl_ext_socket_.get()); + bool ssl = (socket == ssl_int_socket_.get() || + socket == ssl_ext_socket_.get()); + rtc::AsyncSocket* raw_socket = socket->Accept(NULL); + if (raw_socket) { + rtc::AsyncTCPSocket* packet_socket = new rtc::AsyncTCPSocket( + (!ssl) ? raw_socket : + new rtc::AsyncSSLServerSocket(raw_socket), false); + if (!external) { + packet_socket->SignalClose.connect(this, + &TestRelayServer::OnInternalClose); + server_.AddInternalSocket(packet_socket); + } else { + packet_socket->SignalClose.connect(this, + &TestRelayServer::OnExternalClose); + server_.AddExternalSocket(packet_socket); + } + } + } + void OnInternalClose(rtc::AsyncPacketSocket* socket, int error) { + server_.RemoveInternalSocket(socket); + } + void OnExternalClose(rtc::AsyncPacketSocket* socket, int error) { + server_.RemoveExternalSocket(socket); + } + private: + cricket::RelayServer server_; + rtc::scoped_ptr tcp_int_socket_; + rtc::scoped_ptr tcp_ext_socket_; + rtc::scoped_ptr ssl_int_socket_; + rtc::scoped_ptr ssl_ext_socket_; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_TESTRELAYSERVER_H_ diff --git a/webrtc/p2p/base/teststunserver.h b/webrtc/p2p/base/teststunserver.h new file mode 100644 index 000000000..a2a8b6fc1 --- /dev/null +++ b/webrtc/p2p/base/teststunserver.h @@ -0,0 +1,58 @@ +/* + * Copyright 2008 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_TESTSTUNSERVER_H_ +#define WEBRTC_P2P_BASE_TESTSTUNSERVER_H_ + +#include "webrtc/p2p/base/stunserver.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/thread.h" + +namespace cricket { + +// A test STUN server. Useful for unit tests. +class TestStunServer : StunServer { + public: + static TestStunServer* Create(rtc::Thread* thread, + const rtc::SocketAddress& addr) { + rtc::AsyncSocket* socket = + thread->socketserver()->CreateAsyncSocket(addr.family(), SOCK_DGRAM); + rtc::AsyncUDPSocket* udp_socket = + rtc::AsyncUDPSocket::Create(socket, addr); + + return new TestStunServer(udp_socket); + } + + // Set a fake STUN address to return to the client. + void set_fake_stun_addr(const rtc::SocketAddress& addr) { + fake_stun_addr_ = addr; + } + + private: + explicit TestStunServer(rtc::AsyncUDPSocket* socket) : StunServer(socket) {} + + void OnBindingRequest(StunMessage* msg, + const rtc::SocketAddress& remote_addr) OVERRIDE { + if (fake_stun_addr_.IsNil()) { + StunServer::OnBindingRequest(msg, remote_addr); + } else { + StunMessage response; + GetStunBindReqponse(msg, fake_stun_addr_, &response); + SendResponse(response, remote_addr); + } + } + + private: + rtc::SocketAddress fake_stun_addr_; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_TESTSTUNSERVER_H_ diff --git a/webrtc/p2p/base/testturnserver.h b/webrtc/p2p/base/testturnserver.h new file mode 100644 index 000000000..19f73e7f8 --- /dev/null +++ b/webrtc/p2p/base/testturnserver.h @@ -0,0 +1,103 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_TESTTURNSERVER_H_ +#define WEBRTC_P2P_BASE_TESTTURNSERVER_H_ + +#include +#include + +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/stun.h" +#include "webrtc/p2p/base/turnserver.h" +#include "webrtc/base/asyncudpsocket.h" +#include "webrtc/base/thread.h" + +namespace cricket { + +static const char kTestRealm[] = "example.org"; +static const char kTestSoftware[] = "TestTurnServer"; + +class TestTurnRedirector : public TurnRedirectInterface { + public: + explicit TestTurnRedirector(const std::vector& addresses) + : alternate_server_addresses_(addresses), + iter_(alternate_server_addresses_.begin()) { + } + + virtual bool ShouldRedirect(const rtc::SocketAddress&, + rtc::SocketAddress* out) { + if (!out || iter_ == alternate_server_addresses_.end()) { + return false; + } + *out = *iter_++; + return true; + } + + private: + const std::vector& alternate_server_addresses_; + std::vector::const_iterator iter_; +}; + +class TestTurnServer : public TurnAuthInterface { + public: + TestTurnServer(rtc::Thread* thread, + const rtc::SocketAddress& udp_int_addr, + const rtc::SocketAddress& udp_ext_addr) + : server_(thread) { + AddInternalSocket(udp_int_addr, cricket::PROTO_UDP); + server_.SetExternalSocketFactory(new rtc::BasicPacketSocketFactory(), + udp_ext_addr); + server_.set_realm(kTestRealm); + server_.set_software(kTestSoftware); + server_.set_auth_hook(this); + } + + void set_enable_otu_nonce(bool enable) { + server_.set_enable_otu_nonce(enable); + } + + TurnServer* server() { return &server_; } + + void set_redirect_hook(TurnRedirectInterface* redirect_hook) { + server_.set_redirect_hook(redirect_hook); + } + + void AddInternalSocket(const rtc::SocketAddress& int_addr, + ProtocolType proto) { + rtc::Thread* thread = rtc::Thread::Current(); + if (proto == cricket::PROTO_UDP) { + server_.AddInternalSocket(rtc::AsyncUDPSocket::Create( + thread->socketserver(), int_addr), proto); + } else if (proto == cricket::PROTO_TCP) { + // For TCP we need to create a server socket which can listen for incoming + // new connections. + rtc::AsyncSocket* socket = + thread->socketserver()->CreateAsyncSocket(SOCK_STREAM); + socket->Bind(int_addr); + socket->Listen(5); + server_.AddInternalServerSocket(socket, proto); + } + } + + private: + // For this test server, succeed if the password is the same as the username. + // Obviously, do not use this in a production environment. + virtual bool GetKey(const std::string& username, const std::string& realm, + std::string* key) { + return ComputeStunCredentialHash(username, realm, username, key); + } + + TurnServer server_; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_TESTTURNSERVER_H_ diff --git a/webrtc/p2p/base/transport.cc b/webrtc/p2p/base/transport.cc new file mode 100644 index 000000000..05c455e4e --- /dev/null +++ b/webrtc/p2p/base/transport.cc @@ -0,0 +1,960 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/transport.h" + +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/parsing.h" +#include "webrtc/p2p/base/port.h" +#include "webrtc/p2p/base/sessionmanager.h" +#include "webrtc/p2p/base/transportchannelimpl.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/base/bind.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" + +namespace cricket { + +using rtc::Bind; + +enum { + MSG_ONSIGNALINGREADY = 1, + MSG_ONREMOTECANDIDATE, + MSG_READSTATE, + MSG_WRITESTATE, + MSG_REQUESTSIGNALING, + MSG_CANDIDATEREADY, + MSG_ROUTECHANGE, + MSG_CONNECTING, + MSG_CANDIDATEALLOCATIONCOMPLETE, + MSG_ROLECONFLICT, + MSG_COMPLETED, + MSG_FAILED, +}; + +struct ChannelParams : public rtc::MessageData { + ChannelParams() : channel(NULL), candidate(NULL) {} + explicit ChannelParams(int component) + : component(component), channel(NULL), candidate(NULL) {} + explicit ChannelParams(Candidate* candidate) + : channel(NULL), candidate(candidate) { + } + + ~ChannelParams() { + delete candidate; + } + + std::string name; + int component; + TransportChannelImpl* channel; + Candidate* candidate; +}; + +static std::string IceProtoToString(TransportProtocol proto) { + std::string proto_str; + switch (proto) { + case ICEPROTO_GOOGLE: + proto_str = "gice"; + break; + case ICEPROTO_HYBRID: + proto_str = "hybrid"; + break; + case ICEPROTO_RFC5245: + proto_str = "ice"; + break; + default: + ASSERT(false); + break; + } + return proto_str; +} + +static bool VerifyIceParams(const TransportDescription& desc) { + // For legacy protocols. + if (desc.ice_ufrag.empty() && desc.ice_pwd.empty()) + return true; + + if (desc.ice_ufrag.length() < ICE_UFRAG_MIN_LENGTH || + desc.ice_ufrag.length() > ICE_UFRAG_MAX_LENGTH) { + return false; + } + if (desc.ice_pwd.length() < ICE_PWD_MIN_LENGTH || + desc.ice_pwd.length() > ICE_PWD_MAX_LENGTH) { + return false; + } + return true; +} + +bool BadTransportDescription(const std::string& desc, std::string* err_desc) { + if (err_desc) { + *err_desc = desc; + } + LOG(LS_ERROR) << desc; + return false; +} + +bool IceCredentialsChanged(const std::string& old_ufrag, + const std::string& old_pwd, + const std::string& new_ufrag, + const std::string& new_pwd) { + // TODO(jiayl): The standard (RFC 5245 Section 9.1.1.1) says that ICE should + // restart when both the ufrag and password are changed, but we do restart + // when either ufrag or passwrod is changed to keep compatible with GICE. We + // should clean this up when GICE is no longer used. + return (old_ufrag != new_ufrag) || (old_pwd != new_pwd); +} + +static bool IceCredentialsChanged(const TransportDescription& old_desc, + const TransportDescription& new_desc) { + return IceCredentialsChanged(old_desc.ice_ufrag, old_desc.ice_pwd, + new_desc.ice_ufrag, new_desc.ice_pwd); +} + +Transport::Transport(rtc::Thread* signaling_thread, + rtc::Thread* worker_thread, + const std::string& content_name, + const std::string& type, + PortAllocator* allocator) + : signaling_thread_(signaling_thread), + worker_thread_(worker_thread), + content_name_(content_name), + type_(type), + allocator_(allocator), + destroyed_(false), + readable_(TRANSPORT_STATE_NONE), + writable_(TRANSPORT_STATE_NONE), + was_writable_(false), + connect_requested_(false), + ice_role_(ICEROLE_UNKNOWN), + tiebreaker_(0), + protocol_(ICEPROTO_HYBRID), + remote_ice_mode_(ICEMODE_FULL) { +} + +Transport::~Transport() { + ASSERT(signaling_thread_->IsCurrent()); + ASSERT(destroyed_); +} + +void Transport::SetIceRole(IceRole role) { + worker_thread_->Invoke(Bind(&Transport::SetIceRole_w, this, role)); +} + +void Transport::SetIdentity(rtc::SSLIdentity* identity) { + worker_thread_->Invoke(Bind(&Transport::SetIdentity_w, this, identity)); +} + +bool Transport::GetIdentity(rtc::SSLIdentity** identity) { + // The identity is set on the worker thread, so for safety it must also be + // acquired on the worker thread. + return worker_thread_->Invoke( + Bind(&Transport::GetIdentity_w, this, identity)); +} + +bool Transport::GetRemoteCertificate(rtc::SSLCertificate** cert) { + // Channels can be deleted on the worker thread, so for safety the remote + // certificate is acquired on the worker thread. + return worker_thread_->Invoke( + Bind(&Transport::GetRemoteCertificate_w, this, cert)); +} + +bool Transport::GetRemoteCertificate_w(rtc::SSLCertificate** cert) { + ASSERT(worker_thread()->IsCurrent()); + if (channels_.empty()) + return false; + + ChannelMap::iterator iter = channels_.begin(); + return iter->second->GetRemoteCertificate(cert); +} + +bool Transport::SetLocalTransportDescription( + const TransportDescription& description, + ContentAction action, + std::string* error_desc) { + return worker_thread_->Invoke(Bind( + &Transport::SetLocalTransportDescription_w, this, + description, action, error_desc)); +} + +bool Transport::SetRemoteTransportDescription( + const TransportDescription& description, + ContentAction action, + std::string* error_desc) { + return worker_thread_->Invoke(Bind( + &Transport::SetRemoteTransportDescription_w, this, + description, action, error_desc)); +} + +TransportChannelImpl* Transport::CreateChannel(int component) { + return worker_thread_->Invoke(Bind( + &Transport::CreateChannel_w, this, component)); +} + +TransportChannelImpl* Transport::CreateChannel_w(int component) { + ASSERT(worker_thread()->IsCurrent()); + TransportChannelImpl *impl; + rtc::CritScope cs(&crit_); + + // Create the entry if it does not exist. + bool impl_exists = false; + if (channels_.find(component) == channels_.end()) { + impl = CreateTransportChannel(component); + channels_[component] = ChannelMapEntry(impl); + } else { + impl = channels_[component].get(); + impl_exists = true; + } + + // Increase the ref count. + channels_[component].AddRef(); + destroyed_ = false; + + if (impl_exists) { + // If this is an existing channel, we should just return it without + // connecting to all the signal again. + return impl; + } + + // Push down our transport state to the new channel. + impl->SetIceRole(ice_role_); + impl->SetIceTiebreaker(tiebreaker_); + // TODO(ronghuawu): Change CreateChannel_w to be able to return error since + // below Apply**Description_w calls can fail. + if (local_description_) + ApplyLocalTransportDescription_w(impl, NULL); + if (remote_description_) + ApplyRemoteTransportDescription_w(impl, NULL); + if (local_description_ && remote_description_) + ApplyNegotiatedTransportDescription_w(impl, NULL); + + impl->SignalReadableState.connect(this, &Transport::OnChannelReadableState); + impl->SignalWritableState.connect(this, &Transport::OnChannelWritableState); + impl->SignalRequestSignaling.connect( + this, &Transport::OnChannelRequestSignaling); + impl->SignalCandidateReady.connect(this, &Transport::OnChannelCandidateReady); + impl->SignalRouteChange.connect(this, &Transport::OnChannelRouteChange); + impl->SignalCandidatesAllocationDone.connect( + this, &Transport::OnChannelCandidatesAllocationDone); + impl->SignalRoleConflict.connect(this, &Transport::OnRoleConflict); + impl->SignalConnectionRemoved.connect( + this, &Transport::OnChannelConnectionRemoved); + + if (connect_requested_) { + impl->Connect(); + if (channels_.size() == 1) { + // If this is the first channel, then indicate that we have started + // connecting. + signaling_thread()->Post(this, MSG_CONNECTING, NULL); + } + } + return impl; +} + +TransportChannelImpl* Transport::GetChannel(int component) { + rtc::CritScope cs(&crit_); + ChannelMap::iterator iter = channels_.find(component); + return (iter != channels_.end()) ? iter->second.get() : NULL; +} + +bool Transport::HasChannels() { + rtc::CritScope cs(&crit_); + return !channels_.empty(); +} + +void Transport::DestroyChannel(int component) { + worker_thread_->Invoke(Bind( + &Transport::DestroyChannel_w, this, component)); +} + +void Transport::DestroyChannel_w(int component) { + ASSERT(worker_thread()->IsCurrent()); + + TransportChannelImpl* impl = NULL; + { + rtc::CritScope cs(&crit_); + ChannelMap::iterator iter = channels_.find(component); + if (iter == channels_.end()) + return; + + iter->second.DecRef(); + if (!iter->second.ref()) { + impl = iter->second.get(); + channels_.erase(iter); + } + } + + if (connect_requested_ && channels_.empty()) { + // We're no longer attempting to connect. + signaling_thread()->Post(this, MSG_CONNECTING, NULL); + } + + if (impl) { + // Check in case the deleted channel was the only non-writable channel. + OnChannelWritableState(impl); + DestroyTransportChannel(impl); + } +} + +void Transport::ConnectChannels() { + ASSERT(signaling_thread()->IsCurrent()); + worker_thread_->Invoke(Bind(&Transport::ConnectChannels_w, this)); +} + +void Transport::ConnectChannels_w() { + ASSERT(worker_thread()->IsCurrent()); + if (connect_requested_ || channels_.empty()) + return; + connect_requested_ = true; + signaling_thread()->Post( + this, MSG_CANDIDATEREADY, NULL); + + if (!local_description_) { + // TOOD(mallinath) : TransportDescription(TD) shouldn't be generated here. + // As Transport must know TD is offer or answer and cricket::Transport + // doesn't have the capability to decide it. This should be set by the + // Session. + // Session must generate local TD before remote candidates pushed when + // initiate request initiated by the remote. + LOG(LS_INFO) << "Transport::ConnectChannels_w: No local description has " + << "been set. Will generate one."; + TransportDescription desc(NS_GINGLE_P2P, std::vector(), + rtc::CreateRandomString(ICE_UFRAG_LENGTH), + rtc::CreateRandomString(ICE_PWD_LENGTH), + ICEMODE_FULL, CONNECTIONROLE_NONE, NULL, + Candidates()); + SetLocalTransportDescription_w(desc, CA_OFFER, NULL); + } + + CallChannels_w(&TransportChannelImpl::Connect); + if (!channels_.empty()) { + signaling_thread()->Post(this, MSG_CONNECTING, NULL); + } +} + +void Transport::OnConnecting_s() { + ASSERT(signaling_thread()->IsCurrent()); + SignalConnecting(this); +} + +void Transport::DestroyAllChannels() { + ASSERT(signaling_thread()->IsCurrent()); + worker_thread_->Invoke( + Bind(&Transport::DestroyAllChannels_w, this)); + worker_thread()->Clear(this); + signaling_thread()->Clear(this); + destroyed_ = true; +} + +void Transport::DestroyAllChannels_w() { + ASSERT(worker_thread()->IsCurrent()); + std::vector impls; + { + rtc::CritScope cs(&crit_); + for (ChannelMap::iterator iter = channels_.begin(); + iter != channels_.end(); + ++iter) { + iter->second.DecRef(); + if (!iter->second.ref()) + impls.push_back(iter->second.get()); + } + } + channels_.clear(); + + + for (size_t i = 0; i < impls.size(); ++i) + DestroyTransportChannel(impls[i]); +} + +void Transport::ResetChannels() { + ASSERT(signaling_thread()->IsCurrent()); + worker_thread_->Invoke(Bind(&Transport::ResetChannels_w, this)); +} + +void Transport::ResetChannels_w() { + ASSERT(worker_thread()->IsCurrent()); + + // We are no longer attempting to connect + connect_requested_ = false; + + // Clear out the old messages, they aren't relevant + rtc::CritScope cs(&crit_); + ready_candidates_.clear(); + + // Reset all of the channels + CallChannels_w(&TransportChannelImpl::Reset); +} + +void Transport::OnSignalingReady() { + ASSERT(signaling_thread()->IsCurrent()); + if (destroyed_) return; + + worker_thread()->Post(this, MSG_ONSIGNALINGREADY, NULL); + + // Notify the subclass. + OnTransportSignalingReady(); +} + +void Transport::CallChannels_w(TransportChannelFunc func) { + ASSERT(worker_thread()->IsCurrent()); + rtc::CritScope cs(&crit_); + for (ChannelMap::iterator iter = channels_.begin(); + iter != channels_.end(); + ++iter) { + ((iter->second.get())->*func)(); + } +} + +bool Transport::VerifyCandidate(const Candidate& cand, std::string* error) { + // No address zero. + if (cand.address().IsNil() || cand.address().IsAny()) { + *error = "candidate has address of zero"; + return false; + } + + // Disallow all ports below 1024, except for 80 and 443 on public addresses. + int port = cand.address().port(); + if (cand.protocol() == TCP_PROTOCOL_NAME && + (cand.tcptype() == TCPTYPE_ACTIVE_STR || port == 0)) { + // Expected for active-only candidates per + // http://tools.ietf.org/html/rfc6544#section-4.5 so no error. + // Libjingle clients emit port 0, in "active" mode. + return true; + } + if (port < 1024) { + if ((port != 80) && (port != 443)) { + *error = "candidate has port below 1024, but not 80 or 443"; + return false; + } + + if (cand.address().IsPrivateIP()) { + *error = "candidate has port of 80 or 443 with private IP address"; + return false; + } + } + + return true; +} + + +bool Transport::GetStats(TransportStats* stats) { + ASSERT(signaling_thread()->IsCurrent()); + return worker_thread_->Invoke(Bind( + &Transport::GetStats_w, this, stats)); +} + +bool Transport::GetStats_w(TransportStats* stats) { + ASSERT(worker_thread()->IsCurrent()); + stats->content_name = content_name(); + stats->channel_stats.clear(); + for (ChannelMap::iterator iter = channels_.begin(); + iter != channels_.end(); + ++iter) { + TransportChannelStats substats; + substats.component = iter->second->component(); + if (!iter->second->GetStats(&substats.connection_infos)) { + return false; + } + stats->channel_stats.push_back(substats); + } + return true; +} + +bool Transport::GetSslRole(rtc::SSLRole* ssl_role) const { + return worker_thread_->Invoke(Bind( + &Transport::GetSslRole_w, this, ssl_role)); +} + +void Transport::OnRemoteCandidates(const std::vector& candidates) { + for (std::vector::const_iterator iter = candidates.begin(); + iter != candidates.end(); + ++iter) { + OnRemoteCandidate(*iter); + } +} + +void Transport::OnRemoteCandidate(const Candidate& candidate) { + ASSERT(signaling_thread()->IsCurrent()); + if (destroyed_) return; + + if (!HasChannel(candidate.component())) { + LOG(LS_WARNING) << "Ignoring candidate for unknown component " + << candidate.component(); + return; + } + + ChannelParams* params = new ChannelParams(new Candidate(candidate)); + worker_thread()->Post(this, MSG_ONREMOTECANDIDATE, params); +} + +void Transport::OnRemoteCandidate_w(const Candidate& candidate) { + ASSERT(worker_thread()->IsCurrent()); + ChannelMap::iterator iter = channels_.find(candidate.component()); + // It's ok for a channel to go away while this message is in transit. + if (iter != channels_.end()) { + iter->second->OnCandidate(candidate); + } +} + +void Transport::OnChannelReadableState(TransportChannel* channel) { + ASSERT(worker_thread()->IsCurrent()); + signaling_thread()->Post(this, MSG_READSTATE, NULL); +} + +void Transport::OnChannelReadableState_s() { + ASSERT(signaling_thread()->IsCurrent()); + TransportState readable = GetTransportState_s(true); + if (readable_ != readable) { + readable_ = readable; + SignalReadableState(this); + } +} + +void Transport::OnChannelWritableState(TransportChannel* channel) { + ASSERT(worker_thread()->IsCurrent()); + signaling_thread()->Post(this, MSG_WRITESTATE, NULL); + + MaybeCompleted_w(); +} + +void Transport::OnChannelWritableState_s() { + ASSERT(signaling_thread()->IsCurrent()); + TransportState writable = GetTransportState_s(false); + if (writable_ != writable) { + was_writable_ = (writable_ == TRANSPORT_STATE_ALL); + writable_ = writable; + SignalWritableState(this); + } +} + +TransportState Transport::GetTransportState_s(bool read) { + ASSERT(signaling_thread()->IsCurrent()); + rtc::CritScope cs(&crit_); + bool any = false; + bool all = !channels_.empty(); + for (ChannelMap::iterator iter = channels_.begin(); + iter != channels_.end(); + ++iter) { + bool b = (read ? iter->second->readable() : + iter->second->writable()); + any = any || b; + all = all && b; + } + if (all) { + return TRANSPORT_STATE_ALL; + } else if (any) { + return TRANSPORT_STATE_SOME; + } else { + return TRANSPORT_STATE_NONE; + } +} + +void Transport::OnChannelRequestSignaling(TransportChannelImpl* channel) { + ASSERT(worker_thread()->IsCurrent()); + ChannelParams* params = new ChannelParams(channel->component()); + signaling_thread()->Post(this, MSG_REQUESTSIGNALING, params); +} + +void Transport::OnChannelRequestSignaling_s(int component) { + ASSERT(signaling_thread()->IsCurrent()); + LOG(LS_INFO) << "Transport: " << content_name_ << ", allocating candidates"; + // Resetting ICE state for the channel. + { + rtc::CritScope cs(&crit_); + ChannelMap::iterator iter = channels_.find(component); + if (iter != channels_.end()) + iter->second.set_candidates_allocated(false); + } + SignalRequestSignaling(this); +} + +void Transport::OnChannelCandidateReady(TransportChannelImpl* channel, + const Candidate& candidate) { + ASSERT(worker_thread()->IsCurrent()); + rtc::CritScope cs(&crit_); + ready_candidates_.push_back(candidate); + + // We hold any messages until the client lets us connect. + if (connect_requested_) { + signaling_thread()->Post( + this, MSG_CANDIDATEREADY, NULL); + } +} + +void Transport::OnChannelCandidateReady_s() { + ASSERT(signaling_thread()->IsCurrent()); + ASSERT(connect_requested_); + + std::vector candidates; + { + rtc::CritScope cs(&crit_); + candidates.swap(ready_candidates_); + } + + // we do the deleting of Candidate* here to keep the new above and + // delete below close to each other + if (!candidates.empty()) { + SignalCandidatesReady(this, candidates); + } +} + +void Transport::OnChannelRouteChange(TransportChannel* channel, + const Candidate& remote_candidate) { + ASSERT(worker_thread()->IsCurrent()); + ChannelParams* params = new ChannelParams(new Candidate(remote_candidate)); + params->channel = static_cast(channel); + signaling_thread()->Post(this, MSG_ROUTECHANGE, params); +} + +void Transport::OnChannelRouteChange_s(const TransportChannel* channel, + const Candidate& remote_candidate) { + ASSERT(signaling_thread()->IsCurrent()); + SignalRouteChange(this, remote_candidate.component(), remote_candidate); +} + +void Transport::OnChannelCandidatesAllocationDone( + TransportChannelImpl* channel) { + ASSERT(worker_thread()->IsCurrent()); + rtc::CritScope cs(&crit_); + ChannelMap::iterator iter = channels_.find(channel->component()); + ASSERT(iter != channels_.end()); + LOG(LS_INFO) << "Transport: " << content_name_ << ", component " + << channel->component() << " allocation complete"; + iter->second.set_candidates_allocated(true); + + // If all channels belonging to this Transport got signal, then + // forward this signal to upper layer. + // Can this signal arrive before all transport channels are created? + for (iter = channels_.begin(); iter != channels_.end(); ++iter) { + if (!iter->second.candidates_allocated()) + return; + } + signaling_thread_->Post(this, MSG_CANDIDATEALLOCATIONCOMPLETE); + + MaybeCompleted_w(); +} + +void Transport::OnChannelCandidatesAllocationDone_s() { + ASSERT(signaling_thread()->IsCurrent()); + LOG(LS_INFO) << "Transport: " << content_name_ << " allocation complete"; + SignalCandidatesAllocationDone(this); +} + +void Transport::OnRoleConflict(TransportChannelImpl* channel) { + signaling_thread_->Post(this, MSG_ROLECONFLICT); +} + +void Transport::OnChannelConnectionRemoved(TransportChannelImpl* channel) { + ASSERT(worker_thread()->IsCurrent()); + MaybeCompleted_w(); + + // Check if the state is now Failed. + // Failed is only available in the Controlling ICE role. + if (channel->GetIceRole() != ICEROLE_CONTROLLING) { + return; + } + + ChannelMap::iterator iter = channels_.find(channel->component()); + ASSERT(iter != channels_.end()); + // Failed can only occur after candidate allocation has stopped. + if (!iter->second.candidates_allocated()) { + return; + } + + size_t connections = channel->GetConnectionCount(); + if (connections == 0) { + // A Transport has failed if any of its channels have no remaining + // connections. + signaling_thread_->Post(this, MSG_FAILED); + } +} + +void Transport::MaybeCompleted_w() { + ASSERT(worker_thread()->IsCurrent()); + + // A Transport's ICE process is completed if all of its channels are writable, + // have finished allocating candidates, and have pruned all but one of their + // connections. + ChannelMap::const_iterator iter; + for (iter = channels_.begin(); iter != channels_.end(); ++iter) { + const TransportChannelImpl* channel = iter->second.get(); + if (!(channel->writable() && + channel->GetConnectionCount() == 1 && + channel->GetIceRole() == ICEROLE_CONTROLLING && + iter->second.candidates_allocated())) { + return; + } + } + + signaling_thread_->Post(this, MSG_COMPLETED); +} + +void Transport::SetIceRole_w(IceRole role) { + rtc::CritScope cs(&crit_); + ice_role_ = role; + for (ChannelMap::iterator iter = channels_.begin(); + iter != channels_.end(); ++iter) { + iter->second->SetIceRole(ice_role_); + } +} + +void Transport::SetRemoteIceMode_w(IceMode mode) { + rtc::CritScope cs(&crit_); + remote_ice_mode_ = mode; + // Shouldn't channels be created after this method executed? + for (ChannelMap::iterator iter = channels_.begin(); + iter != channels_.end(); ++iter) { + iter->second->SetRemoteIceMode(remote_ice_mode_); + } +} + +bool Transport::SetLocalTransportDescription_w( + const TransportDescription& desc, + ContentAction action, + std::string* error_desc) { + bool ret = true; + rtc::CritScope cs(&crit_); + + if (!VerifyIceParams(desc)) { + return BadTransportDescription("Invalid ice-ufrag or ice-pwd length", + error_desc); + } + + if (local_description_ && IceCredentialsChanged(*local_description_, desc)) { + IceRole new_ice_role = (action == CA_OFFER) ? ICEROLE_CONTROLLING + : ICEROLE_CONTROLLED; + + // It must be called before ApplyLocalTransportDescription_w, which may + // trigger an ICE restart and depends on the new ICE role. + SetIceRole_w(new_ice_role); + } + + local_description_.reset(new TransportDescription(desc)); + + for (ChannelMap::iterator iter = channels_.begin(); + iter != channels_.end(); ++iter) { + ret &= ApplyLocalTransportDescription_w(iter->second.get(), error_desc); + } + if (!ret) + return false; + + // If PRANSWER/ANSWER is set, we should decide transport protocol type. + if (action == CA_PRANSWER || action == CA_ANSWER) { + ret &= NegotiateTransportDescription_w(action, error_desc); + } + return ret; +} + +bool Transport::SetRemoteTransportDescription_w( + const TransportDescription& desc, + ContentAction action, + std::string* error_desc) { + bool ret = true; + rtc::CritScope cs(&crit_); + + if (!VerifyIceParams(desc)) { + return BadTransportDescription("Invalid ice-ufrag or ice-pwd length", + error_desc); + } + + remote_description_.reset(new TransportDescription(desc)); + for (ChannelMap::iterator iter = channels_.begin(); + iter != channels_.end(); ++iter) { + ret &= ApplyRemoteTransportDescription_w(iter->second.get(), error_desc); + } + + // If PRANSWER/ANSWER is set, we should decide transport protocol type. + if (action == CA_PRANSWER || action == CA_ANSWER) { + ret = NegotiateTransportDescription_w(CA_OFFER, error_desc); + } + return ret; +} + +bool Transport::ApplyLocalTransportDescription_w(TransportChannelImpl* ch, + std::string* error_desc) { + // If existing protocol_type is HYBRID, we may have not chosen the final + // protocol type, so update the channel protocol type from the + // local description. Otherwise, skip updating the protocol type. + // We check for HYBRID to avoid accidental changes; in the case of a + // session renegotiation, the new offer will have the google-ice ICE option, + // so we need to make sure we don't switch back from ICE mode to HYBRID + // when this happens. + // There are some other ways we could have solved this, but this is the + // simplest. The ultimate solution will be to get rid of GICE altogether. + IceProtocolType protocol_type; + if (ch->GetIceProtocolType(&protocol_type) && + protocol_type == ICEPROTO_HYBRID) { + ch->SetIceProtocolType( + TransportProtocolFromDescription(local_description())); + } + ch->SetIceCredentials(local_description_->ice_ufrag, + local_description_->ice_pwd); + return true; +} + +bool Transport::ApplyRemoteTransportDescription_w(TransportChannelImpl* ch, + std::string* error_desc) { + ch->SetRemoteIceCredentials(remote_description_->ice_ufrag, + remote_description_->ice_pwd); + return true; +} + +bool Transport::ApplyNegotiatedTransportDescription_w( + TransportChannelImpl* channel, std::string* error_desc) { + channel->SetIceProtocolType(protocol_); + channel->SetRemoteIceMode(remote_ice_mode_); + return true; +} + +bool Transport::NegotiateTransportDescription_w(ContentAction local_role, + std::string* error_desc) { + // TODO(ekr@rtfm.com): This is ICE-specific stuff. Refactor into + // P2PTransport. + const TransportDescription* offer; + const TransportDescription* answer; + + if (local_role == CA_OFFER) { + offer = local_description_.get(); + answer = remote_description_.get(); + } else { + offer = remote_description_.get(); + answer = local_description_.get(); + } + + TransportProtocol offer_proto = TransportProtocolFromDescription(offer); + TransportProtocol answer_proto = TransportProtocolFromDescription(answer); + + // If offered protocol is gice/ice, then we expect to receive matching + // protocol in answer, anything else is treated as an error. + // HYBRID is not an option when offered specific protocol. + // If offered protocol is HYBRID and answered protocol is HYBRID then + // gice is preferred protocol. + // TODO(mallinath) - Answer from local or remote should't have both ice + // and gice support. It should always pick which protocol it wants to use. + // Once WebRTC stops supporting gice (for backward compatibility), HYBRID in + // answer must be treated as error. + if ((offer_proto == ICEPROTO_GOOGLE || offer_proto == ICEPROTO_RFC5245) && + (offer_proto != answer_proto)) { + std::ostringstream desc; + desc << "Offer and answer protocol mismatch: " + << IceProtoToString(offer_proto) + << " vs " + << IceProtoToString(answer_proto); + return BadTransportDescription(desc.str(), error_desc); + } + protocol_ = answer_proto == ICEPROTO_HYBRID ? ICEPROTO_GOOGLE : answer_proto; + + // If transport is in ICEROLE_CONTROLLED and remote end point supports only + // ice_lite, this local end point should take CONTROLLING role. + if (ice_role_ == ICEROLE_CONTROLLED && + remote_description_->ice_mode == ICEMODE_LITE) { + SetIceRole_w(ICEROLE_CONTROLLING); + } + + // Update remote ice_mode to all existing channels. + remote_ice_mode_ = remote_description_->ice_mode; + + // Now that we have negotiated everything, push it downward. + // Note that we cache the result so that if we have race conditions + // between future SetRemote/SetLocal invocations and new channel + // creation, we have the negotiation state saved until a new + // negotiation happens. + for (ChannelMap::iterator iter = channels_.begin(); + iter != channels_.end(); + ++iter) { + if (!ApplyNegotiatedTransportDescription_w(iter->second.get(), error_desc)) + return false; + } + return true; +} + +void Transport::OnMessage(rtc::Message* msg) { + switch (msg->message_id) { + case MSG_ONSIGNALINGREADY: + CallChannels_w(&TransportChannelImpl::OnSignalingReady); + break; + case MSG_ONREMOTECANDIDATE: { + ChannelParams* params = static_cast(msg->pdata); + OnRemoteCandidate_w(*params->candidate); + delete params; + } + break; + case MSG_CONNECTING: + OnConnecting_s(); + break; + case MSG_READSTATE: + OnChannelReadableState_s(); + break; + case MSG_WRITESTATE: + OnChannelWritableState_s(); + break; + case MSG_REQUESTSIGNALING: { + ChannelParams* params = static_cast(msg->pdata); + OnChannelRequestSignaling_s(params->component); + delete params; + } + break; + case MSG_CANDIDATEREADY: + OnChannelCandidateReady_s(); + break; + case MSG_ROUTECHANGE: { + ChannelParams* params = static_cast(msg->pdata); + OnChannelRouteChange_s(params->channel, *params->candidate); + delete params; + } + break; + case MSG_CANDIDATEALLOCATIONCOMPLETE: + OnChannelCandidatesAllocationDone_s(); + break; + case MSG_ROLECONFLICT: + SignalRoleConflict(); + break; + case MSG_COMPLETED: + SignalCompleted(this); + break; + case MSG_FAILED: + SignalFailed(this); + break; + } +} + +bool TransportParser::ParseAddress(const buzz::XmlElement* elem, + const buzz::QName& address_name, + const buzz::QName& port_name, + rtc::SocketAddress* address, + ParseError* error) { + if (!elem->HasAttr(address_name)) + return BadParse("address does not have " + address_name.LocalPart(), error); + if (!elem->HasAttr(port_name)) + return BadParse("address does not have " + port_name.LocalPart(), error); + + address->SetIP(elem->Attr(address_name)); + std::istringstream ist(elem->Attr(port_name)); + int port = 0; + ist >> port; + address->SetPort(port); + + return true; +} + +// We're GICE if the namespace is NS_GOOGLE_P2P, or if NS_JINGLE_ICE_UDP is +// used and the GICE ice-option is set. +TransportProtocol TransportProtocolFromDescription( + const TransportDescription* desc) { + ASSERT(desc != NULL); + if (desc->transport_type == NS_JINGLE_ICE_UDP) { + return (desc->HasOption(ICE_OPTION_GICE)) ? + ICEPROTO_HYBRID : ICEPROTO_RFC5245; + } + return ICEPROTO_GOOGLE; +} + +} // namespace cricket diff --git a/webrtc/p2p/base/transport.h b/webrtc/p2p/base/transport.h new file mode 100644 index 000000000..ab772fe54 --- /dev/null +++ b/webrtc/p2p/base/transport.h @@ -0,0 +1,513 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// A Transport manages a set of named channels of the same type. +// +// Subclasses choose the appropriate class to instantiate for each channel; +// however, this base class keeps track of the channels by name, watches their +// state changes (in order to update the manager's state), and forwards +// requests to begin connecting or to reset to each of the channels. +// +// On Threading: Transport performs work on both the signaling and worker +// threads. For subclasses, the rule is that all signaling related calls will +// be made on the signaling thread and all channel related calls (including +// signaling for a channel) will be made on the worker thread. When +// information needs to be sent between the two threads, this class should do +// the work (e.g., OnRemoteCandidate). +// +// Note: Subclasses must call DestroyChannels() in their own constructors. +// It is not possible to do so here because the subclass constructor will +// already have run. + +#ifndef WEBRTC_P2P_BASE_TRANSPORT_H_ +#define WEBRTC_P2P_BASE_TRANSPORT_H_ + +#include +#include +#include +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/sessiondescription.h" +#include "webrtc/p2p/base/transportinfo.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/messagequeue.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/sslstreamadapter.h" + +namespace rtc { +class Thread; +} + +namespace buzz { +class QName; +class XmlElement; +} + +namespace cricket { + +struct ParseError; +struct WriteError; +class CandidateTranslator; +class PortAllocator; +class SessionManager; +class Session; +class TransportChannel; +class TransportChannelImpl; + +typedef std::vector XmlElements; +typedef std::vector Candidates; + +// Used to parse and serialize (write) transport candidates. For +// convenience of old code, Transports will implement TransportParser. +// Parse/Write seems better than Serialize/Deserialize or +// Create/Translate. +class TransportParser { + public: + // The incoming Translator value may be null, in which case + // ParseCandidates should return false if there are candidates to + // parse (indicating a failure to parse). If the Translator is null + // and there are no candidates to parse, then return true, + // indicating a successful parse of 0 candidates. + + // Parse or write a transport description, including ICE credentials and + // any DTLS fingerprint. Since only Jingle has transport descriptions, these + // functions are only used when serializing to Jingle. + virtual bool ParseTransportDescription(const buzz::XmlElement* elem, + const CandidateTranslator* translator, + TransportDescription* tdesc, + ParseError* error) = 0; + virtual bool WriteTransportDescription(const TransportDescription& tdesc, + const CandidateTranslator* translator, + buzz::XmlElement** tdesc_elem, + WriteError* error) = 0; + + + // Parse a single candidate. This must be used when parsing Gingle + // candidates, since there is no enclosing transport description. + virtual bool ParseGingleCandidate(const buzz::XmlElement* elem, + const CandidateTranslator* translator, + Candidate* candidates, + ParseError* error) = 0; + virtual bool WriteGingleCandidate(const Candidate& candidate, + const CandidateTranslator* translator, + buzz::XmlElement** candidate_elem, + WriteError* error) = 0; + + // Helper function to parse an element describing an address. This + // retrieves the IP and port from the given element and verifies + // that they look like plausible values. + bool ParseAddress(const buzz::XmlElement* elem, + const buzz::QName& address_name, + const buzz::QName& port_name, + rtc::SocketAddress* address, + ParseError* error); + + virtual ~TransportParser() {} +}; + +// For "writable" and "readable", we need to differentiate between +// none, all, and some. +enum TransportState { + TRANSPORT_STATE_NONE = 0, + TRANSPORT_STATE_SOME, + TRANSPORT_STATE_ALL +}; + +// Stats that we can return about the connections for a transport channel. +// TODO(hta): Rename to ConnectionStats +struct ConnectionInfo { + ConnectionInfo() + : best_connection(false), + writable(false), + readable(false), + timeout(false), + new_connection(false), + rtt(0), + sent_total_bytes(0), + sent_bytes_second(0), + recv_total_bytes(0), + recv_bytes_second(0), + key(NULL) {} + + bool best_connection; // Is this the best connection we have? + bool writable; // Has this connection received a STUN response? + bool readable; // Has this connection received a STUN request? + bool timeout; // Has this connection timed out? + bool new_connection; // Is this a newly created connection? + size_t rtt; // The STUN RTT for this connection. + size_t sent_total_bytes; // Total bytes sent on this connection. + size_t sent_bytes_second; // Bps over the last measurement interval. + size_t recv_total_bytes; // Total bytes received on this connection. + size_t recv_bytes_second; // Bps over the last measurement interval. + Candidate local_candidate; // The local candidate for this connection. + Candidate remote_candidate; // The remote candidate for this connection. + void* key; // A static value that identifies this conn. +}; + +// Information about all the connections of a channel. +typedef std::vector ConnectionInfos; + +// Information about a specific channel +struct TransportChannelStats { + int component; + ConnectionInfos connection_infos; +}; + +// Information about all the channels of a transport. +// TODO(hta): Consider if a simple vector is as good as a map. +typedef std::vector TransportChannelStatsList; + +// Information about the stats of a transport. +struct TransportStats { + std::string content_name; + TransportChannelStatsList channel_stats; +}; + +bool BadTransportDescription(const std::string& desc, std::string* err_desc); + +bool IceCredentialsChanged(const std::string& old_ufrag, + const std::string& old_pwd, + const std::string& new_ufrag, + const std::string& new_pwd); + +class Transport : public rtc::MessageHandler, + public sigslot::has_slots<> { + public: + Transport(rtc::Thread* signaling_thread, + rtc::Thread* worker_thread, + const std::string& content_name, + const std::string& type, + PortAllocator* allocator); + virtual ~Transport(); + + // Returns the signaling thread. The app talks to Transport on this thread. + rtc::Thread* signaling_thread() { return signaling_thread_; } + // Returns the worker thread. The actual networking is done on this thread. + rtc::Thread* worker_thread() { return worker_thread_; } + + // Returns the content_name of this transport. + const std::string& content_name() const { return content_name_; } + // Returns the type of this transport. + const std::string& type() const { return type_; } + + // Returns the port allocator object for this transport. + PortAllocator* port_allocator() { return allocator_; } + + // Returns the readable and states of this manager. These bits are the ORs + // of the corresponding bits on the managed channels. Each time one of these + // states changes, a signal is raised. + // TODO: Replace uses of readable() and writable() with + // any_channels_readable() and any_channels_writable(). + bool readable() const { return any_channels_readable(); } + bool writable() const { return any_channels_writable(); } + bool was_writable() const { return was_writable_; } + bool any_channels_readable() const { + return (readable_ == TRANSPORT_STATE_SOME || + readable_ == TRANSPORT_STATE_ALL); + } + bool any_channels_writable() const { + return (writable_ == TRANSPORT_STATE_SOME || + writable_ == TRANSPORT_STATE_ALL); + } + bool all_channels_readable() const { + return (readable_ == TRANSPORT_STATE_ALL); + } + bool all_channels_writable() const { + return (writable_ == TRANSPORT_STATE_ALL); + } + sigslot::signal1 SignalReadableState; + sigslot::signal1 SignalWritableState; + sigslot::signal1 SignalCompleted; + sigslot::signal1 SignalFailed; + + // Returns whether the client has requested the channels to connect. + bool connect_requested() const { return connect_requested_; } + + void SetIceRole(IceRole role); + IceRole ice_role() const { return ice_role_; } + + void SetIceTiebreaker(uint64 IceTiebreaker) { tiebreaker_ = IceTiebreaker; } + uint64 IceTiebreaker() { return tiebreaker_; } + + // Must be called before applying local session description. + void SetIdentity(rtc::SSLIdentity* identity); + + // Get a copy of the local identity provided by SetIdentity. + bool GetIdentity(rtc::SSLIdentity** identity); + + // Get a copy of the remote certificate in use by the specified channel. + bool GetRemoteCertificate(rtc::SSLCertificate** cert); + + TransportProtocol protocol() const { return protocol_; } + + // Create, destroy, and lookup the channels of this type by their components. + TransportChannelImpl* CreateChannel(int component); + // Note: GetChannel may lead to race conditions, since the mutex is not held + // after the pointer is returned. + TransportChannelImpl* GetChannel(int component); + // Note: HasChannel does not lead to race conditions, unlike GetChannel. + bool HasChannel(int component) { + return (NULL != GetChannel(component)); + } + bool HasChannels(); + void DestroyChannel(int component); + + // Set the local TransportDescription to be used by TransportChannels. + // This should be called before ConnectChannels(). + bool SetLocalTransportDescription(const TransportDescription& description, + ContentAction action, + std::string* error_desc); + + // Set the remote TransportDescription to be used by TransportChannels. + bool SetRemoteTransportDescription(const TransportDescription& description, + ContentAction action, + std::string* error_desc); + + // Tells all current and future channels to start connecting. When the first + // channel begins connecting, the following signal is raised. + void ConnectChannels(); + sigslot::signal1 SignalConnecting; + + // Resets all of the channels back to their initial state. They are no + // longer connecting. + void ResetChannels(); + + // Destroys every channel created so far. + void DestroyAllChannels(); + + bool GetStats(TransportStats* stats); + + // Before any stanza is sent, the manager will request signaling. Once + // signaling is available, the client should call OnSignalingReady. Once + // this occurs, the transport (or its channels) can send any waiting stanzas. + // OnSignalingReady invokes OnTransportSignalingReady and then forwards this + // signal to each channel. + sigslot::signal1 SignalRequestSignaling; + void OnSignalingReady(); + + // Handles sending of ready candidates and receiving of remote candidates. + sigslot::signal2&> SignalCandidatesReady; + + sigslot::signal1 SignalCandidatesAllocationDone; + void OnRemoteCandidates(const std::vector& candidates); + + // If candidate is not acceptable, returns false and sets error. + // Call this before calling OnRemoteCandidates. + virtual bool VerifyCandidate(const Candidate& candidate, + std::string* error); + + // Signals when the best connection for a channel changes. + sigslot::signal3 SignalRouteChange; + + // A transport message has generated an transport-specific error. The + // stanza that caused the error is available in session_msg. If false is + // returned, the error is considered unrecoverable, and the session is + // terminated. + // TODO(juberti): Remove these obsolete functions once Session no longer + // references them. + virtual void OnTransportError(const buzz::XmlElement* error) {} + sigslot::signal6 + SignalTransportError; + + // Forwards the signal from TransportChannel to BaseSession. + sigslot::signal0<> SignalRoleConflict; + + virtual bool GetSslRole(rtc::SSLRole* ssl_role) const; + + protected: + // These are called by Create/DestroyChannel above in order to create or + // destroy the appropriate type of channel. + virtual TransportChannelImpl* CreateTransportChannel(int component) = 0; + virtual void DestroyTransportChannel(TransportChannelImpl* channel) = 0; + + // Informs the subclass that we received the signaling ready message. + virtual void OnTransportSignalingReady() {} + + // The current local transport description, for use by derived classes + // when performing transport description negotiation. + const TransportDescription* local_description() const { + return local_description_.get(); + } + + // The current remote transport description, for use by derived classes + // when performing transport description negotiation. + const TransportDescription* remote_description() const { + return remote_description_.get(); + } + + virtual void SetIdentity_w(rtc::SSLIdentity* identity) {} + + virtual bool GetIdentity_w(rtc::SSLIdentity** identity) { + return false; + } + + // Pushes down the transport parameters from the local description, such + // as the ICE ufrag and pwd. + // Derived classes can override, but must call the base as well. + virtual bool ApplyLocalTransportDescription_w(TransportChannelImpl* channel, + std::string* error_desc); + + // Pushes down remote ice credentials from the remote description to the + // transport channel. + virtual bool ApplyRemoteTransportDescription_w(TransportChannelImpl* ch, + std::string* error_desc); + + // Negotiates the transport parameters based on the current local and remote + // transport description, such at the version of ICE to use, and whether DTLS + // should be activated. + // Derived classes can negotiate their specific parameters here, but must call + // the base as well. + virtual bool NegotiateTransportDescription_w(ContentAction local_role, + std::string* error_desc); + + // Pushes down the transport parameters obtained via negotiation. + // Derived classes can set their specific parameters here, but must call the + // base as well. + virtual bool ApplyNegotiatedTransportDescription_w( + TransportChannelImpl* channel, std::string* error_desc); + + virtual bool GetSslRole_w(rtc::SSLRole* ssl_role) const { + return false; + } + + private: + struct ChannelMapEntry { + ChannelMapEntry() : impl_(NULL), candidates_allocated_(false), ref_(0) {} + explicit ChannelMapEntry(TransportChannelImpl *impl) + : impl_(impl), + candidates_allocated_(false), + ref_(0) { + } + + void AddRef() { ++ref_; } + void DecRef() { + ASSERT(ref_ > 0); + --ref_; + } + int ref() const { return ref_; } + + TransportChannelImpl* get() const { return impl_; } + TransportChannelImpl* operator->() const { return impl_; } + void set_candidates_allocated(bool status) { + candidates_allocated_ = status; + } + bool candidates_allocated() const { return candidates_allocated_; } + + private: + TransportChannelImpl *impl_; + bool candidates_allocated_; + int ref_; + }; + + // Candidate component => ChannelMapEntry + typedef std::map ChannelMap; + + // Called when the state of a channel changes. + void OnChannelReadableState(TransportChannel* channel); + void OnChannelWritableState(TransportChannel* channel); + + // Called when a channel requests signaling. + void OnChannelRequestSignaling(TransportChannelImpl* channel); + + // Called when a candidate is ready from remote peer. + void OnRemoteCandidate(const Candidate& candidate); + // Called when a candidate is ready from channel. + void OnChannelCandidateReady(TransportChannelImpl* channel, + const Candidate& candidate); + void OnChannelRouteChange(TransportChannel* channel, + const Candidate& remote_candidate); + void OnChannelCandidatesAllocationDone(TransportChannelImpl* channel); + // Called when there is ICE role change. + void OnRoleConflict(TransportChannelImpl* channel); + // Called when the channel removes a connection. + void OnChannelConnectionRemoved(TransportChannelImpl* channel); + + // Dispatches messages to the appropriate handler (below). + void OnMessage(rtc::Message* msg); + + // These are versions of the above methods that are called only on a + // particular thread (s = signaling, w = worker). The above methods post or + // send a message to invoke this version. + TransportChannelImpl* CreateChannel_w(int component); + void DestroyChannel_w(int component); + void ConnectChannels_w(); + void ResetChannels_w(); + void DestroyAllChannels_w(); + void OnRemoteCandidate_w(const Candidate& candidate); + void OnChannelReadableState_s(); + void OnChannelWritableState_s(); + void OnChannelRequestSignaling_s(int component); + void OnConnecting_s(); + void OnChannelRouteChange_s(const TransportChannel* channel, + const Candidate& remote_candidate); + void OnChannelCandidatesAllocationDone_s(); + + // Helper function that invokes the given function on every channel. + typedef void (TransportChannelImpl::* TransportChannelFunc)(); + void CallChannels_w(TransportChannelFunc func); + + // Computes the OR of the channel's read or write state (argument picks). + TransportState GetTransportState_s(bool read); + + void OnChannelCandidateReady_s(); + + void SetIceRole_w(IceRole role); + void SetRemoteIceMode_w(IceMode mode); + bool SetLocalTransportDescription_w(const TransportDescription& desc, + ContentAction action, + std::string* error_desc); + bool SetRemoteTransportDescription_w(const TransportDescription& desc, + ContentAction action, + std::string* error_desc); + bool GetStats_w(TransportStats* infos); + bool GetRemoteCertificate_w(rtc::SSLCertificate** cert); + + // Sends SignalCompleted if we are now in that state. + void MaybeCompleted_w(); + + rtc::Thread* signaling_thread_; + rtc::Thread* worker_thread_; + std::string content_name_; + std::string type_; + PortAllocator* allocator_; + bool destroyed_; + TransportState readable_; + TransportState writable_; + bool was_writable_; + bool connect_requested_; + IceRole ice_role_; + uint64 tiebreaker_; + TransportProtocol protocol_; + IceMode remote_ice_mode_; + rtc::scoped_ptr local_description_; + rtc::scoped_ptr remote_description_; + + ChannelMap channels_; + // Buffers the ready_candidates so that SignalCanidatesReady can + // provide them in multiples. + std::vector ready_candidates_; + // Protects changes to channels and messages + rtc::CriticalSection crit_; + + DISALLOW_EVIL_CONSTRUCTORS(Transport); +}; + +// Extract a TransportProtocol from a TransportDescription. +TransportProtocol TransportProtocolFromDescription( + const TransportDescription* desc); + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_TRANSPORT_H_ diff --git a/webrtc/p2p/base/transport_unittest.cc b/webrtc/p2p/base/transport_unittest.cc new file mode 100644 index 000000000..abcf32ceb --- /dev/null +++ b/webrtc/p2p/base/transport_unittest.cc @@ -0,0 +1,438 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/fakesession.h" +#include "webrtc/p2p/base/p2ptransport.h" +#include "webrtc/p2p/base/parsing.h" +#include "webrtc/p2p/base/rawtransport.h" +#include "webrtc/p2p/base/sessionmessages.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/base/fakesslidentity.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/thread.h" + +using cricket::Candidate; +using cricket::Candidates; +using cricket::Transport; +using cricket::FakeTransport; +using cricket::TransportChannel; +using cricket::FakeTransportChannel; +using cricket::IceRole; +using cricket::TransportDescription; +using cricket::WriteError; +using cricket::ParseError; +using rtc::SocketAddress; + +static const char kIceUfrag1[] = "TESTICEUFRAG0001"; +static const char kIcePwd1[] = "TESTICEPWD00000000000001"; + +static const char kIceUfrag2[] = "TESTICEUFRAG0002"; +static const char kIcePwd2[] = "TESTICEPWD00000000000002"; + +class TransportTest : public testing::Test, + public sigslot::has_slots<> { + public: + TransportTest() + : thread_(rtc::Thread::Current()), + transport_(new FakeTransport( + thread_, thread_, "test content name", NULL)), + channel_(NULL), + connecting_signalled_(false), + completed_(false), + failed_(false) { + transport_->SignalConnecting.connect(this, &TransportTest::OnConnecting); + transport_->SignalCompleted.connect(this, &TransportTest::OnCompleted); + transport_->SignalFailed.connect(this, &TransportTest::OnFailed); + } + ~TransportTest() { + transport_->DestroyAllChannels(); + } + bool SetupChannel() { + channel_ = CreateChannel(1); + return (channel_ != NULL); + } + FakeTransportChannel* CreateChannel(int component) { + return static_cast( + transport_->CreateChannel(component)); + } + void DestroyChannel() { + transport_->DestroyChannel(1); + channel_ = NULL; + } + + protected: + void OnConnecting(Transport* transport) { + connecting_signalled_ = true; + } + void OnCompleted(Transport* transport) { + completed_ = true; + } + void OnFailed(Transport* transport) { + failed_ = true; + } + + rtc::Thread* thread_; + rtc::scoped_ptr transport_; + FakeTransportChannel* channel_; + bool connecting_signalled_; + bool completed_; + bool failed_; +}; + +class FakeCandidateTranslator : public cricket::CandidateTranslator { + public: + void AddMapping(int component, const std::string& channel_name) { + name_to_component[channel_name] = component; + component_to_name[component] = channel_name; + } + + bool GetChannelNameFromComponent( + int component, std::string* channel_name) const { + if (component_to_name.find(component) == component_to_name.end()) { + return false; + } + *channel_name = component_to_name.find(component)->second; + return true; + } + bool GetComponentFromChannelName( + const std::string& channel_name, int* component) const { + if (name_to_component.find(channel_name) == name_to_component.end()) { + return false; + } + *component = name_to_component.find(channel_name)->second; + return true; + } + + std::map name_to_component; + std::map component_to_name; +}; + +// Test that calling ConnectChannels triggers an OnConnecting signal. +TEST_F(TransportTest, TestConnectChannelsDoesSignal) { + EXPECT_TRUE(SetupChannel()); + transport_->ConnectChannels(); + EXPECT_FALSE(connecting_signalled_); + + EXPECT_TRUE_WAIT(connecting_signalled_, 100); +} + +// Test that DestroyAllChannels kills any pending OnConnecting signals. +TEST_F(TransportTest, TestDestroyAllClearsPosts) { + EXPECT_TRUE(transport_->CreateChannel(1) != NULL); + + transport_->ConnectChannels(); + transport_->DestroyAllChannels(); + + thread_->ProcessMessages(0); + EXPECT_FALSE(connecting_signalled_); +} + +// This test verifies channels are created with proper ICE +// role, tiebreaker and remote ice mode and credentials after offer and +// answer negotiations. +TEST_F(TransportTest, TestChannelIceParameters) { + transport_->SetIceRole(cricket::ICEROLE_CONTROLLING); + transport_->SetIceTiebreaker(99U); + cricket::TransportDescription local_desc( + cricket::NS_JINGLE_ICE_UDP, kIceUfrag1, kIcePwd1); + ASSERT_TRUE(transport_->SetLocalTransportDescription(local_desc, + cricket::CA_OFFER, + NULL)); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, transport_->ice_role()); + EXPECT_TRUE(SetupChannel()); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel_->GetIceRole()); + EXPECT_EQ(cricket::ICEMODE_FULL, channel_->remote_ice_mode()); + EXPECT_EQ(kIceUfrag1, channel_->ice_ufrag()); + EXPECT_EQ(kIcePwd1, channel_->ice_pwd()); + + cricket::TransportDescription remote_desc( + cricket::NS_JINGLE_ICE_UDP, kIceUfrag1, kIcePwd1); + ASSERT_TRUE(transport_->SetRemoteTransportDescription(remote_desc, + cricket::CA_ANSWER, + NULL)); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel_->GetIceRole()); + EXPECT_EQ(99U, channel_->IceTiebreaker()); + EXPECT_EQ(cricket::ICEMODE_FULL, channel_->remote_ice_mode()); + // Changing the transport role from CONTROLLING to CONTROLLED. + transport_->SetIceRole(cricket::ICEROLE_CONTROLLED); + EXPECT_EQ(cricket::ICEROLE_CONTROLLED, channel_->GetIceRole()); + EXPECT_EQ(cricket::ICEMODE_FULL, channel_->remote_ice_mode()); + EXPECT_EQ(kIceUfrag1, channel_->remote_ice_ufrag()); + EXPECT_EQ(kIcePwd1, channel_->remote_ice_pwd()); +} + +// Verifies that IceCredentialsChanged returns true when either ufrag or pwd +// changed, and false in other cases. +TEST_F(TransportTest, TestIceCredentialsChanged) { + EXPECT_TRUE(cricket::IceCredentialsChanged("u1", "p1", "u2", "p2")); + EXPECT_TRUE(cricket::IceCredentialsChanged("u1", "p1", "u2", "p1")); + EXPECT_TRUE(cricket::IceCredentialsChanged("u1", "p1", "u1", "p2")); + EXPECT_FALSE(cricket::IceCredentialsChanged("u1", "p1", "u1", "p1")); +} + +// This test verifies that the callee's ICE role changes from controlled to +// controlling when the callee triggers an ICE restart. +TEST_F(TransportTest, TestIceControlledToControllingOnIceRestart) { + EXPECT_TRUE(SetupChannel()); + transport_->SetIceRole(cricket::ICEROLE_CONTROLLED); + + cricket::TransportDescription desc( + cricket::NS_JINGLE_ICE_UDP, kIceUfrag1, kIcePwd1); + ASSERT_TRUE(transport_->SetRemoteTransportDescription(desc, + cricket::CA_OFFER, + NULL)); + ASSERT_TRUE(transport_->SetLocalTransportDescription(desc, + cricket::CA_ANSWER, + NULL)); + EXPECT_EQ(cricket::ICEROLE_CONTROLLED, transport_->ice_role()); + + cricket::TransportDescription new_local_desc( + cricket::NS_JINGLE_ICE_UDP, kIceUfrag2, kIcePwd2); + ASSERT_TRUE(transport_->SetLocalTransportDescription(new_local_desc, + cricket::CA_OFFER, + NULL)); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, transport_->ice_role()); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel_->GetIceRole()); +} + +// This test verifies that the caller's ICE role changes from controlling to +// controlled when the callee triggers an ICE restart. +TEST_F(TransportTest, TestIceControllingToControlledOnIceRestart) { + EXPECT_TRUE(SetupChannel()); + transport_->SetIceRole(cricket::ICEROLE_CONTROLLING); + + cricket::TransportDescription desc( + cricket::NS_JINGLE_ICE_UDP, kIceUfrag1, kIcePwd1); + ASSERT_TRUE(transport_->SetLocalTransportDescription(desc, + cricket::CA_OFFER, + NULL)); + ASSERT_TRUE(transport_->SetRemoteTransportDescription(desc, + cricket::CA_ANSWER, + NULL)); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, transport_->ice_role()); + + cricket::TransportDescription new_local_desc( + cricket::NS_JINGLE_ICE_UDP, kIceUfrag2, kIcePwd2); + ASSERT_TRUE(transport_->SetLocalTransportDescription(new_local_desc, + cricket::CA_ANSWER, + NULL)); + EXPECT_EQ(cricket::ICEROLE_CONTROLLED, transport_->ice_role()); + EXPECT_EQ(cricket::ICEROLE_CONTROLLED, channel_->GetIceRole()); +} + +// This test verifies that the caller's ICE role is still controlling after the +// callee triggers ICE restart if the callee's ICE mode is LITE. +TEST_F(TransportTest, TestIceControllingOnIceRestartIfRemoteIsIceLite) { + EXPECT_TRUE(SetupChannel()); + transport_->SetIceRole(cricket::ICEROLE_CONTROLLING); + + cricket::TransportDescription desc( + cricket::NS_JINGLE_ICE_UDP, kIceUfrag1, kIcePwd1); + ASSERT_TRUE(transport_->SetLocalTransportDescription(desc, + cricket::CA_OFFER, + NULL)); + + cricket::TransportDescription remote_desc( + cricket::NS_JINGLE_ICE_UDP, std::vector(), + kIceUfrag1, kIcePwd1, cricket::ICEMODE_LITE, + cricket::CONNECTIONROLE_NONE, NULL, cricket::Candidates()); + ASSERT_TRUE(transport_->SetRemoteTransportDescription(remote_desc, + cricket::CA_ANSWER, + NULL)); + + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, transport_->ice_role()); + + cricket::TransportDescription new_local_desc( + cricket::NS_JINGLE_ICE_UDP, kIceUfrag2, kIcePwd2); + ASSERT_TRUE(transport_->SetLocalTransportDescription(new_local_desc, + cricket::CA_ANSWER, + NULL)); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, transport_->ice_role()); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel_->GetIceRole()); +} + +// This test verifies that the Completed and Failed states can be reached. +TEST_F(TransportTest, TestChannelCompletedAndFailed) { + transport_->SetIceRole(cricket::ICEROLE_CONTROLLING); + cricket::TransportDescription local_desc( + cricket::NS_JINGLE_ICE_UDP, kIceUfrag1, kIcePwd1); + ASSERT_TRUE(transport_->SetLocalTransportDescription(local_desc, + cricket::CA_OFFER, + NULL)); + EXPECT_TRUE(SetupChannel()); + + cricket::TransportDescription remote_desc( + cricket::NS_JINGLE_ICE_UDP, kIceUfrag1, kIcePwd1); + ASSERT_TRUE(transport_->SetRemoteTransportDescription(remote_desc, + cricket::CA_ANSWER, + NULL)); + + channel_->SetConnectionCount(2); + channel_->SignalCandidatesAllocationDone(channel_); + channel_->SetWritable(true); + EXPECT_TRUE_WAIT(transport_->all_channels_writable(), 100); + // ICE is not yet completed because there is still more than one connection. + EXPECT_FALSE(completed_); + EXPECT_FALSE(failed_); + + // When the connection count drops to 1, SignalCompleted should be emitted, + // and completed() should be true. + channel_->SetConnectionCount(1); + EXPECT_TRUE_WAIT(completed_, 100); + completed_ = false; + + // When the connection count drops to 0, SignalFailed should be emitted, and + // completed() should be false. + channel_->SetConnectionCount(0); + EXPECT_TRUE_WAIT(failed_, 100); + EXPECT_FALSE(completed_); +} + +// Tests channel role is reversed after receiving ice-lite from remote. +TEST_F(TransportTest, TestSetRemoteIceLiteInOffer) { + transport_->SetIceRole(cricket::ICEROLE_CONTROLLED); + cricket::TransportDescription remote_desc( + cricket::NS_JINGLE_ICE_UDP, std::vector(), + kIceUfrag1, kIcePwd1, cricket::ICEMODE_LITE, + cricket::CONNECTIONROLE_ACTPASS, NULL, cricket::Candidates()); + ASSERT_TRUE(transport_->SetRemoteTransportDescription(remote_desc, + cricket::CA_OFFER, + NULL)); + cricket::TransportDescription local_desc( + cricket::NS_JINGLE_ICE_UDP, kIceUfrag1, kIcePwd1); + ASSERT_TRUE(transport_->SetLocalTransportDescription(local_desc, + cricket::CA_ANSWER, + NULL)); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, transport_->ice_role()); + EXPECT_TRUE(SetupChannel()); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel_->GetIceRole()); + EXPECT_EQ(cricket::ICEMODE_LITE, channel_->remote_ice_mode()); +} + +// Tests ice-lite in remote answer. +TEST_F(TransportTest, TestSetRemoteIceLiteInAnswer) { + transport_->SetIceRole(cricket::ICEROLE_CONTROLLING); + cricket::TransportDescription local_desc( + cricket::NS_JINGLE_ICE_UDP, kIceUfrag1, kIcePwd1); + ASSERT_TRUE(transport_->SetLocalTransportDescription(local_desc, + cricket::CA_OFFER, + NULL)); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, transport_->ice_role()); + EXPECT_TRUE(SetupChannel()); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel_->GetIceRole()); + // Channels will be created in ICEFULL_MODE. + EXPECT_EQ(cricket::ICEMODE_FULL, channel_->remote_ice_mode()); + cricket::TransportDescription remote_desc( + cricket::NS_JINGLE_ICE_UDP, std::vector(), + kIceUfrag1, kIcePwd1, cricket::ICEMODE_LITE, + cricket::CONNECTIONROLE_NONE, NULL, cricket::Candidates()); + ASSERT_TRUE(transport_->SetRemoteTransportDescription(remote_desc, + cricket::CA_ANSWER, + NULL)); + EXPECT_EQ(cricket::ICEROLE_CONTROLLING, channel_->GetIceRole()); + // After receiving remote description with ICEMODE_LITE, channel should + // have mode set to ICEMODE_LITE. + EXPECT_EQ(cricket::ICEMODE_LITE, channel_->remote_ice_mode()); +} + +// Tests that we can properly serialize/deserialize candidates. +TEST_F(TransportTest, TestP2PTransportWriteAndParseCandidate) { + Candidate test_candidate( + "", 1, "udp", + rtc::SocketAddress("2001:db8:fefe::1", 9999), + 738197504, "abcdef", "ghijkl", "foo", "testnet", 50, ""); + Candidate test_candidate2( + "", 2, "tcp", + rtc::SocketAddress("192.168.7.1", 9999), + 1107296256, "mnopqr", "stuvwx", "bar", "testnet2", 100, ""); + rtc::SocketAddress host_address("www.google.com", 24601); + host_address.SetResolvedIP(rtc::IPAddress(0x0A000001)); + Candidate test_candidate3( + "", 3, "spdy", host_address, 1476395008, "yzabcd", + "efghij", "baz", "testnet3", 150, ""); + WriteError write_error; + ParseError parse_error; + rtc::scoped_ptr elem; + cricket::Candidate parsed_candidate; + cricket::P2PTransportParser parser; + + FakeCandidateTranslator translator; + translator.AddMapping(1, "test"); + translator.AddMapping(2, "test2"); + translator.AddMapping(3, "test3"); + + EXPECT_TRUE(parser.WriteGingleCandidate(test_candidate, &translator, + elem.accept(), &write_error)); + EXPECT_EQ("", write_error.text); + EXPECT_EQ("test", elem->Attr(buzz::QN_NAME)); + EXPECT_EQ("udp", elem->Attr(cricket::QN_PROTOCOL)); + EXPECT_EQ("2001:db8:fefe::1", elem->Attr(cricket::QN_ADDRESS)); + EXPECT_EQ("9999", elem->Attr(cricket::QN_PORT)); + EXPECT_EQ("0.34", elem->Attr(cricket::QN_PREFERENCE)); + EXPECT_EQ("abcdef", elem->Attr(cricket::QN_USERNAME)); + EXPECT_EQ("ghijkl", elem->Attr(cricket::QN_PASSWORD)); + EXPECT_EQ("foo", elem->Attr(cricket::QN_TYPE)); + EXPECT_EQ("testnet", elem->Attr(cricket::QN_NETWORK)); + EXPECT_EQ("50", elem->Attr(cricket::QN_GENERATION)); + + EXPECT_TRUE(parser.ParseGingleCandidate(elem.get(), &translator, + &parsed_candidate, &parse_error)); + EXPECT_TRUE(test_candidate.IsEquivalent(parsed_candidate)); + + EXPECT_TRUE(parser.WriteGingleCandidate(test_candidate2, &translator, + elem.accept(), &write_error)); + EXPECT_EQ("test2", elem->Attr(buzz::QN_NAME)); + EXPECT_EQ("tcp", elem->Attr(cricket::QN_PROTOCOL)); + EXPECT_EQ("192.168.7.1", elem->Attr(cricket::QN_ADDRESS)); + EXPECT_EQ("9999", elem->Attr(cricket::QN_PORT)); + EXPECT_EQ("0.51", elem->Attr(cricket::QN_PREFERENCE)); + EXPECT_EQ("mnopqr", elem->Attr(cricket::QN_USERNAME)); + EXPECT_EQ("stuvwx", elem->Attr(cricket::QN_PASSWORD)); + EXPECT_EQ("bar", elem->Attr(cricket::QN_TYPE)); + EXPECT_EQ("testnet2", elem->Attr(cricket::QN_NETWORK)); + EXPECT_EQ("100", elem->Attr(cricket::QN_GENERATION)); + + EXPECT_TRUE(parser.ParseGingleCandidate(elem.get(), &translator, + &parsed_candidate, &parse_error)); + EXPECT_TRUE(test_candidate2.IsEquivalent(parsed_candidate)); + + // Check that an ip is preferred over hostname. + EXPECT_TRUE(parser.WriteGingleCandidate(test_candidate3, &translator, + elem.accept(), &write_error)); + EXPECT_EQ("test3", elem->Attr(cricket::QN_NAME)); + EXPECT_EQ("spdy", elem->Attr(cricket::QN_PROTOCOL)); + EXPECT_EQ("10.0.0.1", elem->Attr(cricket::QN_ADDRESS)); + EXPECT_EQ("24601", elem->Attr(cricket::QN_PORT)); + EXPECT_EQ("0.69", elem->Attr(cricket::QN_PREFERENCE)); + EXPECT_EQ("yzabcd", elem->Attr(cricket::QN_USERNAME)); + EXPECT_EQ("efghij", elem->Attr(cricket::QN_PASSWORD)); + EXPECT_EQ("baz", elem->Attr(cricket::QN_TYPE)); + EXPECT_EQ("testnet3", elem->Attr(cricket::QN_NETWORK)); + EXPECT_EQ("150", elem->Attr(cricket::QN_GENERATION)); + + EXPECT_TRUE(parser.ParseGingleCandidate(elem.get(), &translator, + &parsed_candidate, &parse_error)); + EXPECT_TRUE(test_candidate3.IsEquivalent(parsed_candidate)); +} + +TEST_F(TransportTest, TestGetStats) { + EXPECT_TRUE(SetupChannel()); + cricket::TransportStats stats; + EXPECT_TRUE(transport_->GetStats(&stats)); + // Note that this tests the behavior of a FakeTransportChannel. + ASSERT_EQ(1U, stats.channel_stats.size()); + EXPECT_EQ(1, stats.channel_stats[0].component); + transport_->ConnectChannels(); + EXPECT_TRUE(transport_->GetStats(&stats)); + ASSERT_EQ(1U, stats.channel_stats.size()); + EXPECT_EQ(1, stats.channel_stats[0].component); +} diff --git a/webrtc/p2p/base/transportchannel.cc b/webrtc/p2p/base/transportchannel.cc new file mode 100644 index 000000000..16ae27d16 --- /dev/null +++ b/webrtc/p2p/base/transportchannel.cc @@ -0,0 +1,43 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include "webrtc/p2p/base/transportchannel.h" + +namespace cricket { + +std::string TransportChannel::ToString() const { + const char READABLE_ABBREV[2] = { '_', 'R' }; + const char WRITABLE_ABBREV[2] = { '_', 'W' }; + std::stringstream ss; + ss << "Channel[" << content_name_ + << "|" << component_ + << "|" << READABLE_ABBREV[readable_] << WRITABLE_ABBREV[writable_] << "]"; + return ss.str(); +} + +void TransportChannel::set_readable(bool readable) { + if (readable_ != readable) { + readable_ = readable; + SignalReadableState(this); + } +} + +void TransportChannel::set_writable(bool writable) { + if (writable_ != writable) { + writable_ = writable; + if (writable_) { + SignalReadyToSend(this); + } + SignalWritableState(this); + } +} + +} // namespace cricket diff --git a/webrtc/p2p/base/transportchannel.h b/webrtc/p2p/base/transportchannel.h new file mode 100644 index 000000000..f91c4a8e1 --- /dev/null +++ b/webrtc/p2p/base/transportchannel.h @@ -0,0 +1,143 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_TRANSPORTCHANNEL_H_ +#define WEBRTC_P2P_BASE_TRANSPORTCHANNEL_H_ + +#include +#include + +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/transport.h" +#include "webrtc/p2p/base/transportdescription.h" +#include "webrtc/base/asyncpacketsocket.h" +#include "webrtc/base/basictypes.h" +#include "webrtc/base/dscp.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/socket.h" +#include "webrtc/base/sslidentity.h" +#include "webrtc/base/sslstreamadapter.h" + +namespace cricket { + +class Candidate; + +// Flags for SendPacket/SignalReadPacket. +enum PacketFlags { + PF_NORMAL = 0x00, // A normal packet. + PF_SRTP_BYPASS = 0x01, // An encrypted SRTP packet; bypass any additional + // crypto provided by the transport (e.g. DTLS) +}; + +// A TransportChannel represents one logical stream of packets that are sent +// between the two sides of a session. +class TransportChannel : public sigslot::has_slots<> { + public: + explicit TransportChannel(const std::string& content_name, int component) + : content_name_(content_name), + component_(component), + readable_(false), writable_(false) {} + virtual ~TransportChannel() {} + + // TODO(mallinath) - Remove this API, as it's no longer useful. + // Returns the session id of this channel. + virtual const std::string SessionId() const { return std::string(); } + + const std::string& content_name() const { return content_name_; } + int component() const { return component_; } + + // Returns the readable and states of this channel. Each time one of these + // states changes, a signal is raised. These states are aggregated by the + // TransportManager. + bool readable() const { return readable_; } + bool writable() const { return writable_; } + sigslot::signal1 SignalReadableState; + sigslot::signal1 SignalWritableState; + // Emitted when the TransportChannel's ability to send has changed. + sigslot::signal1 SignalReadyToSend; + + // Attempts to send the given packet. The return value is < 0 on failure. + // TODO: Remove the default argument once channel code is updated. + virtual int SendPacket(const char* data, size_t len, + const rtc::PacketOptions& options, + int flags = 0) = 0; + + // Sets a socket option on this channel. Note that not all options are + // supported by all transport types. + virtual int SetOption(rtc::Socket::Option opt, int value) = 0; + + // Returns the most recent error that occurred on this channel. + virtual int GetError() = 0; + + // Returns the current stats for this connection. + virtual bool GetStats(ConnectionInfos* infos) = 0; + + // Is DTLS active? + virtual bool IsDtlsActive() const = 0; + + // Default implementation. + virtual bool GetSslRole(rtc::SSLRole* role) const = 0; + + // Sets up the ciphers to use for DTLS-SRTP. + virtual bool SetSrtpCiphers(const std::vector& ciphers) = 0; + + // Finds out which DTLS-SRTP cipher was negotiated + virtual bool GetSrtpCipher(std::string* cipher) = 0; + + // Gets a copy of the local SSL identity, owned by the caller. + virtual bool GetLocalIdentity(rtc::SSLIdentity** identity) const = 0; + + // Gets a copy of the remote side's SSL certificate, owned by the caller. + virtual bool GetRemoteCertificate(rtc::SSLCertificate** cert) const = 0; + + // Allows key material to be extracted for external encryption. + virtual bool ExportKeyingMaterial(const std::string& label, + const uint8* context, + size_t context_len, + bool use_context, + uint8* result, + size_t result_len) = 0; + + // Signalled each time a packet is received on this channel. + sigslot::signal5 SignalReadPacket; + + // This signal occurs when there is a change in the way that packets are + // being routed, i.e. to a different remote location. The candidate + // indicates where and how we are currently sending media. + sigslot::signal2 SignalRouteChange; + + // Invoked when the channel is being destroyed. + sigslot::signal1 SignalDestroyed; + + // Debugging description of this transport channel. + std::string ToString() const; + + protected: + // Sets the readable state, signaling if necessary. + void set_readable(bool readable); + + // Sets the writable state, signaling if necessary. + void set_writable(bool writable); + + + private: + // Used mostly for debugging. + std::string content_name_; + int component_; + bool readable_; + bool writable_; + + DISALLOW_EVIL_CONSTRUCTORS(TransportChannel); +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_TRANSPORTCHANNEL_H_ diff --git a/webrtc/p2p/base/transportchannelimpl.h b/webrtc/p2p/base/transportchannelimpl.h new file mode 100644 index 000000000..060df7fdb --- /dev/null +++ b/webrtc/p2p/base/transportchannelimpl.h @@ -0,0 +1,111 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_TRANSPORTCHANNELIMPL_H_ +#define WEBRTC_P2P_BASE_TRANSPORTCHANNELIMPL_H_ + +#include +#include "webrtc/p2p/base/transport.h" +#include "webrtc/p2p/base/transportchannel.h" + +namespace buzz { class XmlElement; } + +namespace cricket { + +class Candidate; + +// Base class for real implementations of TransportChannel. This includes some +// methods called only by Transport, which do not need to be exposed to the +// client. +class TransportChannelImpl : public TransportChannel { + public: + explicit TransportChannelImpl(const std::string& content_name, int component) + : TransportChannel(content_name, component) {} + + // Returns the transport that created this channel. + virtual Transport* GetTransport() = 0; + + // For ICE channels. + virtual IceRole GetIceRole() const = 0; + virtual void SetIceRole(IceRole role) = 0; + virtual void SetIceTiebreaker(uint64 tiebreaker) = 0; + virtual size_t GetConnectionCount() const = 0; + // To toggle G-ICE/ICE. + virtual bool GetIceProtocolType(IceProtocolType* type) const = 0; + virtual void SetIceProtocolType(IceProtocolType type) = 0; + // SetIceCredentials only need to be implemented by the ICE + // transport channels. Non-ICE transport channels can just ignore. + // The ufrag and pwd should be set before the Connect() is called. + virtual void SetIceCredentials(const std::string& ice_ufrag, + const std::string& ice_pwd) = 0; + // SetRemoteIceCredentials only need to be implemented by the ICE + // transport channels. Non-ICE transport channels can just ignore. + virtual void SetRemoteIceCredentials(const std::string& ice_ufrag, + const std::string& ice_pwd) = 0; + + // SetRemoteIceMode must be implemented only by the ICE transport channels. + virtual void SetRemoteIceMode(IceMode mode) = 0; + + // Begins the process of attempting to make a connection to the other client. + virtual void Connect() = 0; + + // Resets this channel back to the initial state (i.e., not connecting). + virtual void Reset() = 0; + + // Allows an individual channel to request signaling and be notified when it + // is ready. This is useful if the individual named channels have need to + // send their own transport-info stanzas. + sigslot::signal1 SignalRequestSignaling; + virtual void OnSignalingReady() = 0; + + // Handles sending and receiving of candidates. The Transport + // receives the candidates and may forward them to the relevant + // channel. + // + // Note: Since candidates are delivered asynchronously to the + // channel, they cannot return an error if the message is invalid. + // It is assumed that the Transport will have checked validity + // before forwarding. + sigslot::signal2 SignalCandidateReady; + virtual void OnCandidate(const Candidate& candidate) = 0; + + // DTLS methods + // Set DTLS local identity. The identity object is not copied, but the caller + // retains ownership and must delete it after this TransportChannelImpl is + // destroyed. + // TODO(bemasc): Fix the ownership semantics of this method. + virtual bool SetLocalIdentity(rtc::SSLIdentity* identity) = 0; + + // Set DTLS Remote fingerprint. Must be after local identity set. + virtual bool SetRemoteFingerprint(const std::string& digest_alg, + const uint8* digest, + size_t digest_len) = 0; + + virtual bool SetSslRole(rtc::SSLRole role) = 0; + + // TransportChannel is forwarding this signal from PortAllocatorSession. + sigslot::signal1 SignalCandidatesAllocationDone; + + // Invoked when there is conflict in the ICE role between local and remote + // agents. + sigslot::signal1 SignalRoleConflict; + + // Emitted whenever the number of connections available to the transport + // channel decreases. + sigslot::signal1 SignalConnectionRemoved; + + private: + DISALLOW_EVIL_CONSTRUCTORS(TransportChannelImpl); +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_TRANSPORTCHANNELIMPL_H_ diff --git a/webrtc/p2p/base/transportchannelproxy.cc b/webrtc/p2p/base/transportchannelproxy.cc new file mode 100644 index 000000000..b5e09571a --- /dev/null +++ b/webrtc/p2p/base/transportchannelproxy.cc @@ -0,0 +1,249 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/transport.h" +#include "webrtc/p2p/base/transportchannelimpl.h" +#include "webrtc/p2p/base/transportchannelproxy.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/thread.h" + +namespace cricket { + +enum { + MSG_UPDATESTATE, +}; + +TransportChannelProxy::TransportChannelProxy(const std::string& content_name, + const std::string& name, + int component) + : TransportChannel(content_name, component), + name_(name), + impl_(NULL) { + worker_thread_ = rtc::Thread::Current(); +} + +TransportChannelProxy::~TransportChannelProxy() { + // Clearing any pending signal. + worker_thread_->Clear(this); + if (impl_) + impl_->GetTransport()->DestroyChannel(impl_->component()); +} + +void TransportChannelProxy::SetImplementation(TransportChannelImpl* impl) { + ASSERT(rtc::Thread::Current() == worker_thread_); + + if (impl == impl_) { + // Ignore if the |impl| has already been set. + LOG(LS_WARNING) << "Ignored TransportChannelProxy::SetImplementation call " + << "with a same impl as the existing one."; + return; + } + + // Destroy any existing impl_. + if (impl_) { + impl_->GetTransport()->DestroyChannel(impl_->component()); + } + + // Adopt the supplied impl, and connect to its signals. + impl_ = impl; + + if (impl_) { + impl_->SignalReadableState.connect( + this, &TransportChannelProxy::OnReadableState); + impl_->SignalWritableState.connect( + this, &TransportChannelProxy::OnWritableState); + impl_->SignalReadPacket.connect( + this, &TransportChannelProxy::OnReadPacket); + impl_->SignalReadyToSend.connect( + this, &TransportChannelProxy::OnReadyToSend); + impl_->SignalRouteChange.connect( + this, &TransportChannelProxy::OnRouteChange); + for (OptionList::iterator it = pending_options_.begin(); + it != pending_options_.end(); + ++it) { + impl_->SetOption(it->first, it->second); + } + + // Push down the SRTP ciphers, if any were set. + if (!pending_srtp_ciphers_.empty()) { + impl_->SetSrtpCiphers(pending_srtp_ciphers_); + } + pending_options_.clear(); + } + + // Post ourselves a message to see if we need to fire state callbacks. + worker_thread_->Post(this, MSG_UPDATESTATE); +} + +int TransportChannelProxy::SendPacket(const char* data, size_t len, + const rtc::PacketOptions& options, + int flags) { + ASSERT(rtc::Thread::Current() == worker_thread_); + // Fail if we don't have an impl yet. + if (!impl_) { + return -1; + } + return impl_->SendPacket(data, len, options, flags); +} + +int TransportChannelProxy::SetOption(rtc::Socket::Option opt, int value) { + ASSERT(rtc::Thread::Current() == worker_thread_); + if (!impl_) { + pending_options_.push_back(OptionPair(opt, value)); + return 0; + } + return impl_->SetOption(opt, value); +} + +int TransportChannelProxy::GetError() { + ASSERT(rtc::Thread::Current() == worker_thread_); + if (!impl_) { + return 0; + } + return impl_->GetError(); +} + +bool TransportChannelProxy::GetStats(ConnectionInfos* infos) { + ASSERT(rtc::Thread::Current() == worker_thread_); + if (!impl_) { + return false; + } + return impl_->GetStats(infos); +} + +bool TransportChannelProxy::IsDtlsActive() const { + ASSERT(rtc::Thread::Current() == worker_thread_); + if (!impl_) { + return false; + } + return impl_->IsDtlsActive(); +} + +bool TransportChannelProxy::GetSslRole(rtc::SSLRole* role) const { + ASSERT(rtc::Thread::Current() == worker_thread_); + if (!impl_) { + return false; + } + return impl_->GetSslRole(role); +} + +bool TransportChannelProxy::SetSslRole(rtc::SSLRole role) { + ASSERT(rtc::Thread::Current() == worker_thread_); + if (!impl_) { + return false; + } + return impl_->SetSslRole(role); +} + +bool TransportChannelProxy::SetSrtpCiphers(const std::vector& + ciphers) { + ASSERT(rtc::Thread::Current() == worker_thread_); + pending_srtp_ciphers_ = ciphers; // Cache so we can send later, but always + // set so it stays consistent. + if (impl_) { + return impl_->SetSrtpCiphers(ciphers); + } + return true; +} + +bool TransportChannelProxy::GetSrtpCipher(std::string* cipher) { + ASSERT(rtc::Thread::Current() == worker_thread_); + if (!impl_) { + return false; + } + return impl_->GetSrtpCipher(cipher); +} + +bool TransportChannelProxy::GetLocalIdentity( + rtc::SSLIdentity** identity) const { + ASSERT(rtc::Thread::Current() == worker_thread_); + if (!impl_) { + return false; + } + return impl_->GetLocalIdentity(identity); +} + +bool TransportChannelProxy::GetRemoteCertificate( + rtc::SSLCertificate** cert) const { + ASSERT(rtc::Thread::Current() == worker_thread_); + if (!impl_) { + return false; + } + return impl_->GetRemoteCertificate(cert); +} + +bool TransportChannelProxy::ExportKeyingMaterial(const std::string& label, + const uint8* context, + size_t context_len, + bool use_context, + uint8* result, + size_t result_len) { + ASSERT(rtc::Thread::Current() == worker_thread_); + if (!impl_) { + return false; + } + return impl_->ExportKeyingMaterial(label, context, context_len, use_context, + result, result_len); +} + +IceRole TransportChannelProxy::GetIceRole() const { + ASSERT(rtc::Thread::Current() == worker_thread_); + if (!impl_) { + return ICEROLE_UNKNOWN; + } + return impl_->GetIceRole(); +} + +void TransportChannelProxy::OnReadableState(TransportChannel* channel) { + ASSERT(rtc::Thread::Current() == worker_thread_); + ASSERT(channel == impl_); + set_readable(impl_->readable()); + // Note: SignalReadableState fired by set_readable. +} + +void TransportChannelProxy::OnWritableState(TransportChannel* channel) { + ASSERT(rtc::Thread::Current() == worker_thread_); + ASSERT(channel == impl_); + set_writable(impl_->writable()); + // Note: SignalWritableState fired by set_readable. +} + +void TransportChannelProxy::OnReadPacket( + TransportChannel* channel, const char* data, size_t size, + const rtc::PacketTime& packet_time, int flags) { + ASSERT(rtc::Thread::Current() == worker_thread_); + ASSERT(channel == impl_); + SignalReadPacket(this, data, size, packet_time, flags); +} + +void TransportChannelProxy::OnReadyToSend(TransportChannel* channel) { + ASSERT(rtc::Thread::Current() == worker_thread_); + ASSERT(channel == impl_); + SignalReadyToSend(this); +} + +void TransportChannelProxy::OnRouteChange(TransportChannel* channel, + const Candidate& candidate) { + ASSERT(rtc::Thread::Current() == worker_thread_); + ASSERT(channel == impl_); + SignalRouteChange(this, candidate); +} + +void TransportChannelProxy::OnMessage(rtc::Message* msg) { + ASSERT(rtc::Thread::Current() == worker_thread_); + if (msg->message_id == MSG_UPDATESTATE) { + // If impl_ is already readable or writable, push up those signals. + set_readable(impl_ ? impl_->readable() : false); + set_writable(impl_ ? impl_->writable() : false); + } +} + +} // namespace cricket diff --git a/webrtc/p2p/base/transportchannelproxy.h b/webrtc/p2p/base/transportchannelproxy.h new file mode 100644 index 000000000..cfd07f858 --- /dev/null +++ b/webrtc/p2p/base/transportchannelproxy.h @@ -0,0 +1,95 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_TRANSPORTCHANNELPROXY_H_ +#define WEBRTC_P2P_BASE_TRANSPORTCHANNELPROXY_H_ + +#include +#include +#include + +#include "webrtc/p2p/base/transportchannel.h" +#include "webrtc/base/messagehandler.h" + +namespace rtc { +class Thread; +} + +namespace cricket { + +class TransportChannelImpl; + +// Proxies calls between the client and the transport channel implementation. +// This is needed because clients are allowed to create channels before the +// network negotiation is complete. Hence, we create a proxy up front, and +// when negotiation completes, connect the proxy to the implementaiton. +class TransportChannelProxy : public TransportChannel, + public rtc::MessageHandler { + public: + TransportChannelProxy(const std::string& content_name, + const std::string& name, + int component); + virtual ~TransportChannelProxy(); + + const std::string& name() const { return name_; } + TransportChannelImpl* impl() { return impl_; } + + // Sets the implementation to which we will proxy. + void SetImplementation(TransportChannelImpl* impl); + + // Implementation of the TransportChannel interface. These simply forward to + // the implementation. + virtual int SendPacket(const char* data, size_t len, + const rtc::PacketOptions& options, + int flags); + virtual int SetOption(rtc::Socket::Option opt, int value); + virtual int GetError(); + virtual IceRole GetIceRole() const; + virtual bool GetStats(ConnectionInfos* infos); + virtual bool IsDtlsActive() const; + virtual bool GetSslRole(rtc::SSLRole* role) const; + virtual bool SetSslRole(rtc::SSLRole role); + virtual bool SetSrtpCiphers(const std::vector& ciphers); + virtual bool GetSrtpCipher(std::string* cipher); + virtual bool GetLocalIdentity(rtc::SSLIdentity** identity) const; + virtual bool GetRemoteCertificate(rtc::SSLCertificate** cert) const; + virtual bool ExportKeyingMaterial(const std::string& label, + const uint8* context, + size_t context_len, + bool use_context, + uint8* result, + size_t result_len); + + private: + // Catch signals from the implementation channel. These just forward to the + // client (after updating our state to match). + void OnReadableState(TransportChannel* channel); + void OnWritableState(TransportChannel* channel); + void OnReadPacket(TransportChannel* channel, const char* data, size_t size, + const rtc::PacketTime& packet_time, int flags); + void OnReadyToSend(TransportChannel* channel); + void OnRouteChange(TransportChannel* channel, const Candidate& candidate); + + void OnMessage(rtc::Message* message); + + typedef std::pair OptionPair; + typedef std::vector OptionList; + std::string name_; + rtc::Thread* worker_thread_; + TransportChannelImpl* impl_; + OptionList pending_options_; + std::vector pending_srtp_ciphers_; + + DISALLOW_EVIL_CONSTRUCTORS(TransportChannelProxy); +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_TRANSPORTCHANNELPROXY_H_ diff --git a/webrtc/p2p/base/transportdescription.cc b/webrtc/p2p/base/transportdescription.cc new file mode 100644 index 000000000..01c6a8f07 --- /dev/null +++ b/webrtc/p2p/base/transportdescription.cc @@ -0,0 +1,55 @@ +/* + * Copyright 2013 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/transportdescription.h" + +#include "webrtc/p2p/base/constants.h" +#include "webrtc/base/stringutils.h" + +namespace cricket { + +bool StringToConnectionRole(const std::string& role_str, ConnectionRole* role) { + const char* const roles[] = { + CONNECTIONROLE_ACTIVE_STR, + CONNECTIONROLE_PASSIVE_STR, + CONNECTIONROLE_ACTPASS_STR, + CONNECTIONROLE_HOLDCONN_STR + }; + + for (size_t i = 0; i < ARRAY_SIZE(roles); ++i) { + if (_stricmp(roles[i], role_str.c_str()) == 0) { + *role = static_cast(CONNECTIONROLE_ACTIVE + i); + return true; + } + } + return false; +} + +bool ConnectionRoleToString(const ConnectionRole& role, std::string* role_str) { + switch (role) { + case cricket::CONNECTIONROLE_ACTIVE: + *role_str = cricket::CONNECTIONROLE_ACTIVE_STR; + break; + case cricket::CONNECTIONROLE_ACTPASS: + *role_str = cricket::CONNECTIONROLE_ACTPASS_STR; + break; + case cricket::CONNECTIONROLE_PASSIVE: + *role_str = cricket::CONNECTIONROLE_PASSIVE_STR; + break; + case cricket::CONNECTIONROLE_HOLDCONN: + *role_str = cricket::CONNECTIONROLE_HOLDCONN_STR; + break; + default: + return false; + } + return true; +} + +} // namespace cricket diff --git a/webrtc/p2p/base/transportdescription.h b/webrtc/p2p/base/transportdescription.h new file mode 100644 index 000000000..5ab1cd6a1 --- /dev/null +++ b/webrtc/p2p/base/transportdescription.h @@ -0,0 +1,171 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_TRANSPORTDESCRIPTION_H_ +#define WEBRTC_P2P_BASE_TRANSPORTDESCRIPTION_H_ + +#include +#include +#include + +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sslfingerprint.h" + +namespace cricket { + +// SEC_ENABLED and SEC_REQUIRED should only be used if the session +// was negotiated over TLS, to protect the inline crypto material +// exchange. +// SEC_DISABLED: No crypto in outgoing offer, ignore any supplied crypto. +// SEC_ENABLED: Crypto in outgoing offer and answer (if supplied in offer). +// SEC_REQUIRED: Crypto in outgoing offer and answer. Fail any offer with absent +// or unsupported crypto. +enum SecurePolicy { + SEC_DISABLED, + SEC_ENABLED, + SEC_REQUIRED +}; + +// The transport protocol we've elected to use. +enum TransportProtocol { + ICEPROTO_GOOGLE, // Google version of ICE protocol. + ICEPROTO_HYBRID, // ICE, but can fall back to the Google version. + ICEPROTO_RFC5245 // Standard RFC 5245 version of ICE. +}; +// The old name for TransportProtocol. +// TODO(juberti): remove this. +typedef TransportProtocol IceProtocolType; + +// Whether our side of the call is driving the negotiation, or the other side. +enum IceRole { + ICEROLE_CONTROLLING = 0, + ICEROLE_CONTROLLED, + ICEROLE_UNKNOWN +}; + +// ICE RFC 5245 implementation type. +enum IceMode { + ICEMODE_FULL, // As defined in http://tools.ietf.org/html/rfc5245#section-4.1 + ICEMODE_LITE // As defined in http://tools.ietf.org/html/rfc5245#section-4.2 +}; + +// RFC 4145 - http://tools.ietf.org/html/rfc4145#section-4 +// 'active': The endpoint will initiate an outgoing connection. +// 'passive': The endpoint will accept an incoming connection. +// 'actpass': The endpoint is willing to accept an incoming +// connection or to initiate an outgoing connection. +enum ConnectionRole { + CONNECTIONROLE_NONE = 0, + CONNECTIONROLE_ACTIVE, + CONNECTIONROLE_PASSIVE, + CONNECTIONROLE_ACTPASS, + CONNECTIONROLE_HOLDCONN, +}; + +extern const char CONNECTIONROLE_ACTIVE_STR[]; +extern const char CONNECTIONROLE_PASSIVE_STR[]; +extern const char CONNECTIONROLE_ACTPASS_STR[]; +extern const char CONNECTIONROLE_HOLDCONN_STR[]; + +bool StringToConnectionRole(const std::string& role_str, ConnectionRole* role); +bool ConnectionRoleToString(const ConnectionRole& role, std::string* role_str); + +typedef std::vector Candidates; + +struct TransportDescription { + TransportDescription() + : ice_mode(ICEMODE_FULL), + connection_role(CONNECTIONROLE_NONE) {} + + TransportDescription(const std::string& transport_type, + const std::vector& transport_options, + const std::string& ice_ufrag, + const std::string& ice_pwd, + IceMode ice_mode, + ConnectionRole role, + const rtc::SSLFingerprint* identity_fingerprint, + const Candidates& candidates) + : transport_type(transport_type), + transport_options(transport_options), + ice_ufrag(ice_ufrag), + ice_pwd(ice_pwd), + ice_mode(ice_mode), + connection_role(role), + identity_fingerprint(CopyFingerprint(identity_fingerprint)), + candidates(candidates) {} + TransportDescription(const std::string& transport_type, + const std::string& ice_ufrag, + const std::string& ice_pwd) + : transport_type(transport_type), + ice_ufrag(ice_ufrag), + ice_pwd(ice_pwd), + ice_mode(ICEMODE_FULL), + connection_role(CONNECTIONROLE_NONE) {} + TransportDescription(const TransportDescription& from) + : transport_type(from.transport_type), + transport_options(from.transport_options), + ice_ufrag(from.ice_ufrag), + ice_pwd(from.ice_pwd), + ice_mode(from.ice_mode), + connection_role(from.connection_role), + identity_fingerprint(CopyFingerprint(from.identity_fingerprint.get())), + candidates(from.candidates) {} + + TransportDescription& operator=(const TransportDescription& from) { + // Self-assignment + if (this == &from) + return *this; + + transport_type = from.transport_type; + transport_options = from.transport_options; + ice_ufrag = from.ice_ufrag; + ice_pwd = from.ice_pwd; + ice_mode = from.ice_mode; + connection_role = from.connection_role; + + identity_fingerprint.reset(CopyFingerprint( + from.identity_fingerprint.get())); + candidates = from.candidates; + return *this; + } + + bool HasOption(const std::string& option) const { + return (std::find(transport_options.begin(), transport_options.end(), + option) != transport_options.end()); + } + void AddOption(const std::string& option) { + transport_options.push_back(option); + } + bool secure() const { return identity_fingerprint != NULL; } + + static rtc::SSLFingerprint* CopyFingerprint( + const rtc::SSLFingerprint* from) { + if (!from) + return NULL; + + return new rtc::SSLFingerprint(*from); + } + + std::string transport_type; // xmlns of + std::vector transport_options; + std::string ice_ufrag; + std::string ice_pwd; + IceMode ice_mode; + ConnectionRole connection_role; + + rtc::scoped_ptr identity_fingerprint; + Candidates candidates; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_TRANSPORTDESCRIPTION_H_ diff --git a/webrtc/p2p/base/transportdescriptionfactory.cc b/webrtc/p2p/base/transportdescriptionfactory.cc new file mode 100644 index 000000000..619c9d160 --- /dev/null +++ b/webrtc/p2p/base/transportdescriptionfactory.cc @@ -0,0 +1,160 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/transportdescriptionfactory.h" + +#include "webrtc/p2p/base/transportdescription.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/messagedigest.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sslfingerprint.h" + +namespace cricket { + +static TransportProtocol kDefaultProtocol = ICEPROTO_GOOGLE; + +TransportDescriptionFactory::TransportDescriptionFactory() + : protocol_(kDefaultProtocol), + secure_(SEC_DISABLED), + identity_(NULL) { +} + +TransportDescription* TransportDescriptionFactory::CreateOffer( + const TransportOptions& options, + const TransportDescription* current_description) const { + rtc::scoped_ptr desc(new TransportDescription()); + + // Set the transport type depending on the selected protocol. + if (protocol_ == ICEPROTO_RFC5245) { + desc->transport_type = NS_JINGLE_ICE_UDP; + } else if (protocol_ == ICEPROTO_HYBRID) { + desc->transport_type = NS_JINGLE_ICE_UDP; + desc->AddOption(ICE_OPTION_GICE); + } else if (protocol_ == ICEPROTO_GOOGLE) { + desc->transport_type = NS_GINGLE_P2P; + } + + // Generate the ICE credentials if we don't already have them. + if (!current_description || options.ice_restart) { + desc->ice_ufrag = rtc::CreateRandomString(ICE_UFRAG_LENGTH); + desc->ice_pwd = rtc::CreateRandomString(ICE_PWD_LENGTH); + } else { + desc->ice_ufrag = current_description->ice_ufrag; + desc->ice_pwd = current_description->ice_pwd; + } + + // If we are trying to establish a secure transport, add a fingerprint. + if (secure_ == SEC_ENABLED || secure_ == SEC_REQUIRED) { + // Fail if we can't create the fingerprint. + // If we are the initiator set role to "actpass". + if (!SetSecurityInfo(desc.get(), CONNECTIONROLE_ACTPASS)) { + return NULL; + } + } + + return desc.release(); +} + +TransportDescription* TransportDescriptionFactory::CreateAnswer( + const TransportDescription* offer, + const TransportOptions& options, + const TransportDescription* current_description) const { + // A NULL offer is treated as a GICE transport description. + // TODO(juberti): Figure out why we get NULL offers, and fix this upstream. + rtc::scoped_ptr desc(new TransportDescription()); + + // Figure out which ICE variant to negotiate; prefer RFC 5245 ICE, but fall + // back to G-ICE if needed. Note that we never create a hybrid answer, since + // we know what the other side can support already. + if (offer && offer->transport_type == NS_JINGLE_ICE_UDP && + (protocol_ == ICEPROTO_RFC5245 || protocol_ == ICEPROTO_HYBRID)) { + // Offer is ICE or hybrid, we support ICE or hybrid: use ICE. + desc->transport_type = NS_JINGLE_ICE_UDP; + } else if (offer && offer->transport_type == NS_JINGLE_ICE_UDP && + offer->HasOption(ICE_OPTION_GICE) && + protocol_ == ICEPROTO_GOOGLE) { + desc->transport_type = NS_GINGLE_P2P; + // Offer is hybrid, we support GICE: use GICE. + } else if ((!offer || offer->transport_type == NS_GINGLE_P2P) && + (protocol_ == ICEPROTO_HYBRID || protocol_ == ICEPROTO_GOOGLE)) { + // Offer is GICE, we support hybrid or GICE: use GICE. + desc->transport_type = NS_GINGLE_P2P; + } else { + // Mismatch. + LOG(LS_WARNING) << "Failed to create TransportDescription answer " + "because of incompatible transport types"; + return NULL; + } + + // Generate the ICE credentials if we don't already have them or ice is + // being restarted. + if (!current_description || options.ice_restart) { + desc->ice_ufrag = rtc::CreateRandomString(ICE_UFRAG_LENGTH); + desc->ice_pwd = rtc::CreateRandomString(ICE_PWD_LENGTH); + } else { + desc->ice_ufrag = current_description->ice_ufrag; + desc->ice_pwd = current_description->ice_pwd; + } + + // Negotiate security params. + if (offer && offer->identity_fingerprint.get()) { + // The offer supports DTLS, so answer with DTLS, as long as we support it. + if (secure_ == SEC_ENABLED || secure_ == SEC_REQUIRED) { + // Fail if we can't create the fingerprint. + // Setting DTLS role to active. + ConnectionRole role = (options.prefer_passive_role) ? + CONNECTIONROLE_PASSIVE : CONNECTIONROLE_ACTIVE; + + if (!SetSecurityInfo(desc.get(), role)) { + return NULL; + } + } + } else if (secure_ == SEC_REQUIRED) { + // We require DTLS, but the other side didn't offer it. Fail. + LOG(LS_WARNING) << "Failed to create TransportDescription answer " + "because of incompatible security settings"; + return NULL; + } + + return desc.release(); +} + +bool TransportDescriptionFactory::SetSecurityInfo( + TransportDescription* desc, ConnectionRole role) const { + if (!identity_) { + LOG(LS_ERROR) << "Cannot create identity digest with no identity"; + return false; + } + + // This digest algorithm is used to produce the a=fingerprint lines in SDP. + // RFC 4572 Section 5 requires that those lines use the same hash function as + // the certificate's signature. + std::string digest_alg; + if (!identity_->certificate().GetSignatureDigestAlgorithm(&digest_alg)) { + LOG(LS_ERROR) << "Failed to retrieve the certificate's digest algorithm"; + return false; + } + + desc->identity_fingerprint.reset( + rtc::SSLFingerprint::Create(digest_alg, identity_)); + if (!desc->identity_fingerprint.get()) { + LOG(LS_ERROR) << "Failed to create identity fingerprint, alg=" + << digest_alg; + return false; + } + + // Assign security role. + desc->connection_role = role; + return true; +} + +} // namespace cricket + diff --git a/webrtc/p2p/base/transportdescriptionfactory.h b/webrtc/p2p/base/transportdescriptionfactory.h new file mode 100644 index 000000000..a137f7211 --- /dev/null +++ b/webrtc/p2p/base/transportdescriptionfactory.h @@ -0,0 +1,66 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_TRANSPORTDESCRIPTIONFACTORY_H_ +#define WEBRTC_P2P_BASE_TRANSPORTDESCRIPTIONFACTORY_H_ + +#include "webrtc/p2p/base/transportdescription.h" + +namespace rtc { +class SSLIdentity; +} + +namespace cricket { + +struct TransportOptions { + TransportOptions() : ice_restart(false), prefer_passive_role(false) {} + bool ice_restart; + bool prefer_passive_role; +}; + +// Creates transport descriptions according to the supplied configuration. +// When creating answers, performs the appropriate negotiation +// of the various fields to determine the proper result. +class TransportDescriptionFactory { + public: + // Default ctor; use methods below to set configuration. + TransportDescriptionFactory(); + SecurePolicy secure() const { return secure_; } + // The identity to use when setting up DTLS. + rtc::SSLIdentity* identity() const { return identity_; } + + // Specifies the transport protocol to be use. + void set_protocol(TransportProtocol protocol) { protocol_ = protocol; } + // Specifies the transport security policy to use. + void set_secure(SecurePolicy s) { secure_ = s; } + // Specifies the identity to use (only used when secure is not SEC_DISABLED). + void set_identity(rtc::SSLIdentity* identity) { identity_ = identity; } + + // Creates a transport description suitable for use in an offer. + TransportDescription* CreateOffer(const TransportOptions& options, + const TransportDescription* current_description) const; + // Create a transport description that is a response to an offer. + TransportDescription* CreateAnswer( + const TransportDescription* offer, + const TransportOptions& options, + const TransportDescription* current_description) const; + + private: + bool SetSecurityInfo(TransportDescription* description, + ConnectionRole role) const; + + TransportProtocol protocol_; + SecurePolicy secure_; + rtc::SSLIdentity* identity_; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_TRANSPORTDESCRIPTIONFACTORY_H_ diff --git a/webrtc/p2p/base/transportdescriptionfactory_unittest.cc b/webrtc/p2p/base/transportdescriptionfactory_unittest.cc new file mode 100644 index 000000000..22816a2f9 --- /dev/null +++ b/webrtc/p2p/base/transportdescriptionfactory_unittest.cc @@ -0,0 +1,365 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/transportdescription.h" +#include "webrtc/p2p/base/transportdescriptionfactory.h" +#include "webrtc/base/fakesslidentity.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/ssladapter.h" + +using rtc::scoped_ptr; +using cricket::TransportDescriptionFactory; +using cricket::TransportDescription; +using cricket::TransportOptions; + +class TransportDescriptionFactoryTest : public testing::Test { + public: + TransportDescriptionFactoryTest() + : id1_(new rtc::FakeSSLIdentity("User1")), + id2_(new rtc::FakeSSLIdentity("User2")) { + } + + void CheckDesc(const TransportDescription* desc, const std::string& type, + const std::string& opt, const std::string& ice_ufrag, + const std::string& ice_pwd, const std::string& dtls_alg) { + ASSERT_TRUE(desc != NULL); + EXPECT_EQ(type, desc->transport_type); + EXPECT_EQ(!opt.empty(), desc->HasOption(opt)); + if (ice_ufrag.empty() && ice_pwd.empty()) { + EXPECT_EQ(static_cast(cricket::ICE_UFRAG_LENGTH), + desc->ice_ufrag.size()); + EXPECT_EQ(static_cast(cricket::ICE_PWD_LENGTH), + desc->ice_pwd.size()); + } else { + EXPECT_EQ(ice_ufrag, desc->ice_ufrag); + EXPECT_EQ(ice_pwd, desc->ice_pwd); + } + if (dtls_alg.empty()) { + EXPECT_TRUE(desc->identity_fingerprint.get() == NULL); + } else { + ASSERT_TRUE(desc->identity_fingerprint.get() != NULL); + EXPECT_EQ(desc->identity_fingerprint->algorithm, dtls_alg); + EXPECT_GT(desc->identity_fingerprint->digest.length(), 0U); + } + } + + // This test ice restart by doing two offer answer exchanges. On the second + // exchange ice is restarted. The test verifies that the ufrag and password + // in the offer and answer is changed. + // If |dtls| is true, the test verifies that the finger print is not changed. + void TestIceRestart(bool dtls) { + if (dtls) { + f1_.set_secure(cricket::SEC_ENABLED); + f2_.set_secure(cricket::SEC_ENABLED); + f1_.set_identity(id1_.get()); + f2_.set_identity(id2_.get()); + } else { + f1_.set_secure(cricket::SEC_DISABLED); + f2_.set_secure(cricket::SEC_DISABLED); + } + + cricket::TransportOptions options; + // The initial offer / answer exchange. + rtc::scoped_ptr offer(f1_.CreateOffer( + options, NULL)); + rtc::scoped_ptr answer( + f2_.CreateAnswer(offer.get(), + options, NULL)); + + // Create an updated offer where we restart ice. + options.ice_restart = true; + rtc::scoped_ptr restart_offer(f1_.CreateOffer( + options, offer.get())); + + VerifyUfragAndPasswordChanged(dtls, offer.get(), restart_offer.get()); + + // Create a new answer. The transport ufrag and password is changed since + // |options.ice_restart == true| + rtc::scoped_ptr restart_answer( + f2_.CreateAnswer(restart_offer.get(), options, answer.get())); + ASSERT_TRUE(restart_answer.get() != NULL); + + VerifyUfragAndPasswordChanged(dtls, answer.get(), restart_answer.get()); + } + + void VerifyUfragAndPasswordChanged(bool dtls, + const TransportDescription* org_desc, + const TransportDescription* restart_desc) { + EXPECT_NE(org_desc->ice_pwd, restart_desc->ice_pwd); + EXPECT_NE(org_desc->ice_ufrag, restart_desc->ice_ufrag); + EXPECT_EQ(static_cast(cricket::ICE_UFRAG_LENGTH), + restart_desc->ice_ufrag.size()); + EXPECT_EQ(static_cast(cricket::ICE_PWD_LENGTH), + restart_desc->ice_pwd.size()); + // If DTLS is enabled, make sure the finger print is unchanged. + if (dtls) { + EXPECT_FALSE( + org_desc->identity_fingerprint->GetRfc4572Fingerprint().empty()); + EXPECT_EQ(org_desc->identity_fingerprint->GetRfc4572Fingerprint(), + restart_desc->identity_fingerprint->GetRfc4572Fingerprint()); + } + } + + protected: + TransportDescriptionFactory f1_; + TransportDescriptionFactory f2_; + scoped_ptr id1_; + scoped_ptr id2_; +}; + +// Test that in the default case, we generate the expected G-ICE offer. +TEST_F(TransportDescriptionFactoryTest, TestOfferGice) { + f1_.set_protocol(cricket::ICEPROTO_GOOGLE); + scoped_ptr desc(f1_.CreateOffer( + TransportOptions(), NULL)); + CheckDesc(desc.get(), cricket::NS_GINGLE_P2P, "", "", "", ""); +} + +// Test generating a hybrid offer. +TEST_F(TransportDescriptionFactoryTest, TestOfferHybrid) { + f1_.set_protocol(cricket::ICEPROTO_HYBRID); + scoped_ptr desc(f1_.CreateOffer( + TransportOptions(), NULL)); + CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "google-ice", "", "", ""); +} + +// Test generating an ICE-only offer. +TEST_F(TransportDescriptionFactoryTest, TestOfferIce) { + f1_.set_protocol(cricket::ICEPROTO_RFC5245); + scoped_ptr desc(f1_.CreateOffer( + TransportOptions(), NULL)); + CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", ""); +} + +// Test generating a hybrid offer with DTLS. +TEST_F(TransportDescriptionFactoryTest, TestOfferHybridDtls) { + f1_.set_protocol(cricket::ICEPROTO_HYBRID); + f1_.set_secure(cricket::SEC_ENABLED); + f1_.set_identity(id1_.get()); + std::string digest_alg; + ASSERT_TRUE(id1_->certificate().GetSignatureDigestAlgorithm(&digest_alg)); + scoped_ptr desc(f1_.CreateOffer( + TransportOptions(), NULL)); + CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "google-ice", "", "", + digest_alg); + // Ensure it also works with SEC_REQUIRED. + f1_.set_secure(cricket::SEC_REQUIRED); + desc.reset(f1_.CreateOffer(TransportOptions(), NULL)); + CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "google-ice", "", "", + digest_alg); +} + +// Test generating a hybrid offer with DTLS fails with no identity. +TEST_F(TransportDescriptionFactoryTest, TestOfferHybridDtlsWithNoIdentity) { + f1_.set_protocol(cricket::ICEPROTO_HYBRID); + f1_.set_secure(cricket::SEC_ENABLED); + scoped_ptr desc(f1_.CreateOffer( + TransportOptions(), NULL)); + ASSERT_TRUE(desc.get() == NULL); +} + +// Test updating a hybrid offer with DTLS to pick ICE. +// The ICE credentials should stay the same in the new offer. +TEST_F(TransportDescriptionFactoryTest, TestOfferHybridDtlsReofferIceDtls) { + f1_.set_protocol(cricket::ICEPROTO_HYBRID); + f1_.set_secure(cricket::SEC_ENABLED); + f1_.set_identity(id1_.get()); + std::string digest_alg; + ASSERT_TRUE(id1_->certificate().GetSignatureDigestAlgorithm(&digest_alg)); + scoped_ptr old_desc(f1_.CreateOffer( + TransportOptions(), NULL)); + ASSERT_TRUE(old_desc.get() != NULL); + f1_.set_protocol(cricket::ICEPROTO_RFC5245); + scoped_ptr desc( + f1_.CreateOffer(TransportOptions(), old_desc.get())); + CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", + old_desc->ice_ufrag, old_desc->ice_pwd, digest_alg); +} + +// Test that we can answer a GICE offer with GICE. +TEST_F(TransportDescriptionFactoryTest, TestAnswerGiceToGice) { + f1_.set_protocol(cricket::ICEPROTO_GOOGLE); + f2_.set_protocol(cricket::ICEPROTO_GOOGLE); + scoped_ptr offer(f1_.CreateOffer( + TransportOptions(), NULL)); + ASSERT_TRUE(offer.get() != NULL); + scoped_ptr desc(f2_.CreateAnswer( + offer.get(), TransportOptions(), NULL)); + CheckDesc(desc.get(), cricket::NS_GINGLE_P2P, "", "", "", ""); + // Should get the same result when answering as hybrid. + f2_.set_protocol(cricket::ICEPROTO_HYBRID); + desc.reset(f2_.CreateAnswer(offer.get(), TransportOptions(), + NULL)); + CheckDesc(desc.get(), cricket::NS_GINGLE_P2P, "", "", "", ""); +} + +// Test that we can answer a hybrid offer with GICE. +TEST_F(TransportDescriptionFactoryTest, TestAnswerGiceToHybrid) { + f1_.set_protocol(cricket::ICEPROTO_HYBRID); + f2_.set_protocol(cricket::ICEPROTO_GOOGLE); + scoped_ptr offer(f1_.CreateOffer( + TransportOptions(), NULL)); + ASSERT_TRUE(offer.get() != NULL); + scoped_ptr desc( + f2_.CreateAnswer(offer.get(), TransportOptions(), NULL)); + CheckDesc(desc.get(), cricket::NS_GINGLE_P2P, "", "", "", ""); +} + +// Test that we can answer a hybrid offer with ICE. +TEST_F(TransportDescriptionFactoryTest, TestAnswerIceToHybrid) { + f1_.set_protocol(cricket::ICEPROTO_HYBRID); + f2_.set_protocol(cricket::ICEPROTO_RFC5245); + scoped_ptr offer(f1_.CreateOffer( + TransportOptions(), NULL)); + ASSERT_TRUE(offer.get() != NULL); + scoped_ptr desc( + f2_.CreateAnswer(offer.get(), TransportOptions(), NULL)); + CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", ""); + // Should get the same result when answering as hybrid. + f2_.set_protocol(cricket::ICEPROTO_HYBRID); + desc.reset(f2_.CreateAnswer(offer.get(), TransportOptions(), + NULL)); + CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", ""); +} + +// Test that we can answer an ICE offer with ICE. +TEST_F(TransportDescriptionFactoryTest, TestAnswerIceToIce) { + f1_.set_protocol(cricket::ICEPROTO_RFC5245); + f2_.set_protocol(cricket::ICEPROTO_RFC5245); + scoped_ptr offer(f1_.CreateOffer( + TransportOptions(), NULL)); + ASSERT_TRUE(offer.get() != NULL); + scoped_ptr desc(f2_.CreateAnswer( + offer.get(), TransportOptions(), NULL)); + CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", ""); + // Should get the same result when answering as hybrid. + f2_.set_protocol(cricket::ICEPROTO_HYBRID); + desc.reset(f2_.CreateAnswer(offer.get(), TransportOptions(), + NULL)); + CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", ""); +} + +// Test that we can't answer a GICE offer with ICE. +TEST_F(TransportDescriptionFactoryTest, TestAnswerIceToGice) { + f1_.set_protocol(cricket::ICEPROTO_GOOGLE); + f2_.set_protocol(cricket::ICEPROTO_RFC5245); + scoped_ptr offer( + f1_.CreateOffer(TransportOptions(), NULL)); + ASSERT_TRUE(offer.get() != NULL); + scoped_ptr desc( + f2_.CreateAnswer(offer.get(), TransportOptions(), NULL)); + ASSERT_TRUE(desc.get() == NULL); +} + +// Test that we can't answer an ICE offer with GICE. +TEST_F(TransportDescriptionFactoryTest, TestAnswerGiceToIce) { + f1_.set_protocol(cricket::ICEPROTO_RFC5245); + f2_.set_protocol(cricket::ICEPROTO_GOOGLE); + scoped_ptr offer( + f1_.CreateOffer(TransportOptions(), NULL)); + ASSERT_TRUE(offer.get() != NULL); + scoped_ptr desc(f2_.CreateAnswer( + offer.get(), TransportOptions(), NULL)); + ASSERT_TRUE(desc.get() == NULL); +} + +// Test that we can update an answer properly; ICE credentials shouldn't change. +TEST_F(TransportDescriptionFactoryTest, TestAnswerIceToIceReanswer) { + f1_.set_protocol(cricket::ICEPROTO_RFC5245); + f2_.set_protocol(cricket::ICEPROTO_RFC5245); + scoped_ptr offer( + f1_.CreateOffer(TransportOptions(), NULL)); + ASSERT_TRUE(offer.get() != NULL); + scoped_ptr old_desc( + f2_.CreateAnswer(offer.get(), TransportOptions(), NULL)); + ASSERT_TRUE(old_desc.get() != NULL); + scoped_ptr desc( + f2_.CreateAnswer(offer.get(), TransportOptions(), + old_desc.get())); + ASSERT_TRUE(desc.get() != NULL); + CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", + old_desc->ice_ufrag, old_desc->ice_pwd, ""); +} + +// Test that we handle answering an offer with DTLS with no DTLS. +TEST_F(TransportDescriptionFactoryTest, TestAnswerHybridToHybridDtls) { + f1_.set_protocol(cricket::ICEPROTO_HYBRID); + f1_.set_secure(cricket::SEC_ENABLED); + f1_.set_identity(id1_.get()); + f2_.set_protocol(cricket::ICEPROTO_HYBRID); + scoped_ptr offer( + f1_.CreateOffer(TransportOptions(), NULL)); + ASSERT_TRUE(offer.get() != NULL); + scoped_ptr desc( + f2_.CreateAnswer(offer.get(), TransportOptions(), NULL)); + CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", ""); +} + +// Test that we handle answering an offer without DTLS if we have DTLS enabled, +// but fail if we require DTLS. +TEST_F(TransportDescriptionFactoryTest, TestAnswerHybridDtlsToHybrid) { + f1_.set_protocol(cricket::ICEPROTO_HYBRID); + f2_.set_protocol(cricket::ICEPROTO_HYBRID); + f2_.set_secure(cricket::SEC_ENABLED); + f2_.set_identity(id2_.get()); + scoped_ptr offer( + f1_.CreateOffer(TransportOptions(), NULL)); + ASSERT_TRUE(offer.get() != NULL); + scoped_ptr desc( + f2_.CreateAnswer(offer.get(), TransportOptions(), NULL)); + CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", ""); + f2_.set_secure(cricket::SEC_REQUIRED); + desc.reset(f2_.CreateAnswer(offer.get(), TransportOptions(), + NULL)); + ASSERT_TRUE(desc.get() == NULL); +} + +// Test that we handle answering an DTLS offer with DTLS, both if we have +// DTLS enabled and required. +TEST_F(TransportDescriptionFactoryTest, TestAnswerHybridDtlsToHybridDtls) { + f1_.set_protocol(cricket::ICEPROTO_HYBRID); + f1_.set_secure(cricket::SEC_ENABLED); + f1_.set_identity(id1_.get()); + + f2_.set_protocol(cricket::ICEPROTO_HYBRID); + f2_.set_secure(cricket::SEC_ENABLED); + f2_.set_identity(id2_.get()); + // f2_ produces the answer that is being checked in this test, so the + // answer must contain fingerprint lines with id2_'s digest algorithm. + std::string digest_alg2; + ASSERT_TRUE(id2_->certificate().GetSignatureDigestAlgorithm(&digest_alg2)); + + scoped_ptr offer( + f1_.CreateOffer(TransportOptions(), NULL)); + ASSERT_TRUE(offer.get() != NULL); + scoped_ptr desc( + f2_.CreateAnswer(offer.get(), TransportOptions(), NULL)); + CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", digest_alg2); + f2_.set_secure(cricket::SEC_REQUIRED); + desc.reset(f2_.CreateAnswer(offer.get(), TransportOptions(), + NULL)); + CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", digest_alg2); +} + +// Test that ice ufrag and password is changed in an updated offer and answer +// if |TransportDescriptionOptions::ice_restart| is true. +TEST_F(TransportDescriptionFactoryTest, TestIceRestart) { + TestIceRestart(false); +} + +// Test that ice ufrag and password is changed in an updated offer and answer +// if |TransportDescriptionOptions::ice_restart| is true and DTLS is enabled. +TEST_F(TransportDescriptionFactoryTest, TestIceRestartWithDtls) { + TestIceRestart(true); +} diff --git a/webrtc/p2p/base/transportinfo.h b/webrtc/p2p/base/transportinfo.h new file mode 100644 index 000000000..3fbf204d9 --- /dev/null +++ b/webrtc/p2p/base/transportinfo.h @@ -0,0 +1,43 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_TRANSPORTINFO_H_ +#define WEBRTC_P2P_BASE_TRANSPORTINFO_H_ + +#include +#include + +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/transportdescription.h" +#include "webrtc/base/helpers.h" + +namespace cricket { + +// A TransportInfo is NOT a transport-info message. It is comparable +// to a "ContentInfo". A transport-infos message is basically just a +// collection of TransportInfos. +struct TransportInfo { + TransportInfo() {} + + TransportInfo(const std::string& content_name, + const TransportDescription& description) + : content_name(content_name), + description(description) {} + + std::string content_name; + TransportDescription description; +}; + +typedef std::vector TransportInfos; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_TRANSPORTINFO_H_ diff --git a/webrtc/p2p/base/turnport.cc b/webrtc/p2p/base/turnport.cc new file mode 100644 index 000000000..e7626fe04 --- /dev/null +++ b/webrtc/p2p/base/turnport.cc @@ -0,0 +1,1196 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/turnport.h" + +#include + +#include "webrtc/p2p/base/common.h" +#include "webrtc/p2p/base/stun.h" +#include "webrtc/base/asyncpacketsocket.h" +#include "webrtc/base/byteorder.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/nethelpers.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/stringencode.h" + +namespace cricket { + +// TODO(juberti): Move to stun.h when relay messages have been renamed. +static const int TURN_ALLOCATE_REQUEST = STUN_ALLOCATE_REQUEST; + +// TODO(juberti): Extract to turnmessage.h +static const int TURN_DEFAULT_PORT = 3478; +static const int TURN_CHANNEL_NUMBER_START = 0x4000; +static const int TURN_PERMISSION_TIMEOUT = 5 * 60 * 1000; // 5 minutes + +static const size_t TURN_CHANNEL_HEADER_SIZE = 4U; + +// Retry at most twice (i.e. three different ALLOCATE requests) on +// STUN_ERROR_ALLOCATION_MISMATCH error per rfc5766. +static const size_t MAX_ALLOCATE_MISMATCH_RETRIES = 2; + +inline bool IsTurnChannelData(uint16 msg_type) { + return ((msg_type & 0xC000) == 0x4000); // MSB are 0b01 +} + +static int GetRelayPreference(cricket::ProtocolType proto, bool secure) { + int relay_preference = ICE_TYPE_PREFERENCE_RELAY; + if (proto == cricket::PROTO_TCP) { + relay_preference -= 1; + if (secure) + relay_preference -= 1; + } + + ASSERT(relay_preference >= 0); + return relay_preference; +} + +class TurnAllocateRequest : public StunRequest { + public: + explicit TurnAllocateRequest(TurnPort* port); + virtual void Prepare(StunMessage* request); + virtual void OnResponse(StunMessage* response); + virtual void OnErrorResponse(StunMessage* response); + virtual void OnTimeout(); + + private: + // Handles authentication challenge from the server. + void OnAuthChallenge(StunMessage* response, int code); + void OnTryAlternate(StunMessage* response, int code); + void OnUnknownAttribute(StunMessage* response); + + TurnPort* port_; +}; + +class TurnRefreshRequest : public StunRequest { + public: + explicit TurnRefreshRequest(TurnPort* port); + virtual void Prepare(StunMessage* request); + virtual void OnResponse(StunMessage* response); + virtual void OnErrorResponse(StunMessage* response); + virtual void OnTimeout(); + + private: + TurnPort* port_; +}; + +class TurnCreatePermissionRequest : public StunRequest, + public sigslot::has_slots<> { + public: + TurnCreatePermissionRequest(TurnPort* port, TurnEntry* entry, + const rtc::SocketAddress& ext_addr); + virtual void Prepare(StunMessage* request); + virtual void OnResponse(StunMessage* response); + virtual void OnErrorResponse(StunMessage* response); + virtual void OnTimeout(); + + private: + void OnEntryDestroyed(TurnEntry* entry); + + TurnPort* port_; + TurnEntry* entry_; + rtc::SocketAddress ext_addr_; +}; + +class TurnChannelBindRequest : public StunRequest, + public sigslot::has_slots<> { + public: + TurnChannelBindRequest(TurnPort* port, TurnEntry* entry, int channel_id, + const rtc::SocketAddress& ext_addr); + virtual void Prepare(StunMessage* request); + virtual void OnResponse(StunMessage* response); + virtual void OnErrorResponse(StunMessage* response); + virtual void OnTimeout(); + + private: + void OnEntryDestroyed(TurnEntry* entry); + + TurnPort* port_; + TurnEntry* entry_; + int channel_id_; + rtc::SocketAddress ext_addr_; +}; + +// Manages a "connection" to a remote destination. We will attempt to bring up +// a channel for this remote destination to reduce the overhead of sending data. +class TurnEntry : public sigslot::has_slots<> { + public: + enum BindState { STATE_UNBOUND, STATE_BINDING, STATE_BOUND }; + TurnEntry(TurnPort* port, int channel_id, + const rtc::SocketAddress& ext_addr); + + TurnPort* port() { return port_; } + + int channel_id() const { return channel_id_; } + const rtc::SocketAddress& address() const { return ext_addr_; } + BindState state() const { return state_; } + + // Helper methods to send permission and channel bind requests. + void SendCreatePermissionRequest(); + void SendChannelBindRequest(int delay); + // Sends a packet to the given destination address. + // This will wrap the packet in STUN if necessary. + int Send(const void* data, size_t size, bool payload, + const rtc::PacketOptions& options); + + void OnCreatePermissionSuccess(); + void OnCreatePermissionError(StunMessage* response, int code); + void OnChannelBindSuccess(); + void OnChannelBindError(StunMessage* response, int code); + // Signal sent when TurnEntry is destroyed. + sigslot::signal1 SignalDestroyed; + + private: + TurnPort* port_; + int channel_id_; + rtc::SocketAddress ext_addr_; + BindState state_; +}; + +TurnPort::TurnPort(rtc::Thread* thread, + rtc::PacketSocketFactory* factory, + rtc::Network* network, + rtc::AsyncPacketSocket* socket, + const std::string& username, + const std::string& password, + const ProtocolAddress& server_address, + const RelayCredentials& credentials, + int server_priority) + : Port(thread, factory, network, socket->GetLocalAddress().ipaddr(), + username, password), + server_address_(server_address), + credentials_(credentials), + socket_(socket), + resolver_(NULL), + error_(0), + request_manager_(thread), + next_channel_number_(TURN_CHANNEL_NUMBER_START), + connected_(false), + server_priority_(server_priority), + allocate_mismatch_retries_(0) { + request_manager_.SignalSendPacket.connect(this, &TurnPort::OnSendStunPacket); +} + +TurnPort::TurnPort(rtc::Thread* thread, + rtc::PacketSocketFactory* factory, + rtc::Network* network, + const rtc::IPAddress& ip, + int min_port, int max_port, + const std::string& username, + const std::string& password, + const ProtocolAddress& server_address, + const RelayCredentials& credentials, + int server_priority) + : Port(thread, RELAY_PORT_TYPE, factory, network, ip, min_port, max_port, + username, password), + server_address_(server_address), + credentials_(credentials), + socket_(NULL), + resolver_(NULL), + error_(0), + request_manager_(thread), + next_channel_number_(TURN_CHANNEL_NUMBER_START), + connected_(false), + server_priority_(server_priority), + allocate_mismatch_retries_(0) { + request_manager_.SignalSendPacket.connect(this, &TurnPort::OnSendStunPacket); +} + +TurnPort::~TurnPort() { + // TODO(juberti): Should this even be necessary? + while (!entries_.empty()) { + DestroyEntry(entries_.front()->address()); + } + if (resolver_) { + resolver_->Destroy(false); + } + if (!SharedSocket()) { + delete socket_; + } +} + +void TurnPort::PrepareAddress() { + if (credentials_.username.empty() || + credentials_.password.empty()) { + LOG(LS_ERROR) << "Allocation can't be started without setting the" + << " TURN server credentials for the user."; + OnAllocateError(); + return; + } + + if (!server_address_.address.port()) { + // We will set default TURN port, if no port is set in the address. + server_address_.address.SetPort(TURN_DEFAULT_PORT); + } + + if (server_address_.address.IsUnresolved()) { + ResolveTurnAddress(server_address_.address); + } else { + // If protocol family of server address doesn't match with local, return. + if (!IsCompatibleAddress(server_address_.address)) { + LOG(LS_ERROR) << "Server IP address family does not match with " + << "local host address family type"; + OnAllocateError(); + return; + } + + // Insert the current address to prevent redirection pingpong. + attempted_server_addresses_.insert(server_address_.address); + + LOG_J(LS_INFO, this) << "Trying to connect to TURN server via " + << ProtoToString(server_address_.proto) << " @ " + << server_address_.address.ToSensitiveString(); + if (!CreateTurnClientSocket()) { + OnAllocateError(); + } else if (server_address_.proto == PROTO_UDP) { + // If its UDP, send AllocateRequest now. + // For TCP and TLS AllcateRequest will be sent by OnSocketConnect. + SendRequest(new TurnAllocateRequest(this), 0); + } + } +} + +bool TurnPort::CreateTurnClientSocket() { + ASSERT(!socket_ || SharedSocket()); + + if (server_address_.proto == PROTO_UDP && !SharedSocket()) { + socket_ = socket_factory()->CreateUdpSocket( + rtc::SocketAddress(ip(), 0), min_port(), max_port()); + } else if (server_address_.proto == PROTO_TCP) { + ASSERT(!SharedSocket()); + int opts = rtc::PacketSocketFactory::OPT_STUN; + // If secure bit is enabled in server address, use TLS over TCP. + if (server_address_.secure) { + opts |= rtc::PacketSocketFactory::OPT_TLS; + } + socket_ = socket_factory()->CreateClientTcpSocket( + rtc::SocketAddress(ip(), 0), server_address_.address, + proxy(), user_agent(), opts); + } + + if (!socket_) { + error_ = SOCKET_ERROR; + return false; + } + + // Apply options if any. + for (SocketOptionsMap::iterator iter = socket_options_.begin(); + iter != socket_options_.end(); ++iter) { + socket_->SetOption(iter->first, iter->second); + } + + if (!SharedSocket()) { + // If socket is shared, AllocationSequence will receive the packet. + socket_->SignalReadPacket.connect(this, &TurnPort::OnReadPacket); + } + + socket_->SignalReadyToSend.connect(this, &TurnPort::OnReadyToSend); + + if (server_address_.proto == PROTO_TCP) { + socket_->SignalConnect.connect(this, &TurnPort::OnSocketConnect); + socket_->SignalClose.connect(this, &TurnPort::OnSocketClose); + } + return true; +} + +void TurnPort::OnSocketConnect(rtc::AsyncPacketSocket* socket) { + ASSERT(server_address_.proto == PROTO_TCP); + // Do not use this port if the socket bound to a different address than + // the one we asked for. This is seen in Chrome, where TCP sockets cannot be + // given a binding address, and the platform is expected to pick the + // correct local address. + if (socket->GetLocalAddress().ipaddr() != ip()) { + LOG(LS_WARNING) << "Socket is bound to a different address then the " + << "local port. Discarding TURN port."; + OnAllocateError(); + return; + } + + if (server_address_.address.IsUnresolved()) { + server_address_.address = socket_->GetRemoteAddress(); + } + + LOG(LS_INFO) << "TurnPort connected to " << socket->GetRemoteAddress() + << " using tcp."; + SendRequest(new TurnAllocateRequest(this), 0); +} + +void TurnPort::OnSocketClose(rtc::AsyncPacketSocket* socket, int error) { + LOG_J(LS_WARNING, this) << "Connection with server failed, error=" << error; + if (!connected_) { + OnAllocateError(); + } +} + +void TurnPort::OnAllocateMismatch() { + if (allocate_mismatch_retries_ >= MAX_ALLOCATE_MISMATCH_RETRIES) { + LOG_J(LS_WARNING, this) << "Giving up on the port after " + << allocate_mismatch_retries_ + << " retries for STUN_ERROR_ALLOCATION_MISMATCH"; + OnAllocateError(); + return; + } + + LOG_J(LS_INFO, this) << "Allocating a new socket after " + << "STUN_ERROR_ALLOCATION_MISMATCH, retry = " + << allocate_mismatch_retries_ + 1; + if (SharedSocket()) { + ResetSharedSocket(); + } else { + delete socket_; + } + socket_ = NULL; + + PrepareAddress(); + ++allocate_mismatch_retries_; +} + +Connection* TurnPort::CreateConnection(const Candidate& address, + CandidateOrigin origin) { + // TURN-UDP can only connect to UDP candidates. + if (address.protocol() != UDP_PROTOCOL_NAME) { + return NULL; + } + + if (!IsCompatibleAddress(address.address())) { + return NULL; + } + + // Create an entry, if needed, so we can get our permissions set up correctly. + CreateEntry(address.address()); + + // A TURN port will have two candiates, STUN and TURN. STUN may not + // present in all cases. If present stun candidate will be added first + // and TURN candidate later. + for (size_t index = 0; index < Candidates().size(); ++index) { + if (Candidates()[index].type() == RELAY_PORT_TYPE) { + ProxyConnection* conn = new ProxyConnection(this, index, address); + conn->SignalDestroyed.connect(this, &TurnPort::OnConnectionDestroyed); + AddConnection(conn); + return conn; + } + } + return NULL; +} + +int TurnPort::SetOption(rtc::Socket::Option opt, int value) { + if (!socket_) { + // If socket is not created yet, these options will be applied during socket + // creation. + socket_options_[opt] = value; + return 0; + } + return socket_->SetOption(opt, value); +} + +int TurnPort::GetOption(rtc::Socket::Option opt, int* value) { + if (!socket_) { + SocketOptionsMap::const_iterator it = socket_options_.find(opt); + if (it == socket_options_.end()) { + return -1; + } + *value = it->second; + return 0; + } + + return socket_->GetOption(opt, value); +} + +int TurnPort::GetError() { + return error_; +} + +int TurnPort::SendTo(const void* data, size_t size, + const rtc::SocketAddress& addr, + const rtc::PacketOptions& options, + bool payload) { + // Try to find an entry for this specific address; we should have one. + TurnEntry* entry = FindEntry(addr); + ASSERT(entry != NULL); + if (!entry) { + return 0; + } + + if (!connected()) { + error_ = EWOULDBLOCK; + return SOCKET_ERROR; + } + + // Send the actual contents to the server using the usual mechanism. + int sent = entry->Send(data, size, payload, options); + if (sent <= 0) { + return SOCKET_ERROR; + } + + // The caller of the function is expecting the number of user data bytes, + // rather than the size of the packet. + return static_cast(size); +} + +void TurnPort::OnReadPacket( + rtc::AsyncPacketSocket* socket, const char* data, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time) { + ASSERT(socket == socket_); + ASSERT(remote_addr == server_address_.address); + + // The message must be at least the size of a channel header. + if (size < TURN_CHANNEL_HEADER_SIZE) { + LOG_J(LS_WARNING, this) << "Received TURN message that was too short"; + return; + } + + // Check the message type, to see if is a Channel Data message. + // The message will either be channel data, a TURN data indication, or + // a response to a previous request. + uint16 msg_type = rtc::GetBE16(data); + if (IsTurnChannelData(msg_type)) { + HandleChannelData(msg_type, data, size, packet_time); + } else if (msg_type == TURN_DATA_INDICATION) { + HandleDataIndication(data, size, packet_time); + } else { + // This must be a response for one of our requests. + // Check success responses, but not errors, for MESSAGE-INTEGRITY. + if (IsStunSuccessResponseType(msg_type) && + !StunMessage::ValidateMessageIntegrity(data, size, hash())) { + LOG_J(LS_WARNING, this) << "Received TURN message with invalid " + << "message integrity, msg_type=" << msg_type; + return; + } + request_manager_.CheckResponse(data, size); + } +} + +void TurnPort::OnReadyToSend(rtc::AsyncPacketSocket* socket) { + if (connected_) { + Port::OnReadyToSend(); + } +} + + +// Update current server address port with the alternate server address port. +bool TurnPort::SetAlternateServer(const rtc::SocketAddress& address) { + // Check if we have seen this address before and reject if we did. + AttemptedServerSet::iterator iter = attempted_server_addresses_.find(address); + if (iter != attempted_server_addresses_.end()) { + LOG_J(LS_WARNING, this) << "Redirection to [" + << address.ToSensitiveString() + << "] ignored, allocation failed."; + return false; + } + + // If protocol family of server address doesn't match with local, return. + if (!IsCompatibleAddress(address)) { + LOG(LS_WARNING) << "Server IP address family does not match with " + << "local host address family type"; + return false; + } + + LOG_J(LS_INFO, this) << "Redirecting from TURN server [" + << server_address_.address.ToSensitiveString() + << "] to TURN server [" + << address.ToSensitiveString() + << "]"; + server_address_ = ProtocolAddress(address, server_address_.proto, + server_address_.secure); + + // Insert the current address to prevent redirection pingpong. + attempted_server_addresses_.insert(server_address_.address); + return true; +} + +void TurnPort::ResolveTurnAddress(const rtc::SocketAddress& address) { + if (resolver_) + return; + + resolver_ = socket_factory()->CreateAsyncResolver(); + resolver_->SignalDone.connect(this, &TurnPort::OnResolveResult); + resolver_->Start(address); +} + +void TurnPort::OnResolveResult(rtc::AsyncResolverInterface* resolver) { + ASSERT(resolver == resolver_); + // If DNS resolve is failed when trying to connect to the server using TCP, + // one of the reason could be due to DNS queries blocked by firewall. + // In such cases we will try to connect to the server with hostname, assuming + // socket layer will resolve the hostname through a HTTP proxy (if any). + if (resolver_->GetError() != 0 && server_address_.proto == PROTO_TCP) { + if (!CreateTurnClientSocket()) { + OnAllocateError(); + } + return; + } + + // Copy the original server address in |resolved_address|. For TLS based + // sockets we need hostname along with resolved address. + rtc::SocketAddress resolved_address = server_address_.address; + if (resolver_->GetError() != 0 || + !resolver_->GetResolvedAddress(ip().family(), &resolved_address)) { + LOG_J(LS_WARNING, this) << "TURN host lookup received error " + << resolver_->GetError(); + error_ = resolver_->GetError(); + OnAllocateError(); + return; + } + // Signal needs both resolved and unresolved address. After signal is sent + // we can copy resolved address back into |server_address_|. + SignalResolvedServerAddress(this, server_address_.address, + resolved_address); + server_address_.address = resolved_address; + PrepareAddress(); +} + +void TurnPort::OnSendStunPacket(const void* data, size_t size, + StunRequest* request) { + rtc::PacketOptions options(DefaultDscpValue()); + if (Send(data, size, options) < 0) { + LOG_J(LS_ERROR, this) << "Failed to send TURN message, err=" + << socket_->GetError(); + } +} + +void TurnPort::OnStunAddress(const rtc::SocketAddress& address) { + // STUN Port will discover STUN candidate, as it's supplied with first TURN + // server address. + // Why not using this address? - P2PTransportChannel will start creating + // connections after first candidate, which means it could start creating the + // connections before TURN candidate added. For that to handle, we need to + // supply STUN candidate from this port to UDPPort, and TurnPort should have + // handle to UDPPort to pass back the address. +} + +void TurnPort::OnAllocateSuccess(const rtc::SocketAddress& address, + const rtc::SocketAddress& stun_address) { + connected_ = true; + + rtc::SocketAddress related_address = stun_address; + if (!(candidate_filter() & CF_REFLEXIVE)) { + // If candidate filter only allows relay type of address, empty raddr to + // avoid local address leakage. + related_address = rtc::EmptySocketAddressWithFamily(stun_address.family()); + } + + // For relayed candidate, Base is the candidate itself. + AddAddress(address, // Candidate address. + address, // Base address. + related_address, // Related address. + UDP_PROTOCOL_NAME, + "", // TCP canddiate type, empty for turn candidates. + RELAY_PORT_TYPE, + GetRelayPreference(server_address_.proto, server_address_.secure), + server_priority_, + true); +} + +void TurnPort::OnAllocateError() { + // We will send SignalPortError asynchronously as this can be sent during + // port initialization. This way it will not be blocking other port + // creation. + thread()->Post(this, MSG_ERROR); +} + +void TurnPort::OnMessage(rtc::Message* message) { + if (message->message_id == MSG_ERROR) { + SignalPortError(this); + return; + } else if (message->message_id == MSG_ALLOCATE_MISMATCH) { + OnAllocateMismatch(); + return; + } + + Port::OnMessage(message); +} + +void TurnPort::OnAllocateRequestTimeout() { + OnAllocateError(); +} + +void TurnPort::HandleDataIndication(const char* data, size_t size, + const rtc::PacketTime& packet_time) { + // Read in the message, and process according to RFC5766, Section 10.4. + rtc::ByteBuffer buf(data, size); + TurnMessage msg; + if (!msg.Read(&buf)) { + LOG_J(LS_WARNING, this) << "Received invalid TURN data indication"; + return; + } + + // Check mandatory attributes. + const StunAddressAttribute* addr_attr = + msg.GetAddress(STUN_ATTR_XOR_PEER_ADDRESS); + if (!addr_attr) { + LOG_J(LS_WARNING, this) << "Missing STUN_ATTR_XOR_PEER_ADDRESS attribute " + << "in data indication."; + return; + } + + const StunByteStringAttribute* data_attr = + msg.GetByteString(STUN_ATTR_DATA); + if (!data_attr) { + LOG_J(LS_WARNING, this) << "Missing STUN_ATTR_DATA attribute in " + << "data indication."; + return; + } + + // Verify that the data came from somewhere we think we have a permission for. + rtc::SocketAddress ext_addr(addr_attr->GetAddress()); + if (!HasPermission(ext_addr.ipaddr())) { + LOG_J(LS_WARNING, this) << "Received TURN data indication with invalid " + << "peer address, addr=" + << ext_addr.ToSensitiveString(); + return; + } + + DispatchPacket(data_attr->bytes(), data_attr->length(), ext_addr, + PROTO_UDP, packet_time); +} + +void TurnPort::HandleChannelData(int channel_id, const char* data, + size_t size, + const rtc::PacketTime& packet_time) { + // Read the message, and process according to RFC5766, Section 11.6. + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Channel Number | Length | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | | + // / Application Data / + // / / + // | | + // | +-------------------------------+ + // | | + // +-------------------------------+ + + // Extract header fields from the message. + uint16 len = rtc::GetBE16(data + 2); + if (len > size - TURN_CHANNEL_HEADER_SIZE) { + LOG_J(LS_WARNING, this) << "Received TURN channel data message with " + << "incorrect length, len=" << len; + return; + } + // Allowing messages larger than |len|, as ChannelData can be padded. + + TurnEntry* entry = FindEntry(channel_id); + if (!entry) { + LOG_J(LS_WARNING, this) << "Received TURN channel data message for invalid " + << "channel, channel_id=" << channel_id; + return; + } + + DispatchPacket(data + TURN_CHANNEL_HEADER_SIZE, len, entry->address(), + PROTO_UDP, packet_time); +} + +void TurnPort::DispatchPacket(const char* data, size_t size, + const rtc::SocketAddress& remote_addr, + ProtocolType proto, const rtc::PacketTime& packet_time) { + if (Connection* conn = GetConnection(remote_addr)) { + conn->OnReadPacket(data, size, packet_time); + } else { + Port::OnReadPacket(data, size, remote_addr, proto); + } +} + +bool TurnPort::ScheduleRefresh(int lifetime) { + // Lifetime is in seconds; we schedule a refresh for one minute less. + if (lifetime < 2 * 60) { + LOG_J(LS_WARNING, this) << "Received response with lifetime that was " + << "too short, lifetime=" << lifetime; + return false; + } + + SendRequest(new TurnRefreshRequest(this), (lifetime - 60) * 1000); + return true; +} + +void TurnPort::SendRequest(StunRequest* req, int delay) { + request_manager_.SendDelayed(req, delay); +} + +void TurnPort::AddRequestAuthInfo(StunMessage* msg) { + // If we've gotten the necessary data from the server, add it to our request. + VERIFY(!hash_.empty()); + VERIFY(msg->AddAttribute(new StunByteStringAttribute( + STUN_ATTR_USERNAME, credentials_.username))); + VERIFY(msg->AddAttribute(new StunByteStringAttribute( + STUN_ATTR_REALM, realm_))); + VERIFY(msg->AddAttribute(new StunByteStringAttribute( + STUN_ATTR_NONCE, nonce_))); + VERIFY(msg->AddMessageIntegrity(hash())); +} + +int TurnPort::Send(const void* data, size_t len, + const rtc::PacketOptions& options) { + return socket_->SendTo(data, len, server_address_.address, options); +} + +void TurnPort::UpdateHash() { + VERIFY(ComputeStunCredentialHash(credentials_.username, realm_, + credentials_.password, &hash_)); +} + +bool TurnPort::UpdateNonce(StunMessage* response) { + // When stale nonce error received, we should update + // hash and store realm and nonce. + // Check the mandatory attributes. + const StunByteStringAttribute* realm_attr = + response->GetByteString(STUN_ATTR_REALM); + if (!realm_attr) { + LOG(LS_ERROR) << "Missing STUN_ATTR_REALM attribute in " + << "stale nonce error response."; + return false; + } + set_realm(realm_attr->GetString()); + + const StunByteStringAttribute* nonce_attr = + response->GetByteString(STUN_ATTR_NONCE); + if (!nonce_attr) { + LOG(LS_ERROR) << "Missing STUN_ATTR_NONCE attribute in " + << "stale nonce error response."; + return false; + } + set_nonce(nonce_attr->GetString()); + return true; +} + +static bool MatchesIP(TurnEntry* e, rtc::IPAddress ipaddr) { + return e->address().ipaddr() == ipaddr; +} +bool TurnPort::HasPermission(const rtc::IPAddress& ipaddr) const { + return (std::find_if(entries_.begin(), entries_.end(), + std::bind2nd(std::ptr_fun(MatchesIP), ipaddr)) != entries_.end()); +} + +static bool MatchesAddress(TurnEntry* e, rtc::SocketAddress addr) { + return e->address() == addr; +} +TurnEntry* TurnPort::FindEntry(const rtc::SocketAddress& addr) const { + EntryList::const_iterator it = std::find_if(entries_.begin(), entries_.end(), + std::bind2nd(std::ptr_fun(MatchesAddress), addr)); + return (it != entries_.end()) ? *it : NULL; +} + +static bool MatchesChannelId(TurnEntry* e, int id) { + return e->channel_id() == id; +} +TurnEntry* TurnPort::FindEntry(int channel_id) const { + EntryList::const_iterator it = std::find_if(entries_.begin(), entries_.end(), + std::bind2nd(std::ptr_fun(MatchesChannelId), channel_id)); + return (it != entries_.end()) ? *it : NULL; +} + +TurnEntry* TurnPort::CreateEntry(const rtc::SocketAddress& addr) { + ASSERT(FindEntry(addr) == NULL); + TurnEntry* entry = new TurnEntry(this, next_channel_number_++, addr); + entries_.push_back(entry); + return entry; +} + +void TurnPort::DestroyEntry(const rtc::SocketAddress& addr) { + TurnEntry* entry = FindEntry(addr); + ASSERT(entry != NULL); + entry->SignalDestroyed(entry); + entries_.remove(entry); + delete entry; +} + +void TurnPort::OnConnectionDestroyed(Connection* conn) { + // Destroying TurnEntry for the connection, which is already destroyed. + DestroyEntry(conn->remote_candidate().address()); +} + +TurnAllocateRequest::TurnAllocateRequest(TurnPort* port) + : StunRequest(new TurnMessage()), + port_(port) { +} + +void TurnAllocateRequest::Prepare(StunMessage* request) { + // Create the request as indicated in RFC 5766, Section 6.1. + request->SetType(TURN_ALLOCATE_REQUEST); + StunUInt32Attribute* transport_attr = StunAttribute::CreateUInt32( + STUN_ATTR_REQUESTED_TRANSPORT); + transport_attr->SetValue(IPPROTO_UDP << 24); + VERIFY(request->AddAttribute(transport_attr)); + if (!port_->hash().empty()) { + port_->AddRequestAuthInfo(request); + } +} + +void TurnAllocateRequest::OnResponse(StunMessage* response) { + // Check mandatory attributes as indicated in RFC5766, Section 6.3. + const StunAddressAttribute* mapped_attr = + response->GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS); + if (!mapped_attr) { + LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_XOR_MAPPED_ADDRESS " + << "attribute in allocate success response"; + return; + } + // Using XOR-Mapped-Address for stun. + port_->OnStunAddress(mapped_attr->GetAddress()); + + const StunAddressAttribute* relayed_attr = + response->GetAddress(STUN_ATTR_XOR_RELAYED_ADDRESS); + if (!relayed_attr) { + LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_XOR_RELAYED_ADDRESS " + << "attribute in allocate success response"; + return; + } + + const StunUInt32Attribute* lifetime_attr = + response->GetUInt32(STUN_ATTR_TURN_LIFETIME); + if (!lifetime_attr) { + LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_TURN_LIFETIME attribute in " + << "allocate success response"; + return; + } + // Notify the port the allocate succeeded, and schedule a refresh request. + port_->OnAllocateSuccess(relayed_attr->GetAddress(), + mapped_attr->GetAddress()); + port_->ScheduleRefresh(lifetime_attr->value()); +} + +void TurnAllocateRequest::OnErrorResponse(StunMessage* response) { + // Process error response according to RFC5766, Section 6.4. + const StunErrorCodeAttribute* error_code = response->GetErrorCode(); + switch (error_code->code()) { + case STUN_ERROR_UNAUTHORIZED: // Unauthrorized. + OnAuthChallenge(response, error_code->code()); + break; + case STUN_ERROR_TRY_ALTERNATE: + OnTryAlternate(response, error_code->code()); + break; + case STUN_ERROR_ALLOCATION_MISMATCH: + // We must handle this error async because trying to delete the socket in + // OnErrorResponse will cause a deadlock on the socket. + port_->thread()->Post(port_, TurnPort::MSG_ALLOCATE_MISMATCH); + break; + default: + LOG_J(LS_WARNING, port_) << "Allocate response error, code=" + << error_code->code(); + port_->OnAllocateError(); + } +} + +void TurnAllocateRequest::OnTimeout() { + LOG_J(LS_WARNING, port_) << "Allocate request timeout"; + port_->OnAllocateRequestTimeout(); +} + +void TurnAllocateRequest::OnAuthChallenge(StunMessage* response, int code) { + // If we failed to authenticate even after we sent our credentials, fail hard. + if (code == STUN_ERROR_UNAUTHORIZED && !port_->hash().empty()) { + LOG_J(LS_WARNING, port_) << "Failed to authenticate with the server " + << "after challenge."; + port_->OnAllocateError(); + return; + } + + // Check the mandatory attributes. + const StunByteStringAttribute* realm_attr = + response->GetByteString(STUN_ATTR_REALM); + if (!realm_attr) { + LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_REALM attribute in " + << "allocate unauthorized response."; + return; + } + port_->set_realm(realm_attr->GetString()); + + const StunByteStringAttribute* nonce_attr = + response->GetByteString(STUN_ATTR_NONCE); + if (!nonce_attr) { + LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_NONCE attribute in " + << "allocate unauthorized response."; + return; + } + port_->set_nonce(nonce_attr->GetString()); + + // Send another allocate request, with the received realm and nonce values. + port_->SendRequest(new TurnAllocateRequest(port_), 0); +} + +void TurnAllocateRequest::OnTryAlternate(StunMessage* response, int code) { + // TODO(guoweis): Currently, we only support UDP redirect + if (port_->server_address().proto != PROTO_UDP) { + LOG_J(LS_WARNING, port_) << "Receiving 300 Alternate Server on non-UDP " + << "allocating request from [" + << port_->server_address().address.ToSensitiveString() + << "], failed as currently not supported"; + port_->OnAllocateError(); + return; + } + + // According to RFC 5389 section 11, there are use cases where + // authentication of response is not possible, we're not validating + // message integrity. + + // Get the alternate server address attribute value. + const StunAddressAttribute* alternate_server_attr = + response->GetAddress(STUN_ATTR_ALTERNATE_SERVER); + if (!alternate_server_attr) { + LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_ALTERNATE_SERVER " + << "attribute in try alternate error response"; + port_->OnAllocateError(); + return; + } + if (!port_->SetAlternateServer(alternate_server_attr->GetAddress())) { + port_->OnAllocateError(); + return; + } + + // Check the attributes. + const StunByteStringAttribute* realm_attr = + response->GetByteString(STUN_ATTR_REALM); + if (realm_attr) { + LOG_J(LS_INFO, port_) << "Applying STUN_ATTR_REALM attribute in " + << "try alternate error response."; + port_->set_realm(realm_attr->GetString()); + } + + const StunByteStringAttribute* nonce_attr = + response->GetByteString(STUN_ATTR_NONCE); + if (nonce_attr) { + LOG_J(LS_INFO, port_) << "Applying STUN_ATTR_NONCE attribute in " + << "try alternate error response."; + port_->set_nonce(nonce_attr->GetString()); + } + + // Send another allocate request to alternate server, + // with the received realm and nonce values. + port_->SendRequest(new TurnAllocateRequest(port_), 0); +} + +TurnRefreshRequest::TurnRefreshRequest(TurnPort* port) + : StunRequest(new TurnMessage()), + port_(port) { +} + +void TurnRefreshRequest::Prepare(StunMessage* request) { + // Create the request as indicated in RFC 5766, Section 7.1. + // No attributes need to be included. + request->SetType(TURN_REFRESH_REQUEST); + port_->AddRequestAuthInfo(request); +} + +void TurnRefreshRequest::OnResponse(StunMessage* response) { + // Check mandatory attributes as indicated in RFC5766, Section 7.3. + const StunUInt32Attribute* lifetime_attr = + response->GetUInt32(STUN_ATTR_TURN_LIFETIME); + if (!lifetime_attr) { + LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_TURN_LIFETIME attribute in " + << "refresh success response."; + return; + } + + // Schedule a refresh based on the returned lifetime value. + port_->ScheduleRefresh(lifetime_attr->value()); +} + +void TurnRefreshRequest::OnErrorResponse(StunMessage* response) { + const StunErrorCodeAttribute* error_code = response->GetErrorCode(); + LOG_J(LS_WARNING, port_) << "Refresh response error, code=" + << error_code->code(); + + if (error_code->code() == STUN_ERROR_STALE_NONCE) { + if (port_->UpdateNonce(response)) { + // Send RefreshRequest immediately. + port_->SendRequest(new TurnRefreshRequest(port_), 0); + } + } +} + +void TurnRefreshRequest::OnTimeout() { +} + +TurnCreatePermissionRequest::TurnCreatePermissionRequest( + TurnPort* port, TurnEntry* entry, + const rtc::SocketAddress& ext_addr) + : StunRequest(new TurnMessage()), + port_(port), + entry_(entry), + ext_addr_(ext_addr) { + entry_->SignalDestroyed.connect( + this, &TurnCreatePermissionRequest::OnEntryDestroyed); +} + +void TurnCreatePermissionRequest::Prepare(StunMessage* request) { + // Create the request as indicated in RFC5766, Section 9.1. + request->SetType(TURN_CREATE_PERMISSION_REQUEST); + VERIFY(request->AddAttribute(new StunXorAddressAttribute( + STUN_ATTR_XOR_PEER_ADDRESS, ext_addr_))); + port_->AddRequestAuthInfo(request); +} + +void TurnCreatePermissionRequest::OnResponse(StunMessage* response) { + if (entry_) { + entry_->OnCreatePermissionSuccess(); + } +} + +void TurnCreatePermissionRequest::OnErrorResponse(StunMessage* response) { + if (entry_) { + const StunErrorCodeAttribute* error_code = response->GetErrorCode(); + entry_->OnCreatePermissionError(response, error_code->code()); + } +} + +void TurnCreatePermissionRequest::OnTimeout() { + LOG_J(LS_WARNING, port_) << "Create permission timeout"; +} + +void TurnCreatePermissionRequest::OnEntryDestroyed(TurnEntry* entry) { + ASSERT(entry_ == entry); + entry_ = NULL; +} + +TurnChannelBindRequest::TurnChannelBindRequest( + TurnPort* port, TurnEntry* entry, + int channel_id, const rtc::SocketAddress& ext_addr) + : StunRequest(new TurnMessage()), + port_(port), + entry_(entry), + channel_id_(channel_id), + ext_addr_(ext_addr) { + entry_->SignalDestroyed.connect( + this, &TurnChannelBindRequest::OnEntryDestroyed); +} + +void TurnChannelBindRequest::Prepare(StunMessage* request) { + // Create the request as indicated in RFC5766, Section 11.1. + request->SetType(TURN_CHANNEL_BIND_REQUEST); + VERIFY(request->AddAttribute(new StunUInt32Attribute( + STUN_ATTR_CHANNEL_NUMBER, channel_id_ << 16))); + VERIFY(request->AddAttribute(new StunXorAddressAttribute( + STUN_ATTR_XOR_PEER_ADDRESS, ext_addr_))); + port_->AddRequestAuthInfo(request); +} + +void TurnChannelBindRequest::OnResponse(StunMessage* response) { + if (entry_) { + entry_->OnChannelBindSuccess(); + // Refresh the channel binding just under the permission timeout + // threshold. The channel binding has a longer lifetime, but + // this is the easiest way to keep both the channel and the + // permission from expiring. + entry_->SendChannelBindRequest(TURN_PERMISSION_TIMEOUT - 60 * 1000); + } +} + +void TurnChannelBindRequest::OnErrorResponse(StunMessage* response) { + if (entry_) { + const StunErrorCodeAttribute* error_code = response->GetErrorCode(); + entry_->OnChannelBindError(response, error_code->code()); + } +} + +void TurnChannelBindRequest::OnTimeout() { + LOG_J(LS_WARNING, port_) << "Channel bind timeout"; +} + +void TurnChannelBindRequest::OnEntryDestroyed(TurnEntry* entry) { + ASSERT(entry_ == entry); + entry_ = NULL; +} + +TurnEntry::TurnEntry(TurnPort* port, int channel_id, + const rtc::SocketAddress& ext_addr) + : port_(port), + channel_id_(channel_id), + ext_addr_(ext_addr), + state_(STATE_UNBOUND) { + // Creating permission for |ext_addr_|. + SendCreatePermissionRequest(); +} + +void TurnEntry::SendCreatePermissionRequest() { + port_->SendRequest(new TurnCreatePermissionRequest( + port_, this, ext_addr_), 0); +} + +void TurnEntry::SendChannelBindRequest(int delay) { + port_->SendRequest(new TurnChannelBindRequest( + port_, this, channel_id_, ext_addr_), delay); +} + +int TurnEntry::Send(const void* data, size_t size, bool payload, + const rtc::PacketOptions& options) { + rtc::ByteBuffer buf; + if (state_ != STATE_BOUND) { + // If we haven't bound the channel yet, we have to use a Send Indication. + TurnMessage msg; + msg.SetType(TURN_SEND_INDICATION); + msg.SetTransactionID( + rtc::CreateRandomString(kStunTransactionIdLength)); + VERIFY(msg.AddAttribute(new StunXorAddressAttribute( + STUN_ATTR_XOR_PEER_ADDRESS, ext_addr_))); + VERIFY(msg.AddAttribute(new StunByteStringAttribute( + STUN_ATTR_DATA, data, size))); + VERIFY(msg.Write(&buf)); + + // If we're sending real data, request a channel bind that we can use later. + if (state_ == STATE_UNBOUND && payload) { + SendChannelBindRequest(0); + state_ = STATE_BINDING; + } + } else { + // If the channel is bound, we can send the data as a Channel Message. + buf.WriteUInt16(channel_id_); + buf.WriteUInt16(static_cast(size)); + buf.WriteBytes(reinterpret_cast(data), size); + } + return port_->Send(buf.Data(), buf.Length(), options); +} + +void TurnEntry::OnCreatePermissionSuccess() { + LOG_J(LS_INFO, port_) << "Create permission for " + << ext_addr_.ToSensitiveString() + << " succeeded"; + // For success result code will be 0. + port_->SignalCreatePermissionResult(port_, ext_addr_, 0); +} + +void TurnEntry::OnCreatePermissionError(StunMessage* response, int code) { + LOG_J(LS_WARNING, port_) << "Create permission for " + << ext_addr_.ToSensitiveString() + << " failed, code=" << code; + if (code == STUN_ERROR_STALE_NONCE) { + if (port_->UpdateNonce(response)) { + SendCreatePermissionRequest(); + } + } else { + // Send signal with error code. + port_->SignalCreatePermissionResult(port_, ext_addr_, code); + } +} + +void TurnEntry::OnChannelBindSuccess() { + LOG_J(LS_INFO, port_) << "Channel bind for " << ext_addr_.ToSensitiveString() + << " succeeded"; + ASSERT(state_ == STATE_BINDING || state_ == STATE_BOUND); + state_ = STATE_BOUND; +} + +void TurnEntry::OnChannelBindError(StunMessage* response, int code) { + // TODO(mallinath) - Implement handling of error response for channel + // bind request as per http://tools.ietf.org/html/rfc5766#section-11.3 + LOG_J(LS_WARNING, port_) << "Channel bind for " + << ext_addr_.ToSensitiveString() + << " failed, code=" << code; + if (code == STUN_ERROR_STALE_NONCE) { + if (port_->UpdateNonce(response)) { + // Send channel bind request with fresh nonce. + SendChannelBindRequest(0); + } + } +} + +} // namespace cricket diff --git a/webrtc/p2p/base/turnport.h b/webrtc/p2p/base/turnport.h new file mode 100644 index 000000000..17fad1764 --- /dev/null +++ b/webrtc/p2p/base/turnport.h @@ -0,0 +1,237 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_TURNPORT_H_ +#define WEBRTC_P2P_BASE_TURNPORT_H_ + +#include +#include +#include +#include + +#include "webrtc/p2p/base/port.h" +#include "webrtc/p2p/client/basicportallocator.h" +#include "webrtc/base/asyncpacketsocket.h" + +namespace rtc { +class AsyncResolver; +class SignalThread; +} + +namespace cricket { + +extern const char TURN_PORT_TYPE[]; +class TurnAllocateRequest; +class TurnEntry; + +class TurnPort : public Port { + public: + static TurnPort* Create(rtc::Thread* thread, + rtc::PacketSocketFactory* factory, + rtc::Network* network, + rtc::AsyncPacketSocket* socket, + const std::string& username, // ice username. + const std::string& password, // ice password. + const ProtocolAddress& server_address, + const RelayCredentials& credentials, + int server_priority) { + return new TurnPort(thread, factory, network, socket, + username, password, server_address, + credentials, server_priority); + } + + static TurnPort* Create(rtc::Thread* thread, + rtc::PacketSocketFactory* factory, + rtc::Network* network, + const rtc::IPAddress& ip, + int min_port, int max_port, + const std::string& username, // ice username. + const std::string& password, // ice password. + const ProtocolAddress& server_address, + const RelayCredentials& credentials, + int server_priority) { + return new TurnPort(thread, factory, network, ip, min_port, max_port, + username, password, server_address, credentials, + server_priority); + } + + virtual ~TurnPort(); + + const ProtocolAddress& server_address() const { return server_address_; } + + bool connected() const { return connected_; } + const RelayCredentials& credentials() const { return credentials_; } + + virtual void PrepareAddress(); + virtual Connection* CreateConnection( + const Candidate& c, PortInterface::CandidateOrigin origin); + virtual int SendTo(const void* data, size_t size, + const rtc::SocketAddress& addr, + const rtc::PacketOptions& options, + bool payload); + virtual int SetOption(rtc::Socket::Option opt, int value); + virtual int GetOption(rtc::Socket::Option opt, int* value); + virtual int GetError(); + + virtual bool HandleIncomingPacket( + rtc::AsyncPacketSocket* socket, const char* data, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time) { + OnReadPacket(socket, data, size, remote_addr, packet_time); + return true; + } + virtual void OnReadPacket(rtc::AsyncPacketSocket* socket, + const char* data, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time); + + virtual void OnReadyToSend(rtc::AsyncPacketSocket* socket); + + void OnSocketConnect(rtc::AsyncPacketSocket* socket); + void OnSocketClose(rtc::AsyncPacketSocket* socket, int error); + + + const std::string& hash() const { return hash_; } + const std::string& nonce() const { return nonce_; } + + int error() const { return error_; } + + void OnAllocateMismatch(); + + rtc::AsyncPacketSocket* socket() const { + return socket_; + } + + // Signal with resolved server address. + // Parameters are port, server address and resolved server address. + // This signal will be sent only if server address is resolved successfully. + sigslot::signal3 SignalResolvedServerAddress; + + // This signal is only for testing purpose. + sigslot::signal3 + SignalCreatePermissionResult; + + protected: + TurnPort(rtc::Thread* thread, + rtc::PacketSocketFactory* factory, + rtc::Network* network, + rtc::AsyncPacketSocket* socket, + const std::string& username, + const std::string& password, + const ProtocolAddress& server_address, + const RelayCredentials& credentials, + int server_priority); + + TurnPort(rtc::Thread* thread, + rtc::PacketSocketFactory* factory, + rtc::Network* network, + const rtc::IPAddress& ip, + int min_port, int max_port, + const std::string& username, + const std::string& password, + const ProtocolAddress& server_address, + const RelayCredentials& credentials, + int server_priority); + + private: + enum { + MSG_ERROR = MSG_FIRST_AVAILABLE, + MSG_ALLOCATE_MISMATCH + }; + + typedef std::list EntryList; + typedef std::map SocketOptionsMap; + typedef std::set AttemptedServerSet; + + virtual void OnMessage(rtc::Message* pmsg); + + bool CreateTurnClientSocket(); + + void set_nonce(const std::string& nonce) { nonce_ = nonce; } + void set_realm(const std::string& realm) { + if (realm != realm_) { + realm_ = realm; + UpdateHash(); + } + } + + bool SetAlternateServer(const rtc::SocketAddress& address); + void ResolveTurnAddress(const rtc::SocketAddress& address); + void OnResolveResult(rtc::AsyncResolverInterface* resolver); + + void AddRequestAuthInfo(StunMessage* msg); + void OnSendStunPacket(const void* data, size_t size, StunRequest* request); + // Stun address from allocate success response. + // Currently used only for testing. + void OnStunAddress(const rtc::SocketAddress& address); + void OnAllocateSuccess(const rtc::SocketAddress& address, + const rtc::SocketAddress& stun_address); + void OnAllocateError(); + void OnAllocateRequestTimeout(); + + void HandleDataIndication(const char* data, size_t size, + const rtc::PacketTime& packet_time); + void HandleChannelData(int channel_id, const char* data, size_t size, + const rtc::PacketTime& packet_time); + void DispatchPacket(const char* data, size_t size, + const rtc::SocketAddress& remote_addr, + ProtocolType proto, const rtc::PacketTime& packet_time); + + bool ScheduleRefresh(int lifetime); + void SendRequest(StunRequest* request, int delay); + int Send(const void* data, size_t size, + const rtc::PacketOptions& options); + void UpdateHash(); + bool UpdateNonce(StunMessage* response); + + bool HasPermission(const rtc::IPAddress& ipaddr) const; + TurnEntry* FindEntry(const rtc::SocketAddress& address) const; + TurnEntry* FindEntry(int channel_id) const; + TurnEntry* CreateEntry(const rtc::SocketAddress& address); + void DestroyEntry(const rtc::SocketAddress& address); + void OnConnectionDestroyed(Connection* conn); + + ProtocolAddress server_address_; + RelayCredentials credentials_; + AttemptedServerSet attempted_server_addresses_; + + rtc::AsyncPacketSocket* socket_; + SocketOptionsMap socket_options_; + rtc::AsyncResolverInterface* resolver_; + int error_; + + StunRequestManager request_manager_; + std::string realm_; // From 401/438 response message. + std::string nonce_; // From 401/438 response message. + std::string hash_; // Digest of username:realm:password + + int next_channel_number_; + EntryList entries_; + + bool connected_; + // By default the value will be set to 0. This value will be used in + // calculating the candidate priority. + int server_priority_; + + // The number of retries made due to allocate mismatch error. + size_t allocate_mismatch_retries_; + + friend class TurnEntry; + friend class TurnAllocateRequest; + friend class TurnRefreshRequest; + friend class TurnCreatePermissionRequest; + friend class TurnChannelBindRequest; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_TURNPORT_H_ diff --git a/webrtc/p2p/base/turnport_unittest.cc b/webrtc/p2p/base/turnport_unittest.cc new file mode 100644 index 000000000..4b524d5b0 --- /dev/null +++ b/webrtc/p2p/base/turnport_unittest.cc @@ -0,0 +1,668 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#if defined(WEBRTC_POSIX) +#include +#endif + +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/tcpport.h" +#include "webrtc/p2p/base/testturnserver.h" +#include "webrtc/p2p/base/turnport.h" +#include "webrtc/p2p/base/udpport.h" +#include "webrtc/base/asynctcpsocket.h" +#include "webrtc/base/buffer.h" +#include "webrtc/base/dscp.h" +#include "webrtc/base/firewallsocketserver.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/ssladapter.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/virtualsocketserver.h" + +using rtc::SocketAddress; +using cricket::Connection; +using cricket::Port; +using cricket::PortInterface; +using cricket::TurnPort; +using cricket::UDPPort; + +static const SocketAddress kLocalAddr1("11.11.11.11", 0); +static const SocketAddress kLocalAddr2("22.22.22.22", 0); +static const SocketAddress kLocalIPv6Addr( + "2401:fa00:4:1000:be30:5bff:fee5:c3", 0); +static const SocketAddress kTurnUdpIntAddr("99.99.99.3", + cricket::TURN_SERVER_PORT); +static const SocketAddress kTurnTcpIntAddr("99.99.99.4", + cricket::TURN_SERVER_PORT); +static const SocketAddress kTurnUdpExtAddr("99.99.99.5", 0); +static const SocketAddress kTurnAlternateUdpIntAddr( + "99.99.99.6", cricket::TURN_SERVER_PORT); +static const SocketAddress kTurnUdpIPv6IntAddr( + "2400:4030:1:2c00:be30:abcd:efab:cdef", cricket::TURN_SERVER_PORT); +static const SocketAddress kTurnUdpIPv6ExtAddr( + "2620:0:1000:1b03:2e41:38ff:fea6:f2a4", 0); + +static const char kIceUfrag1[] = "TESTICEUFRAG0001"; +static const char kIceUfrag2[] = "TESTICEUFRAG0002"; +static const char kIcePwd1[] = "TESTICEPWD00000000000001"; +static const char kIcePwd2[] = "TESTICEPWD00000000000002"; +static const char kTurnUsername[] = "test"; +static const char kTurnPassword[] = "test"; +static const unsigned int kTimeout = 1000; + +static const cricket::ProtocolAddress kTurnUdpProtoAddr( + kTurnUdpIntAddr, cricket::PROTO_UDP); +static const cricket::ProtocolAddress kTurnTcpProtoAddr( + kTurnTcpIntAddr, cricket::PROTO_TCP); +static const cricket::ProtocolAddress kTurnUdpIPv6ProtoAddr( + kTurnUdpIPv6IntAddr, cricket::PROTO_UDP); + +static const unsigned int MSG_TESTFINISH = 0; + +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) +static int GetFDCount() { + struct dirent *dp; + int fd_count = 0; + DIR *dir = opendir("/proc/self/fd/"); + while ((dp = readdir(dir)) != NULL) { + if (dp->d_name[0] == '.') + continue; + ++fd_count; + } + closedir(dir); + return fd_count; +} +#endif + +class TurnPortTest : public testing::Test, + public sigslot::has_slots<>, + public rtc::MessageHandler { + public: + TurnPortTest() + : main_(rtc::Thread::Current()), + pss_(new rtc::PhysicalSocketServer), + ss_(new rtc::VirtualSocketServer(pss_.get())), + ss_scope_(ss_.get()), + network_("unittest", "unittest", rtc::IPAddress(INADDR_ANY), 32), + socket_factory_(rtc::Thread::Current()), + turn_server_(main_, kTurnUdpIntAddr, kTurnUdpExtAddr), + turn_ready_(false), + turn_error_(false), + turn_unknown_address_(false), + turn_create_permission_success_(false), + udp_ready_(false), + test_finish_(false) { + network_.AddIP(rtc::IPAddress(INADDR_ANY)); + } + + virtual void OnMessage(rtc::Message* msg) { + ASSERT(msg->message_id == MSG_TESTFINISH); + if (msg->message_id == MSG_TESTFINISH) + test_finish_ = true; + } + + void OnTurnPortComplete(Port* port) { + turn_ready_ = true; + } + void OnTurnPortError(Port* port) { + turn_error_ = true; + } + void OnTurnUnknownAddress(PortInterface* port, const SocketAddress& addr, + cricket::ProtocolType proto, + cricket::IceMessage* msg, const std::string& rf, + bool /*port_muxed*/) { + turn_unknown_address_ = true; + } + void OnTurnCreatePermissionResult(TurnPort* port, const SocketAddress& addr, + int code) { + // Ignoring the address. + if (code == 0) { + turn_create_permission_success_ = true; + } + } + void OnTurnReadPacket(Connection* conn, const char* data, size_t size, + const rtc::PacketTime& packet_time) { + turn_packets_.push_back(rtc::Buffer(data, size)); + } + void OnUdpPortComplete(Port* port) { + udp_ready_ = true; + } + void OnUdpReadPacket(Connection* conn, const char* data, size_t size, + const rtc::PacketTime& packet_time) { + udp_packets_.push_back(rtc::Buffer(data, size)); + } + void OnSocketReadPacket(rtc::AsyncPacketSocket* socket, + const char* data, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time) { + turn_port_->HandleIncomingPacket(socket, data, size, remote_addr, + packet_time); + } + rtc::AsyncSocket* CreateServerSocket(const SocketAddress addr) { + rtc::AsyncSocket* socket = ss_->CreateAsyncSocket(SOCK_STREAM); + EXPECT_GE(socket->Bind(addr), 0); + EXPECT_GE(socket->Listen(5), 0); + return socket; + } + + void CreateTurnPort(const std::string& username, + const std::string& password, + const cricket::ProtocolAddress& server_address) { + CreateTurnPort(kLocalAddr1, username, password, server_address); + } + void CreateTurnPort(const rtc::SocketAddress& local_address, + const std::string& username, + const std::string& password, + const cricket::ProtocolAddress& server_address) { + cricket::RelayCredentials credentials(username, password); + turn_port_.reset(TurnPort::Create(main_, &socket_factory_, &network_, + local_address.ipaddr(), 0, 0, + kIceUfrag1, kIcePwd1, + server_address, credentials, 0)); + // Set ICE protocol type to ICEPROTO_RFC5245, as port by default will be + // in Hybrid mode. Protocol type is necessary to send correct type STUN ping + // messages. + // This TURN port will be the controlling. + turn_port_->SetIceProtocolType(cricket::ICEPROTO_RFC5245); + turn_port_->SetIceRole(cricket::ICEROLE_CONTROLLING); + ConnectSignals(); + } + + void CreateSharedTurnPort(const std::string& username, + const std::string& password, + const cricket::ProtocolAddress& server_address) { + ASSERT(server_address.proto == cricket::PROTO_UDP); + + if (!socket_) { + socket_.reset(socket_factory_.CreateUdpSocket( + rtc::SocketAddress(kLocalAddr1.ipaddr(), 0), 0, 0)); + ASSERT_TRUE(socket_ != NULL); + socket_->SignalReadPacket.connect( + this, &TurnPortTest::OnSocketReadPacket); + } + + cricket::RelayCredentials credentials(username, password); + turn_port_.reset(cricket::TurnPort::Create( + main_, &socket_factory_, &network_, socket_.get(), + kIceUfrag1, kIcePwd1, server_address, credentials, 0)); + // Set ICE protocol type to ICEPROTO_RFC5245, as port by default will be + // in Hybrid mode. Protocol type is necessary to send correct type STUN ping + // messages. + // This TURN port will be the controlling. + turn_port_->SetIceProtocolType(cricket::ICEPROTO_RFC5245); + turn_port_->SetIceRole(cricket::ICEROLE_CONTROLLING); + ConnectSignals(); + } + + void ConnectSignals() { + turn_port_->SignalPortComplete.connect(this, + &TurnPortTest::OnTurnPortComplete); + turn_port_->SignalPortError.connect(this, + &TurnPortTest::OnTurnPortError); + turn_port_->SignalUnknownAddress.connect(this, + &TurnPortTest::OnTurnUnknownAddress); + turn_port_->SignalCreatePermissionResult.connect(this, + &TurnPortTest::OnTurnCreatePermissionResult); + } + void CreateUdpPort() { + udp_port_.reset(UDPPort::Create(main_, &socket_factory_, &network_, + kLocalAddr2.ipaddr(), 0, 0, + kIceUfrag2, kIcePwd2)); + // Set protocol type to RFC5245, as turn port is also in same mode. + // UDP port will be controlled. + udp_port_->SetIceProtocolType(cricket::ICEPROTO_RFC5245); + udp_port_->SetIceRole(cricket::ICEROLE_CONTROLLED); + udp_port_->SignalPortComplete.connect( + this, &TurnPortTest::OnUdpPortComplete); + } + + void TestTurnConnection() { + // Create ports and prepare addresses. + ASSERT_TRUE(turn_port_ != NULL); + turn_port_->PrepareAddress(); + ASSERT_TRUE_WAIT(turn_ready_, kTimeout); + CreateUdpPort(); + udp_port_->PrepareAddress(); + ASSERT_TRUE_WAIT(udp_ready_, kTimeout); + + // Send ping from UDP to TURN. + Connection* conn1 = udp_port_->CreateConnection( + turn_port_->Candidates()[0], Port::ORIGIN_MESSAGE); + ASSERT_TRUE(conn1 != NULL); + conn1->Ping(0); + WAIT(!turn_unknown_address_, kTimeout); + EXPECT_FALSE(turn_unknown_address_); + EXPECT_EQ(Connection::STATE_READ_INIT, conn1->read_state()); + EXPECT_EQ(Connection::STATE_WRITE_INIT, conn1->write_state()); + + // Send ping from TURN to UDP. + Connection* conn2 = turn_port_->CreateConnection( + udp_port_->Candidates()[0], Port::ORIGIN_MESSAGE); + ASSERT_TRUE(conn2 != NULL); + ASSERT_TRUE_WAIT(turn_create_permission_success_, kTimeout); + conn2->Ping(0); + + EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, conn2->write_state(), kTimeout); + EXPECT_EQ(Connection::STATE_READABLE, conn1->read_state()); + EXPECT_EQ(Connection::STATE_READ_INIT, conn2->read_state()); + EXPECT_EQ(Connection::STATE_WRITE_INIT, conn1->write_state()); + + // Send another ping from UDP to TURN. + conn1->Ping(0); + EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, conn1->write_state(), kTimeout); + EXPECT_EQ(Connection::STATE_READABLE, conn2->read_state()); + } + + void TestTurnSendData() { + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_ready_, kTimeout); + CreateUdpPort(); + udp_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(udp_ready_, kTimeout); + // Create connections and send pings. + Connection* conn1 = turn_port_->CreateConnection( + udp_port_->Candidates()[0], Port::ORIGIN_MESSAGE); + Connection* conn2 = udp_port_->CreateConnection( + turn_port_->Candidates()[0], Port::ORIGIN_MESSAGE); + ASSERT_TRUE(conn1 != NULL); + ASSERT_TRUE(conn2 != NULL); + conn1->SignalReadPacket.connect(static_cast(this), + &TurnPortTest::OnTurnReadPacket); + conn2->SignalReadPacket.connect(static_cast(this), + &TurnPortTest::OnUdpReadPacket); + conn1->Ping(0); + EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, conn1->write_state(), kTimeout); + conn2->Ping(0); + EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, conn2->write_state(), kTimeout); + + // Send some data. + size_t num_packets = 256; + for (size_t i = 0; i < num_packets; ++i) { + unsigned char buf[256] = { 0 }; + for (size_t j = 0; j < i + 1; ++j) { + buf[j] = 0xFF - static_cast(j); + } + conn1->Send(buf, i + 1, options); + conn2->Send(buf, i + 1, options); + main_->ProcessMessages(0); + } + + // Check the data. + ASSERT_EQ_WAIT(num_packets, turn_packets_.size(), kTimeout); + ASSERT_EQ_WAIT(num_packets, udp_packets_.size(), kTimeout); + for (size_t i = 0; i < num_packets; ++i) { + EXPECT_EQ(i + 1, turn_packets_[i].length()); + EXPECT_EQ(i + 1, udp_packets_[i].length()); + EXPECT_EQ(turn_packets_[i], udp_packets_[i]); + } + } + + protected: + rtc::Thread* main_; + rtc::scoped_ptr pss_; + rtc::scoped_ptr ss_; + rtc::SocketServerScope ss_scope_; + rtc::Network network_; + rtc::BasicPacketSocketFactory socket_factory_; + rtc::scoped_ptr socket_; + cricket::TestTurnServer turn_server_; + rtc::scoped_ptr turn_port_; + rtc::scoped_ptr udp_port_; + bool turn_ready_; + bool turn_error_; + bool turn_unknown_address_; + bool turn_create_permission_success_; + bool udp_ready_; + bool test_finish_; + std::vector turn_packets_; + std::vector udp_packets_; + rtc::PacketOptions options; +}; + +// Do a normal TURN allocation. +TEST_F(TurnPortTest, TestTurnAllocate) { + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + EXPECT_EQ(0, turn_port_->SetOption(rtc::Socket::OPT_SNDBUF, 10*1024)); + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_ready_, kTimeout); + ASSERT_EQ(1U, turn_port_->Candidates().size()); + EXPECT_EQ(kTurnUdpExtAddr.ipaddr(), + turn_port_->Candidates()[0].address().ipaddr()); + EXPECT_NE(0, turn_port_->Candidates()[0].address().port()); +} + +// Testing a normal UDP allocation using TCP connection. +TEST_F(TurnPortTest, TestTurnTcpAllocate) { + turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr); + EXPECT_EQ(0, turn_port_->SetOption(rtc::Socket::OPT_SNDBUF, 10*1024)); + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_ready_, kTimeout); + ASSERT_EQ(1U, turn_port_->Candidates().size()); + EXPECT_EQ(kTurnUdpExtAddr.ipaddr(), + turn_port_->Candidates()[0].address().ipaddr()); + EXPECT_NE(0, turn_port_->Candidates()[0].address().port()); +} + +// Testing turn port will attempt to create TCP socket on address resolution +// failure. +TEST_F(TurnPortTest, DISABLED_TestTurnTcpOnAddressResolveFailure) { + turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP); + CreateTurnPort(kTurnUsername, kTurnPassword, cricket::ProtocolAddress( + rtc::SocketAddress("www.webrtc-blah-blah.com", 3478), + cricket::PROTO_TCP)); + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_error_, kTimeout); + // As VSS doesn't provide a DNS resolution, name resolve will fail. TurnPort + // will proceed in creating a TCP socket which will fail as there is no + // server on the above domain and error will be set to SOCKET_ERROR. + EXPECT_EQ(SOCKET_ERROR, turn_port_->error()); +} + +// In case of UDP on address resolve failure, TurnPort will not create socket +// and return allocate failure. +TEST_F(TurnPortTest, DISABLED_TestTurnUdpOnAdressResolveFailure) { + CreateTurnPort(kTurnUsername, kTurnPassword, cricket::ProtocolAddress( + rtc::SocketAddress("www.webrtc-blah-blah.com", 3478), + cricket::PROTO_UDP)); + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_error_, kTimeout); + // Error from turn port will not be socket error. + EXPECT_NE(SOCKET_ERROR, turn_port_->error()); +} + +// Try to do a TURN allocation with an invalid password. +TEST_F(TurnPortTest, TestTurnAllocateBadPassword) { + CreateTurnPort(kTurnUsername, "bad", kTurnUdpProtoAddr); + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_error_, kTimeout); + ASSERT_EQ(0U, turn_port_->Candidates().size()); +} + +// Tests that a new local address is created after +// STUN_ERROR_ALLOCATION_MISMATCH. +TEST_F(TurnPortTest, TestTurnAllocateMismatch) { + // Do a normal allocation first. + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_ready_, kTimeout); + rtc::SocketAddress first_addr(turn_port_->socket()->GetLocalAddress()); + + // Forces the socket server to assign the same port. + ss_->SetNextPortForTesting(first_addr.port()); + + turn_ready_ = false; + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + turn_port_->PrepareAddress(); + + // Verifies that the new port has the same address. + EXPECT_EQ(first_addr, turn_port_->socket()->GetLocalAddress()); + + EXPECT_TRUE_WAIT(turn_ready_, kTimeout); + + // Verifies that the new port has a different address now. + EXPECT_NE(first_addr, turn_port_->socket()->GetLocalAddress()); +} + +// Tests that a shared-socket-TurnPort creates its own socket after +// STUN_ERROR_ALLOCATION_MISMATCH. +TEST_F(TurnPortTest, TestSharedSocketAllocateMismatch) { + // Do a normal allocation first. + CreateSharedTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_ready_, kTimeout); + rtc::SocketAddress first_addr(turn_port_->socket()->GetLocalAddress()); + + turn_ready_ = false; + CreateSharedTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + + // Verifies that the new port has the same address. + EXPECT_EQ(first_addr, turn_port_->socket()->GetLocalAddress()); + EXPECT_TRUE(turn_port_->SharedSocket()); + + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_ready_, kTimeout); + + // Verifies that the new port has a different address now. + EXPECT_NE(first_addr, turn_port_->socket()->GetLocalAddress()); + EXPECT_FALSE(turn_port_->SharedSocket()); +} + +TEST_F(TurnPortTest, TestTurnTcpAllocateMismatch) { + turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr); + + // Do a normal allocation first. + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_ready_, kTimeout); + rtc::SocketAddress first_addr(turn_port_->socket()->GetLocalAddress()); + + // Forces the socket server to assign the same port. + ss_->SetNextPortForTesting(first_addr.port()); + + turn_ready_ = false; + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr); + turn_port_->PrepareAddress(); + + // Verifies that the new port has the same address. + EXPECT_EQ(first_addr, turn_port_->socket()->GetLocalAddress()); + + EXPECT_TRUE_WAIT(turn_ready_, kTimeout); + + // Verifies that the new port has a different address now. + EXPECT_NE(first_addr, turn_port_->socket()->GetLocalAddress()); +} + +// Do a TURN allocation and try to send a packet to it from the outside. +// The packet should be dropped. Then, try to send a packet from TURN to the +// outside. It should reach its destination. Finally, try again from the +// outside. It should now work as well. +TEST_F(TurnPortTest, TestTurnConnection) { + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + TestTurnConnection(); +} + +// Similar to above, except that this test will use the shared socket. +TEST_F(TurnPortTest, TestTurnConnectionUsingSharedSocket) { + CreateSharedTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + TestTurnConnection(); +} + +// Test that we can establish a TCP connection with TURN server. +TEST_F(TurnPortTest, TestTurnTcpConnection) { + turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr); + TestTurnConnection(); +} + +// Test that we fail to create a connection when we want to use TLS over TCP. +// This test should be removed once we have TLS support. +TEST_F(TurnPortTest, TestTurnTlsTcpConnectionFails) { + cricket::ProtocolAddress secure_addr(kTurnTcpProtoAddr.address, + kTurnTcpProtoAddr.proto, + true); + CreateTurnPort(kTurnUsername, kTurnPassword, secure_addr); + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_error_, kTimeout); + ASSERT_EQ(0U, turn_port_->Candidates().size()); +} + +// Test try-alternate-server feature. +TEST_F(TurnPortTest, TestTurnAlternateServer) { + std::vector redirect_addresses; + redirect_addresses.push_back(kTurnAlternateUdpIntAddr); + + cricket::TestTurnRedirector redirector(redirect_addresses); + turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr, + cricket::PROTO_UDP); + turn_server_.set_redirect_hook(&redirector); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + + // Retrieve the address before we run the state machine. + const SocketAddress old_addr = turn_port_->server_address().address; + + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_ready_, kTimeout); + // Retrieve the address again, the turn port's address should be + // changed. + const SocketAddress new_addr = turn_port_->server_address().address; + EXPECT_NE(old_addr, new_addr); + ASSERT_EQ(1U, turn_port_->Candidates().size()); + EXPECT_EQ(kTurnUdpExtAddr.ipaddr(), + turn_port_->Candidates()[0].address().ipaddr()); + EXPECT_NE(0, turn_port_->Candidates()[0].address().port()); +} + +// Test that we fail when we redirect to an address different from +// current IP family. +TEST_F(TurnPortTest, TestTurnAlternateServerV4toV6) { + std::vector redirect_addresses; + redirect_addresses.push_back(kTurnUdpIPv6IntAddr); + + cricket::TestTurnRedirector redirector(redirect_addresses); + turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr, + cricket::PROTO_UDP); + turn_server_.set_redirect_hook(&redirector); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_error_, kTimeout); +} + +// Test that we fail to handle alternate-server response over TCP protocol. +TEST_F(TurnPortTest, TestTurnAlternateServerTcp) { + std::vector redirect_addresses; + redirect_addresses.push_back(kTurnAlternateUdpIntAddr); + + cricket::TestTurnRedirector redirector(redirect_addresses); + turn_server_.set_redirect_hook(&redirector); + turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr); + + turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr, cricket::PROTO_TCP); + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_error_, kTimeout); +} + +// Test try-alternate-server catches the case of pingpong. +TEST_F(TurnPortTest, TestTurnAlternateServerPingPong) { + std::vector redirect_addresses; + redirect_addresses.push_back(kTurnAlternateUdpIntAddr); + redirect_addresses.push_back(kTurnUdpIntAddr); + + cricket::TestTurnRedirector redirector(redirect_addresses); + + turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr, + cricket::PROTO_UDP); + turn_server_.set_redirect_hook(&redirector); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_error_, kTimeout); + ASSERT_EQ(0U, turn_port_->Candidates().size()); + rtc::SocketAddress address; + // Verify that we have exhausted all alternate servers instead of + // failure caused by other errors. + EXPECT_FALSE(redirector.ShouldRedirect(address, &address)); +} + +// Test try-alternate-server catch the case of repeated server. +TEST_F(TurnPortTest, TestTurnAlternateServerDetectRepetition) { + std::vector redirect_addresses; + redirect_addresses.push_back(kTurnAlternateUdpIntAddr); + redirect_addresses.push_back(kTurnAlternateUdpIntAddr); + + cricket::TestTurnRedirector redirector(redirect_addresses); + + turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr, + cricket::PROTO_UDP); + turn_server_.set_redirect_hook(&redirector); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_error_, kTimeout); + ASSERT_EQ(0U, turn_port_->Candidates().size()); +} + + +// Run TurnConnectionTest with one-time-use nonce feature. +// Here server will send a 438 STALE_NONCE error message for +// every TURN transaction. +TEST_F(TurnPortTest, TestTurnConnectionUsingOTUNonce) { + turn_server_.set_enable_otu_nonce(true); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + TestTurnConnection(); +} + +// Do a TURN allocation, establish a UDP connection, and send some data. +TEST_F(TurnPortTest, TestTurnSendDataTurnUdpToUdp) { + // Create ports and prepare addresses. + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + TestTurnSendData(); +} + +// Do a TURN allocation, establish a TCP connection, and send some data. +TEST_F(TurnPortTest, TestTurnSendDataTurnTcpToUdp) { + turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP); + // Create ports and prepare addresses. + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr); + TestTurnSendData(); +} + +// Test TURN fails to make a connection from IPv6 address to a server which has +// IPv4 address. +TEST_F(TurnPortTest, TestTurnLocalIPv6AddressServerIPv4) { + turn_server_.AddInternalSocket(kTurnUdpIPv6IntAddr, cricket::PROTO_UDP); + CreateTurnPort(kLocalIPv6Addr, kTurnUsername, kTurnPassword, + kTurnUdpProtoAddr); + turn_port_->PrepareAddress(); + ASSERT_TRUE_WAIT(turn_error_, kTimeout); + EXPECT_TRUE(turn_port_->Candidates().empty()); +} + +// Test TURN make a connection from IPv6 address to a server which has +// IPv6 intenal address. But in this test external address is a IPv4 address, +// hence allocated address will be a IPv4 address. +TEST_F(TurnPortTest, TestTurnLocalIPv6AddressServerIPv6ExtenalIPv4) { + turn_server_.AddInternalSocket(kTurnUdpIPv6IntAddr, cricket::PROTO_UDP); + CreateTurnPort(kLocalIPv6Addr, kTurnUsername, kTurnPassword, + kTurnUdpIPv6ProtoAddr); + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_ready_, kTimeout); + ASSERT_EQ(1U, turn_port_->Candidates().size()); + EXPECT_EQ(kTurnUdpExtAddr.ipaddr(), + turn_port_->Candidates()[0].address().ipaddr()); + EXPECT_NE(0, turn_port_->Candidates()[0].address().port()); +} + +// This test verifies any FD's are not leaked after TurnPort is destroyed. +// https://code.google.com/p/webrtc/issues/detail?id=2651 +#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) +TEST_F(TurnPortTest, TestResolverShutdown) { + turn_server_.AddInternalSocket(kTurnUdpIPv6IntAddr, cricket::PROTO_UDP); + int last_fd_count = GetFDCount(); + // Need to supply unresolved address to kick off resolver. + CreateTurnPort(kLocalIPv6Addr, kTurnUsername, kTurnPassword, + cricket::ProtocolAddress(rtc::SocketAddress( + "stun.l.google.com", 3478), cricket::PROTO_UDP)); + turn_port_->PrepareAddress(); + ASSERT_TRUE_WAIT(turn_error_, kTimeout); + EXPECT_TRUE(turn_port_->Candidates().empty()); + turn_port_.reset(); + rtc::Thread::Current()->Post(this, MSG_TESTFINISH); + // Waiting for above message to be processed. + ASSERT_TRUE_WAIT(test_finish_, kTimeout); + EXPECT_EQ(last_fd_count, GetFDCount()); +} +#endif diff --git a/webrtc/p2p/base/turnserver.cc b/webrtc/p2p/base/turnserver.cc new file mode 100644 index 000000000..8605e98da --- /dev/null +++ b/webrtc/p2p/base/turnserver.cc @@ -0,0 +1,1011 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/turnserver.h" + +#include "webrtc/p2p/base/asyncstuntcpsocket.h" +#include "webrtc/p2p/base/common.h" +#include "webrtc/p2p/base/packetsocketfactory.h" +#include "webrtc/p2p/base/stun.h" +#include "webrtc/base/bytebuffer.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/messagedigest.h" +#include "webrtc/base/socketadapters.h" +#include "webrtc/base/stringencode.h" +#include "webrtc/base/thread.h" + +namespace cricket { + +// TODO(juberti): Move this all to a future turnmessage.h +//static const int IPPROTO_UDP = 17; +static const int kNonceTimeout = 60 * 60 * 1000; // 60 minutes +static const int kDefaultAllocationTimeout = 10 * 60 * 1000; // 10 minutes +static const int kPermissionTimeout = 5 * 60 * 1000; // 5 minutes +static const int kChannelTimeout = 10 * 60 * 1000; // 10 minutes + +static const int kMinChannelNumber = 0x4000; +static const int kMaxChannelNumber = 0x7FFF; + +static const size_t kNonceKeySize = 16; +static const size_t kNonceSize = 40; + +static const size_t TURN_CHANNEL_HEADER_SIZE = 4U; + +// TODO(mallinath) - Move these to a common place. +inline bool IsTurnChannelData(uint16 msg_type) { + // The first two bits of a channel data message are 0b01. + return ((msg_type & 0xC000) == 0x4000); +} + +// IDs used for posted messages for TurnServer::Allocation. +enum { + MSG_ALLOCATION_TIMEOUT, +}; + +// Encapsulates a TURN allocation. +// The object is created when an allocation request is received, and then +// handles TURN messages (via HandleTurnMessage) and channel data messages +// (via HandleChannelData) for this allocation when received by the server. +// The object self-deletes and informs the server if its lifetime timer expires. +class TurnServer::Allocation : public rtc::MessageHandler, + public sigslot::has_slots<> { + public: + Allocation(TurnServer* server_, + rtc::Thread* thread, const Connection& conn, + rtc::AsyncPacketSocket* server_socket, + const std::string& key); + virtual ~Allocation(); + + Connection* conn() { return &conn_; } + const std::string& key() const { return key_; } + const std::string& transaction_id() const { return transaction_id_; } + const std::string& username() const { return username_; } + const std::string& last_nonce() const { return last_nonce_; } + void set_last_nonce(const std::string& nonce) { last_nonce_ = nonce; } + + std::string ToString() const; + + void HandleTurnMessage(const TurnMessage* msg); + void HandleChannelData(const char* data, size_t size); + + sigslot::signal1 SignalDestroyed; + + private: + typedef std::list PermissionList; + typedef std::list ChannelList; + + void HandleAllocateRequest(const TurnMessage* msg); + void HandleRefreshRequest(const TurnMessage* msg); + void HandleSendIndication(const TurnMessage* msg); + void HandleCreatePermissionRequest(const TurnMessage* msg); + void HandleChannelBindRequest(const TurnMessage* msg); + + void OnExternalPacket(rtc::AsyncPacketSocket* socket, + const char* data, size_t size, + const rtc::SocketAddress& addr, + const rtc::PacketTime& packet_time); + + static int ComputeLifetime(const TurnMessage* msg); + bool HasPermission(const rtc::IPAddress& addr); + void AddPermission(const rtc::IPAddress& addr); + Permission* FindPermission(const rtc::IPAddress& addr) const; + Channel* FindChannel(int channel_id) const; + Channel* FindChannel(const rtc::SocketAddress& addr) const; + + void SendResponse(TurnMessage* msg); + void SendBadRequestResponse(const TurnMessage* req); + void SendErrorResponse(const TurnMessage* req, int code, + const std::string& reason); + void SendExternal(const void* data, size_t size, + const rtc::SocketAddress& peer); + + void OnPermissionDestroyed(Permission* perm); + void OnChannelDestroyed(Channel* channel); + virtual void OnMessage(rtc::Message* msg); + + TurnServer* server_; + rtc::Thread* thread_; + Connection conn_; + rtc::scoped_ptr external_socket_; + std::string key_; + std::string transaction_id_; + std::string username_; + std::string last_nonce_; + PermissionList perms_; + ChannelList channels_; +}; + +// Encapsulates a TURN permission. +// The object is created when a create permission request is received by an +// allocation, and self-deletes when its lifetime timer expires. +class TurnServer::Permission : public rtc::MessageHandler { + public: + Permission(rtc::Thread* thread, const rtc::IPAddress& peer); + ~Permission(); + + const rtc::IPAddress& peer() const { return peer_; } + void Refresh(); + + sigslot::signal1 SignalDestroyed; + + private: + virtual void OnMessage(rtc::Message* msg); + + rtc::Thread* thread_; + rtc::IPAddress peer_; +}; + +// Encapsulates a TURN channel binding. +// The object is created when a channel bind request is received by an +// allocation, and self-deletes when its lifetime timer expires. +class TurnServer::Channel : public rtc::MessageHandler { + public: + Channel(rtc::Thread* thread, int id, + const rtc::SocketAddress& peer); + ~Channel(); + + int id() const { return id_; } + const rtc::SocketAddress& peer() const { return peer_; } + void Refresh(); + + sigslot::signal1 SignalDestroyed; + + private: + virtual void OnMessage(rtc::Message* msg); + + rtc::Thread* thread_; + int id_; + rtc::SocketAddress peer_; +}; + +static bool InitResponse(const StunMessage* req, StunMessage* resp) { + int resp_type = (req) ? GetStunSuccessResponseType(req->type()) : -1; + if (resp_type == -1) + return false; + resp->SetType(resp_type); + resp->SetTransactionID(req->transaction_id()); + return true; +} + +static bool InitErrorResponse(const StunMessage* req, int code, + const std::string& reason, StunMessage* resp) { + int resp_type = (req) ? GetStunErrorResponseType(req->type()) : -1; + if (resp_type == -1) + return false; + resp->SetType(resp_type); + resp->SetTransactionID(req->transaction_id()); + VERIFY(resp->AddAttribute(new cricket::StunErrorCodeAttribute( + STUN_ATTR_ERROR_CODE, code, reason))); + return true; +} + +TurnServer::TurnServer(rtc::Thread* thread) + : thread_(thread), + nonce_key_(rtc::CreateRandomString(kNonceKeySize)), + auth_hook_(NULL), + redirect_hook_(NULL), + enable_otu_nonce_(false) { +} + +TurnServer::~TurnServer() { + for (AllocationMap::iterator it = allocations_.begin(); + it != allocations_.end(); ++it) { + delete it->second; + } + + for (InternalSocketMap::iterator it = server_sockets_.begin(); + it != server_sockets_.end(); ++it) { + rtc::AsyncPacketSocket* socket = it->first; + delete socket; + } + + for (ServerSocketMap::iterator it = server_listen_sockets_.begin(); + it != server_listen_sockets_.end(); ++it) { + rtc::AsyncSocket* socket = it->first; + delete socket; + } +} + +void TurnServer::AddInternalSocket(rtc::AsyncPacketSocket* socket, + ProtocolType proto) { + ASSERT(server_sockets_.end() == server_sockets_.find(socket)); + server_sockets_[socket] = proto; + socket->SignalReadPacket.connect(this, &TurnServer::OnInternalPacket); +} + +void TurnServer::AddInternalServerSocket(rtc::AsyncSocket* socket, + ProtocolType proto) { + ASSERT(server_listen_sockets_.end() == + server_listen_sockets_.find(socket)); + server_listen_sockets_[socket] = proto; + socket->SignalReadEvent.connect(this, &TurnServer::OnNewInternalConnection); +} + +void TurnServer::SetExternalSocketFactory( + rtc::PacketSocketFactory* factory, + const rtc::SocketAddress& external_addr) { + external_socket_factory_.reset(factory); + external_addr_ = external_addr; +} + +void TurnServer::OnNewInternalConnection(rtc::AsyncSocket* socket) { + ASSERT(server_listen_sockets_.find(socket) != server_listen_sockets_.end()); + AcceptConnection(socket); +} + +void TurnServer::AcceptConnection(rtc::AsyncSocket* server_socket) { + // Check if someone is trying to connect to us. + rtc::SocketAddress accept_addr; + rtc::AsyncSocket* accepted_socket = server_socket->Accept(&accept_addr); + if (accepted_socket != NULL) { + ProtocolType proto = server_listen_sockets_[server_socket]; + cricket::AsyncStunTCPSocket* tcp_socket = + new cricket::AsyncStunTCPSocket(accepted_socket, false); + + tcp_socket->SignalClose.connect(this, &TurnServer::OnInternalSocketClose); + // Finally add the socket so it can start communicating with the client. + AddInternalSocket(tcp_socket, proto); + } +} + +void TurnServer::OnInternalSocketClose(rtc::AsyncPacketSocket* socket, + int err) { + DestroyInternalSocket(socket); +} + +void TurnServer::OnInternalPacket(rtc::AsyncPacketSocket* socket, + const char* data, size_t size, + const rtc::SocketAddress& addr, + const rtc::PacketTime& packet_time) { + // Fail if the packet is too small to even contain a channel header. + if (size < TURN_CHANNEL_HEADER_SIZE) { + return; + } + InternalSocketMap::iterator iter = server_sockets_.find(socket); + ASSERT(iter != server_sockets_.end()); + Connection conn(addr, iter->second, socket); + uint16 msg_type = rtc::GetBE16(data); + if (!IsTurnChannelData(msg_type)) { + // This is a STUN message. + HandleStunMessage(&conn, data, size); + } else { + // This is a channel message; let the allocation handle it. + Allocation* allocation = FindAllocation(&conn); + if (allocation) { + allocation->HandleChannelData(data, size); + } + } +} + +void TurnServer::HandleStunMessage(Connection* conn, const char* data, + size_t size) { + TurnMessage msg; + rtc::ByteBuffer buf(data, size); + if (!msg.Read(&buf) || (buf.Length() > 0)) { + LOG(LS_WARNING) << "Received invalid STUN message"; + return; + } + + // If it's a STUN binding request, handle that specially. + if (msg.type() == STUN_BINDING_REQUEST) { + HandleBindingRequest(conn, &msg); + return; + } + + if (redirect_hook_ != NULL && msg.type() == STUN_ALLOCATE_REQUEST) { + rtc::SocketAddress address; + if (redirect_hook_->ShouldRedirect(conn->src(), &address)) { + SendErrorResponseWithAlternateServer( + conn, &msg, address); + return; + } + } + + // Look up the key that we'll use to validate the M-I. If we have an + // existing allocation, the key will already be cached. + Allocation* allocation = FindAllocation(conn); + std::string key; + if (!allocation) { + GetKey(&msg, &key); + } else { + key = allocation->key(); + } + + // Ensure the message is authorized; only needed for requests. + if (IsStunRequestType(msg.type())) { + if (!CheckAuthorization(conn, &msg, data, size, key)) { + return; + } + } + + if (!allocation && msg.type() == STUN_ALLOCATE_REQUEST) { + HandleAllocateRequest(conn, &msg, key); + } else if (allocation && + (msg.type() != STUN_ALLOCATE_REQUEST || + msg.transaction_id() == allocation->transaction_id())) { + // This is a non-allocate request, or a retransmit of an allocate. + // Check that the username matches the previous username used. + if (IsStunRequestType(msg.type()) && + msg.GetByteString(STUN_ATTR_USERNAME)->GetString() != + allocation->username()) { + SendErrorResponse(conn, &msg, STUN_ERROR_WRONG_CREDENTIALS, + STUN_ERROR_REASON_WRONG_CREDENTIALS); + return; + } + allocation->HandleTurnMessage(&msg); + } else { + // Allocation mismatch. + SendErrorResponse(conn, &msg, STUN_ERROR_ALLOCATION_MISMATCH, + STUN_ERROR_REASON_ALLOCATION_MISMATCH); + } +} + +bool TurnServer::GetKey(const StunMessage* msg, std::string* key) { + const StunByteStringAttribute* username_attr = + msg->GetByteString(STUN_ATTR_USERNAME); + if (!username_attr) { + return false; + } + + std::string username = username_attr->GetString(); + return (auth_hook_ != NULL && auth_hook_->GetKey(username, realm_, key)); +} + +bool TurnServer::CheckAuthorization(Connection* conn, + const StunMessage* msg, + const char* data, size_t size, + const std::string& key) { + // RFC 5389, 10.2.2. + ASSERT(IsStunRequestType(msg->type())); + const StunByteStringAttribute* mi_attr = + msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY); + const StunByteStringAttribute* username_attr = + msg->GetByteString(STUN_ATTR_USERNAME); + const StunByteStringAttribute* realm_attr = + msg->GetByteString(STUN_ATTR_REALM); + const StunByteStringAttribute* nonce_attr = + msg->GetByteString(STUN_ATTR_NONCE); + + // Fail if no M-I. + if (!mi_attr) { + SendErrorResponseWithRealmAndNonce(conn, msg, STUN_ERROR_UNAUTHORIZED, + STUN_ERROR_REASON_UNAUTHORIZED); + return false; + } + + // Fail if there is M-I but no username, nonce, or realm. + if (!username_attr || !realm_attr || !nonce_attr) { + SendErrorResponse(conn, msg, STUN_ERROR_BAD_REQUEST, + STUN_ERROR_REASON_BAD_REQUEST); + return false; + } + + // Fail if bad nonce. + if (!ValidateNonce(nonce_attr->GetString())) { + SendErrorResponseWithRealmAndNonce(conn, msg, STUN_ERROR_STALE_NONCE, + STUN_ERROR_REASON_STALE_NONCE); + return false; + } + + // Fail if bad username or M-I. + // We need |data| and |size| for the call to ValidateMessageIntegrity. + if (key.empty() || !StunMessage::ValidateMessageIntegrity(data, size, key)) { + SendErrorResponseWithRealmAndNonce(conn, msg, STUN_ERROR_UNAUTHORIZED, + STUN_ERROR_REASON_UNAUTHORIZED); + return false; + } + + // Fail if one-time-use nonce feature is enabled. + Allocation* allocation = FindAllocation(conn); + if (enable_otu_nonce_ && allocation && + allocation->last_nonce() == nonce_attr->GetString()) { + SendErrorResponseWithRealmAndNonce(conn, msg, STUN_ERROR_STALE_NONCE, + STUN_ERROR_REASON_STALE_NONCE); + return false; + } + + if (allocation) { + allocation->set_last_nonce(nonce_attr->GetString()); + } + // Success. + return true; +} + +void TurnServer::HandleBindingRequest(Connection* conn, + const StunMessage* req) { + StunMessage response; + InitResponse(req, &response); + + // Tell the user the address that we received their request from. + StunAddressAttribute* mapped_addr_attr; + mapped_addr_attr = new StunXorAddressAttribute( + STUN_ATTR_XOR_MAPPED_ADDRESS, conn->src()); + VERIFY(response.AddAttribute(mapped_addr_attr)); + + SendStun(conn, &response); +} + +void TurnServer::HandleAllocateRequest(Connection* conn, + const TurnMessage* msg, + const std::string& key) { + // Check the parameters in the request. + const StunUInt32Attribute* transport_attr = + msg->GetUInt32(STUN_ATTR_REQUESTED_TRANSPORT); + if (!transport_attr) { + SendErrorResponse(conn, msg, STUN_ERROR_BAD_REQUEST, + STUN_ERROR_REASON_BAD_REQUEST); + return; + } + + // Only UDP is supported right now. + int proto = transport_attr->value() >> 24; + if (proto != IPPROTO_UDP) { + SendErrorResponse(conn, msg, STUN_ERROR_UNSUPPORTED_PROTOCOL, + STUN_ERROR_REASON_UNSUPPORTED_PROTOCOL); + return; + } + + // Create the allocation and let it send the success response. + // If the actual socket allocation fails, send an internal error. + Allocation* alloc = CreateAllocation(conn, proto, key); + if (alloc) { + alloc->HandleTurnMessage(msg); + } else { + SendErrorResponse(conn, msg, STUN_ERROR_SERVER_ERROR, + "Failed to allocate socket"); + } +} + +std::string TurnServer::GenerateNonce() const { + // Generate a nonce of the form hex(now + HMAC-MD5(nonce_key_, now)) + uint32 now = rtc::Time(); + std::string input(reinterpret_cast(&now), sizeof(now)); + std::string nonce = rtc::hex_encode(input.c_str(), input.size()); + nonce += rtc::ComputeHmac(rtc::DIGEST_MD5, nonce_key_, input); + ASSERT(nonce.size() == kNonceSize); + return nonce; +} + +bool TurnServer::ValidateNonce(const std::string& nonce) const { + // Check the size. + if (nonce.size() != kNonceSize) { + return false; + } + + // Decode the timestamp. + uint32 then; + char* p = reinterpret_cast(&then); + size_t len = rtc::hex_decode(p, sizeof(then), + nonce.substr(0, sizeof(then) * 2)); + if (len != sizeof(then)) { + return false; + } + + // Verify the HMAC. + if (nonce.substr(sizeof(then) * 2) != rtc::ComputeHmac( + rtc::DIGEST_MD5, nonce_key_, std::string(p, sizeof(then)))) { + return false; + } + + // Validate the timestamp. + return rtc::TimeSince(then) < kNonceTimeout; +} + +TurnServer::Allocation* TurnServer::FindAllocation(Connection* conn) { + AllocationMap::const_iterator it = allocations_.find(*conn); + return (it != allocations_.end()) ? it->second : NULL; +} + +TurnServer::Allocation* TurnServer::CreateAllocation(Connection* conn, + int proto, + const std::string& key) { + rtc::AsyncPacketSocket* external_socket = (external_socket_factory_) ? + external_socket_factory_->CreateUdpSocket(external_addr_, 0, 0) : NULL; + if (!external_socket) { + return NULL; + } + + // The Allocation takes ownership of the socket. + Allocation* allocation = new Allocation(this, + thread_, *conn, external_socket, key); + allocation->SignalDestroyed.connect(this, &TurnServer::OnAllocationDestroyed); + allocations_[*conn] = allocation; + return allocation; +} + +void TurnServer::SendErrorResponse(Connection* conn, + const StunMessage* req, + int code, const std::string& reason) { + TurnMessage resp; + InitErrorResponse(req, code, reason, &resp); + LOG(LS_INFO) << "Sending error response, type=" << resp.type() + << ", code=" << code << ", reason=" << reason; + SendStun(conn, &resp); +} + +void TurnServer::SendErrorResponseWithRealmAndNonce( + Connection* conn, const StunMessage* msg, + int code, const std::string& reason) { + TurnMessage resp; + InitErrorResponse(msg, code, reason, &resp); + VERIFY(resp.AddAttribute(new StunByteStringAttribute( + STUN_ATTR_NONCE, GenerateNonce()))); + VERIFY(resp.AddAttribute(new StunByteStringAttribute( + STUN_ATTR_REALM, realm_))); + SendStun(conn, &resp); +} + +void TurnServer::SendErrorResponseWithAlternateServer( + Connection* conn, const StunMessage* msg, + const rtc::SocketAddress& addr) { + TurnMessage resp; + InitErrorResponse(msg, STUN_ERROR_TRY_ALTERNATE, + STUN_ERROR_REASON_TRY_ALTERNATE_SERVER, &resp); + VERIFY(resp.AddAttribute(new StunAddressAttribute( + STUN_ATTR_ALTERNATE_SERVER, addr))); + SendStun(conn, &resp); +} + +void TurnServer::SendStun(Connection* conn, StunMessage* msg) { + rtc::ByteBuffer buf; + // Add a SOFTWARE attribute if one is set. + if (!software_.empty()) { + VERIFY(msg->AddAttribute( + new StunByteStringAttribute(STUN_ATTR_SOFTWARE, software_))); + } + msg->Write(&buf); + Send(conn, buf); +} + +void TurnServer::Send(Connection* conn, + const rtc::ByteBuffer& buf) { + rtc::PacketOptions options; + conn->socket()->SendTo(buf.Data(), buf.Length(), conn->src(), options); +} + +void TurnServer::OnAllocationDestroyed(Allocation* allocation) { + // Removing the internal socket if the connection is not udp. + rtc::AsyncPacketSocket* socket = allocation->conn()->socket(); + InternalSocketMap::iterator iter = server_sockets_.find(socket); + ASSERT(iter != server_sockets_.end()); + // Skip if the socket serving this allocation is UDP, as this will be shared + // by all allocations. + if (iter->second != cricket::PROTO_UDP) { + DestroyInternalSocket(socket); + } + + AllocationMap::iterator it = allocations_.find(*(allocation->conn())); + if (it != allocations_.end()) + allocations_.erase(it); +} + +void TurnServer::DestroyInternalSocket(rtc::AsyncPacketSocket* socket) { + InternalSocketMap::iterator iter = server_sockets_.find(socket); + if (iter != server_sockets_.end()) { + rtc::AsyncPacketSocket* socket = iter->first; + // We must destroy the socket async to avoid invalidating the sigslot + // callback list iterator inside a sigslot callback. + rtc::Thread::Current()->Dispose(socket); + server_sockets_.erase(iter); + } +} + +TurnServer::Connection::Connection(const rtc::SocketAddress& src, + ProtocolType proto, + rtc::AsyncPacketSocket* socket) + : src_(src), + dst_(socket->GetRemoteAddress()), + proto_(proto), + socket_(socket) { +} + +bool TurnServer::Connection::operator==(const Connection& c) const { + return src_ == c.src_ && dst_ == c.dst_ && proto_ == c.proto_; +} + +bool TurnServer::Connection::operator<(const Connection& c) const { + return src_ < c.src_ || dst_ < c.dst_ || proto_ < c.proto_; +} + +std::string TurnServer::Connection::ToString() const { + const char* const kProtos[] = { + "unknown", "udp", "tcp", "ssltcp" + }; + std::ostringstream ost; + ost << src_.ToString() << "-" << dst_.ToString() << ":"<< kProtos[proto_]; + return ost.str(); +} + +TurnServer::Allocation::Allocation(TurnServer* server, + rtc::Thread* thread, + const Connection& conn, + rtc::AsyncPacketSocket* socket, + const std::string& key) + : server_(server), + thread_(thread), + conn_(conn), + external_socket_(socket), + key_(key) { + external_socket_->SignalReadPacket.connect( + this, &TurnServer::Allocation::OnExternalPacket); +} + +TurnServer::Allocation::~Allocation() { + for (ChannelList::iterator it = channels_.begin(); + it != channels_.end(); ++it) { + delete *it; + } + for (PermissionList::iterator it = perms_.begin(); + it != perms_.end(); ++it) { + delete *it; + } + thread_->Clear(this, MSG_ALLOCATION_TIMEOUT); + LOG_J(LS_INFO, this) << "Allocation destroyed"; +} + +std::string TurnServer::Allocation::ToString() const { + std::ostringstream ost; + ost << "Alloc[" << conn_.ToString() << "]"; + return ost.str(); +} + +void TurnServer::Allocation::HandleTurnMessage(const TurnMessage* msg) { + ASSERT(msg != NULL); + switch (msg->type()) { + case STUN_ALLOCATE_REQUEST: + HandleAllocateRequest(msg); + break; + case TURN_REFRESH_REQUEST: + HandleRefreshRequest(msg); + break; + case TURN_SEND_INDICATION: + HandleSendIndication(msg); + break; + case TURN_CREATE_PERMISSION_REQUEST: + HandleCreatePermissionRequest(msg); + break; + case TURN_CHANNEL_BIND_REQUEST: + HandleChannelBindRequest(msg); + break; + default: + // Not sure what to do with this, just eat it. + LOG_J(LS_WARNING, this) << "Invalid TURN message type received: " + << msg->type(); + } +} + +void TurnServer::Allocation::HandleAllocateRequest(const TurnMessage* msg) { + // Copy the important info from the allocate request. + transaction_id_ = msg->transaction_id(); + const StunByteStringAttribute* username_attr = + msg->GetByteString(STUN_ATTR_USERNAME); + ASSERT(username_attr != NULL); + username_ = username_attr->GetString(); + + // Figure out the lifetime and start the allocation timer. + int lifetime_secs = ComputeLifetime(msg); + thread_->PostDelayed(lifetime_secs * 1000, this, MSG_ALLOCATION_TIMEOUT); + + LOG_J(LS_INFO, this) << "Created allocation, lifetime=" << lifetime_secs; + + // We've already validated all the important bits; just send a response here. + TurnMessage response; + InitResponse(msg, &response); + + StunAddressAttribute* mapped_addr_attr = + new StunXorAddressAttribute(STUN_ATTR_XOR_MAPPED_ADDRESS, conn_.src()); + StunAddressAttribute* relayed_addr_attr = + new StunXorAddressAttribute(STUN_ATTR_XOR_RELAYED_ADDRESS, + external_socket_->GetLocalAddress()); + StunUInt32Attribute* lifetime_attr = + new StunUInt32Attribute(STUN_ATTR_LIFETIME, lifetime_secs); + VERIFY(response.AddAttribute(mapped_addr_attr)); + VERIFY(response.AddAttribute(relayed_addr_attr)); + VERIFY(response.AddAttribute(lifetime_attr)); + + SendResponse(&response); +} + +void TurnServer::Allocation::HandleRefreshRequest(const TurnMessage* msg) { + // Figure out the new lifetime. + int lifetime_secs = ComputeLifetime(msg); + + // Reset the expiration timer. + thread_->Clear(this, MSG_ALLOCATION_TIMEOUT); + thread_->PostDelayed(lifetime_secs * 1000, this, MSG_ALLOCATION_TIMEOUT); + + LOG_J(LS_INFO, this) << "Refreshed allocation, lifetime=" << lifetime_secs; + + // Send a success response with a LIFETIME attribute. + TurnMessage response; + InitResponse(msg, &response); + + StunUInt32Attribute* lifetime_attr = + new StunUInt32Attribute(STUN_ATTR_LIFETIME, lifetime_secs); + VERIFY(response.AddAttribute(lifetime_attr)); + + SendResponse(&response); +} + +void TurnServer::Allocation::HandleSendIndication(const TurnMessage* msg) { + // Check mandatory attributes. + const StunByteStringAttribute* data_attr = msg->GetByteString(STUN_ATTR_DATA); + const StunAddressAttribute* peer_attr = + msg->GetAddress(STUN_ATTR_XOR_PEER_ADDRESS); + if (!data_attr || !peer_attr) { + LOG_J(LS_WARNING, this) << "Received invalid send indication"; + return; + } + + // If a permission exists, send the data on to the peer. + if (HasPermission(peer_attr->GetAddress().ipaddr())) { + SendExternal(data_attr->bytes(), data_attr->length(), + peer_attr->GetAddress()); + } else { + LOG_J(LS_WARNING, this) << "Received send indication without permission" + << "peer=" << peer_attr->GetAddress(); + } +} + +void TurnServer::Allocation::HandleCreatePermissionRequest( + const TurnMessage* msg) { + // Check mandatory attributes. + const StunAddressAttribute* peer_attr = + msg->GetAddress(STUN_ATTR_XOR_PEER_ADDRESS); + if (!peer_attr) { + SendBadRequestResponse(msg); + return; + } + + // Add this permission. + AddPermission(peer_attr->GetAddress().ipaddr()); + + LOG_J(LS_INFO, this) << "Created permission, peer=" + << peer_attr->GetAddress(); + + // Send a success response. + TurnMessage response; + InitResponse(msg, &response); + SendResponse(&response); +} + +void TurnServer::Allocation::HandleChannelBindRequest(const TurnMessage* msg) { + // Check mandatory attributes. + const StunUInt32Attribute* channel_attr = + msg->GetUInt32(STUN_ATTR_CHANNEL_NUMBER); + const StunAddressAttribute* peer_attr = + msg->GetAddress(STUN_ATTR_XOR_PEER_ADDRESS); + if (!channel_attr || !peer_attr) { + SendBadRequestResponse(msg); + return; + } + + // Check that channel id is valid. + int channel_id = channel_attr->value() >> 16; + if (channel_id < kMinChannelNumber || channel_id > kMaxChannelNumber) { + SendBadRequestResponse(msg); + return; + } + + // Check that this channel id isn't bound to another transport address, and + // that this transport address isn't bound to another channel id. + Channel* channel1 = FindChannel(channel_id); + Channel* channel2 = FindChannel(peer_attr->GetAddress()); + if (channel1 != channel2) { + SendBadRequestResponse(msg); + return; + } + + // Add or refresh this channel. + if (!channel1) { + channel1 = new Channel(thread_, channel_id, peer_attr->GetAddress()); + channel1->SignalDestroyed.connect(this, + &TurnServer::Allocation::OnChannelDestroyed); + channels_.push_back(channel1); + } else { + channel1->Refresh(); + } + + // Channel binds also refresh permissions. + AddPermission(peer_attr->GetAddress().ipaddr()); + + LOG_J(LS_INFO, this) << "Bound channel, id=" << channel_id + << ", peer=" << peer_attr->GetAddress(); + + // Send a success response. + TurnMessage response; + InitResponse(msg, &response); + SendResponse(&response); +} + +void TurnServer::Allocation::HandleChannelData(const char* data, size_t size) { + // Extract the channel number from the data. + uint16 channel_id = rtc::GetBE16(data); + Channel* channel = FindChannel(channel_id); + if (channel) { + // Send the data to the peer address. + SendExternal(data + TURN_CHANNEL_HEADER_SIZE, + size - TURN_CHANNEL_HEADER_SIZE, channel->peer()); + } else { + LOG_J(LS_WARNING, this) << "Received channel data for invalid channel, id=" + << channel_id; + } +} + +void TurnServer::Allocation::OnExternalPacket( + rtc::AsyncPacketSocket* socket, + const char* data, size_t size, + const rtc::SocketAddress& addr, + const rtc::PacketTime& packet_time) { + ASSERT(external_socket_.get() == socket); + Channel* channel = FindChannel(addr); + if (channel) { + // There is a channel bound to this address. Send as a channel message. + rtc::ByteBuffer buf; + buf.WriteUInt16(channel->id()); + buf.WriteUInt16(static_cast(size)); + buf.WriteBytes(data, size); + server_->Send(&conn_, buf); + } else if (HasPermission(addr.ipaddr())) { + // No channel, but a permission exists. Send as a data indication. + TurnMessage msg; + msg.SetType(TURN_DATA_INDICATION); + msg.SetTransactionID( + rtc::CreateRandomString(kStunTransactionIdLength)); + VERIFY(msg.AddAttribute(new StunXorAddressAttribute( + STUN_ATTR_XOR_PEER_ADDRESS, addr))); + VERIFY(msg.AddAttribute(new StunByteStringAttribute( + STUN_ATTR_DATA, data, size))); + server_->SendStun(&conn_, &msg); + } else { + LOG_J(LS_WARNING, this) << "Received external packet without permission, " + << "peer=" << addr; + } +} + +int TurnServer::Allocation::ComputeLifetime(const TurnMessage* msg) { + // Return the smaller of our default lifetime and the requested lifetime. + uint32 lifetime = kDefaultAllocationTimeout / 1000; // convert to seconds + const StunUInt32Attribute* lifetime_attr = msg->GetUInt32(STUN_ATTR_LIFETIME); + if (lifetime_attr && lifetime_attr->value() < lifetime) { + lifetime = lifetime_attr->value(); + } + return lifetime; +} + +bool TurnServer::Allocation::HasPermission(const rtc::IPAddress& addr) { + return (FindPermission(addr) != NULL); +} + +void TurnServer::Allocation::AddPermission(const rtc::IPAddress& addr) { + Permission* perm = FindPermission(addr); + if (!perm) { + perm = new Permission(thread_, addr); + perm->SignalDestroyed.connect( + this, &TurnServer::Allocation::OnPermissionDestroyed); + perms_.push_back(perm); + } else { + perm->Refresh(); + } +} + +TurnServer::Permission* TurnServer::Allocation::FindPermission( + const rtc::IPAddress& addr) const { + for (PermissionList::const_iterator it = perms_.begin(); + it != perms_.end(); ++it) { + if ((*it)->peer() == addr) + return *it; + } + return NULL; +} + +TurnServer::Channel* TurnServer::Allocation::FindChannel(int channel_id) const { + for (ChannelList::const_iterator it = channels_.begin(); + it != channels_.end(); ++it) { + if ((*it)->id() == channel_id) + return *it; + } + return NULL; +} + +TurnServer::Channel* TurnServer::Allocation::FindChannel( + const rtc::SocketAddress& addr) const { + for (ChannelList::const_iterator it = channels_.begin(); + it != channels_.end(); ++it) { + if ((*it)->peer() == addr) + return *it; + } + return NULL; +} + +void TurnServer::Allocation::SendResponse(TurnMessage* msg) { + // Success responses always have M-I. + msg->AddMessageIntegrity(key_); + server_->SendStun(&conn_, msg); +} + +void TurnServer::Allocation::SendBadRequestResponse(const TurnMessage* req) { + SendErrorResponse(req, STUN_ERROR_BAD_REQUEST, STUN_ERROR_REASON_BAD_REQUEST); +} + +void TurnServer::Allocation::SendErrorResponse(const TurnMessage* req, int code, + const std::string& reason) { + server_->SendErrorResponse(&conn_, req, code, reason); +} + +void TurnServer::Allocation::SendExternal(const void* data, size_t size, + const rtc::SocketAddress& peer) { + rtc::PacketOptions options; + external_socket_->SendTo(data, size, peer, options); +} + +void TurnServer::Allocation::OnMessage(rtc::Message* msg) { + ASSERT(msg->message_id == MSG_ALLOCATION_TIMEOUT); + SignalDestroyed(this); + delete this; +} + +void TurnServer::Allocation::OnPermissionDestroyed(Permission* perm) { + PermissionList::iterator it = std::find(perms_.begin(), perms_.end(), perm); + ASSERT(it != perms_.end()); + perms_.erase(it); +} + +void TurnServer::Allocation::OnChannelDestroyed(Channel* channel) { + ChannelList::iterator it = + std::find(channels_.begin(), channels_.end(), channel); + ASSERT(it != channels_.end()); + channels_.erase(it); +} + +TurnServer::Permission::Permission(rtc::Thread* thread, + const rtc::IPAddress& peer) + : thread_(thread), peer_(peer) { + Refresh(); +} + +TurnServer::Permission::~Permission() { + thread_->Clear(this, MSG_ALLOCATION_TIMEOUT); +} + +void TurnServer::Permission::Refresh() { + thread_->Clear(this, MSG_ALLOCATION_TIMEOUT); + thread_->PostDelayed(kPermissionTimeout, this, MSG_ALLOCATION_TIMEOUT); +} + +void TurnServer::Permission::OnMessage(rtc::Message* msg) { + ASSERT(msg->message_id == MSG_ALLOCATION_TIMEOUT); + SignalDestroyed(this); + delete this; +} + +TurnServer::Channel::Channel(rtc::Thread* thread, int id, + const rtc::SocketAddress& peer) + : thread_(thread), id_(id), peer_(peer) { + Refresh(); +} + +TurnServer::Channel::~Channel() { + thread_->Clear(this, MSG_ALLOCATION_TIMEOUT); +} + +void TurnServer::Channel::Refresh() { + thread_->Clear(this, MSG_ALLOCATION_TIMEOUT); + thread_->PostDelayed(kChannelTimeout, this, MSG_ALLOCATION_TIMEOUT); +} + +void TurnServer::Channel::OnMessage(rtc::Message* msg) { + ASSERT(msg->message_id == MSG_ALLOCATION_TIMEOUT); + SignalDestroyed(this); + delete this; +} + +} // namespace cricket diff --git a/webrtc/p2p/base/turnserver.h b/webrtc/p2p/base/turnserver.h new file mode 100644 index 000000000..670b61804 --- /dev/null +++ b/webrtc/p2p/base/turnserver.h @@ -0,0 +1,190 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_TURNSERVER_H_ +#define WEBRTC_P2P_BASE_TURNSERVER_H_ + +#include +#include +#include +#include + +#include "webrtc/p2p/base/portinterface.h" +#include "webrtc/base/asyncpacketsocket.h" +#include "webrtc/base/messagequeue.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/socketaddress.h" + +namespace rtc { +class ByteBuffer; +class PacketSocketFactory; +class Thread; +} + +namespace cricket { + +class StunMessage; +class TurnMessage; + +// The default server port for TURN, as specified in RFC5766. +const int TURN_SERVER_PORT = 3478; + +// An interface through which the MD5 credential hash can be retrieved. +class TurnAuthInterface { + public: + // Gets HA1 for the specified user and realm. + // HA1 = MD5(A1) = MD5(username:realm:password). + // Return true if the given username and realm are valid, or false if not. + virtual bool GetKey(const std::string& username, const std::string& realm, + std::string* key) = 0; +}; + +// An interface enables Turn Server to control redirection behavior. +class TurnRedirectInterface { + public: + virtual bool ShouldRedirect(const rtc::SocketAddress& address, + rtc::SocketAddress* out) = 0; + virtual ~TurnRedirectInterface() {} +}; + +// The core TURN server class. Give it a socket to listen on via +// AddInternalServerSocket, and a factory to create external sockets via +// SetExternalSocketFactory, and it's ready to go. +// Not yet wired up: TCP support. +class TurnServer : public sigslot::has_slots<> { + public: + explicit TurnServer(rtc::Thread* thread); + ~TurnServer(); + + // Gets/sets the realm value to use for the server. + const std::string& realm() const { return realm_; } + void set_realm(const std::string& realm) { realm_ = realm; } + + // Gets/sets the value for the SOFTWARE attribute for TURN messages. + const std::string& software() const { return software_; } + void set_software(const std::string& software) { software_ = software; } + + // Sets the authentication callback; does not take ownership. + void set_auth_hook(TurnAuthInterface* auth_hook) { auth_hook_ = auth_hook; } + + void set_redirect_hook(TurnRedirectInterface* redirect_hook) { + redirect_hook_ = redirect_hook; + } + + void set_enable_otu_nonce(bool enable) { enable_otu_nonce_ = enable; } + + // Starts listening for packets from internal clients. + void AddInternalSocket(rtc::AsyncPacketSocket* socket, + ProtocolType proto); + // Starts listening for the connections on this socket. When someone tries + // to connect, the connection will be accepted and a new internal socket + // will be added. + void AddInternalServerSocket(rtc::AsyncSocket* socket, + ProtocolType proto); + // Specifies the factory to use for creating external sockets. + void SetExternalSocketFactory(rtc::PacketSocketFactory* factory, + const rtc::SocketAddress& address); + + private: + // Encapsulates the client's connection to the server. + class Connection { + public: + Connection() : proto_(PROTO_UDP), socket_(NULL) {} + Connection(const rtc::SocketAddress& src, + ProtocolType proto, + rtc::AsyncPacketSocket* socket); + const rtc::SocketAddress& src() const { return src_; } + rtc::AsyncPacketSocket* socket() { return socket_; } + bool operator==(const Connection& t) const; + bool operator<(const Connection& t) const; + std::string ToString() const; + + private: + rtc::SocketAddress src_; + rtc::SocketAddress dst_; + cricket::ProtocolType proto_; + rtc::AsyncPacketSocket* socket_; + }; + class Allocation; + class Permission; + class Channel; + typedef std::map AllocationMap; + + void OnInternalPacket(rtc::AsyncPacketSocket* socket, const char* data, + size_t size, const rtc::SocketAddress& address, + const rtc::PacketTime& packet_time); + + void OnNewInternalConnection(rtc::AsyncSocket* socket); + + // Accept connections on this server socket. + void AcceptConnection(rtc::AsyncSocket* server_socket); + void OnInternalSocketClose(rtc::AsyncPacketSocket* socket, int err); + + void HandleStunMessage(Connection* conn, const char* data, size_t size); + void HandleBindingRequest(Connection* conn, const StunMessage* msg); + void HandleAllocateRequest(Connection* conn, const TurnMessage* msg, + const std::string& key); + + bool GetKey(const StunMessage* msg, std::string* key); + bool CheckAuthorization(Connection* conn, const StunMessage* msg, + const char* data, size_t size, + const std::string& key); + std::string GenerateNonce() const; + bool ValidateNonce(const std::string& nonce) const; + + Allocation* FindAllocation(Connection* conn); + Allocation* CreateAllocation(Connection* conn, int proto, + const std::string& key); + + void SendErrorResponse(Connection* conn, const StunMessage* req, + int code, const std::string& reason); + + void SendErrorResponseWithRealmAndNonce(Connection* conn, + const StunMessage* req, + int code, + const std::string& reason); + + void SendErrorResponseWithAlternateServer(Connection* conn, + const StunMessage* req, + const rtc::SocketAddress& addr); + + void SendStun(Connection* conn, StunMessage* msg); + void Send(Connection* conn, const rtc::ByteBuffer& buf); + + void OnAllocationDestroyed(Allocation* allocation); + void DestroyInternalSocket(rtc::AsyncPacketSocket* socket); + + typedef std::map InternalSocketMap; + typedef std::map ServerSocketMap; + + rtc::Thread* thread_; + std::string nonce_key_; + std::string realm_; + std::string software_; + TurnAuthInterface* auth_hook_; + TurnRedirectInterface* redirect_hook_; + // otu - one-time-use. Server will respond with 438 if it's + // sees the same nonce in next transaction. + bool enable_otu_nonce_; + + InternalSocketMap server_sockets_; + ServerSocketMap server_listen_sockets_; + rtc::scoped_ptr + external_socket_factory_; + rtc::SocketAddress external_addr_; + + AllocationMap allocations_; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_TURNSERVER_H_ diff --git a/webrtc/p2p/base/udpport.h b/webrtc/p2p/base/udpport.h new file mode 100644 index 000000000..9f868644e --- /dev/null +++ b/webrtc/p2p/base/udpport.h @@ -0,0 +1,17 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_UDPPORT_H_ +#define WEBRTC_P2P_BASE_UDPPORT_H_ + +// StunPort will be handling UDPPort functionality. +#include "webrtc/p2p/base/stunport.h" + +#endif // WEBRTC_P2P_BASE_UDPPORT_H_ diff --git a/webrtc/p2p/client/autoportallocator.h b/webrtc/p2p/client/autoportallocator.h new file mode 100644 index 000000000..5df1f0d6c --- /dev/null +++ b/webrtc/p2p/client/autoportallocator.h @@ -0,0 +1,52 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_CLIENT_AUTOPORTALLOCATOR_H_ +#define WEBRTC_P2P_CLIENT_AUTOPORTALLOCATOR_H_ + +#include +#include + +#include "webrtc/p2p/client/httpportallocator.h" +#include "webrtc/libjingle/xmpp/jingleinfotask.h" +#include "webrtc/libjingle/xmpp/xmppclient.h" +#include "webrtc/base/sigslot.h" + +// This class sets the relay and stun servers using XmppClient. +// It enables the client to traverse Proxy and NAT. +class AutoPortAllocator : public cricket::HttpPortAllocator { + public: + AutoPortAllocator(rtc::NetworkManager* network_manager, + const std::string& user_agent) + : cricket::HttpPortAllocator(network_manager, user_agent) { + } + + // Creates and initiates a task to get relay token from XmppClient and set + // it appropriately. + void SetXmppClient(buzz::XmppClient* client) { + // The JingleInfoTask is freed by the task-runner. + buzz::JingleInfoTask* jit = new buzz::JingleInfoTask(client); + jit->SignalJingleInfo.connect(this, &AutoPortAllocator::OnJingleInfo); + jit->Start(); + jit->RefreshJingleInfoNow(); + } + + private: + void OnJingleInfo( + const std::string& token, + const std::vector& relay_hosts, + const std::vector& stun_hosts) { + SetRelayToken(token); + SetStunHosts(stun_hosts); + SetRelayHosts(relay_hosts); + } +}; + +#endif // WEBRTC_P2P_CLIENT_AUTOPORTALLOCATOR_H_ diff --git a/webrtc/p2p/client/basicportallocator.cc b/webrtc/p2p/client/basicportallocator.cc new file mode 100644 index 000000000..5013a5756 --- /dev/null +++ b/webrtc/p2p/client/basicportallocator.cc @@ -0,0 +1,1190 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/client/basicportallocator.h" + +#include +#include + +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/common.h" +#include "webrtc/p2p/base/port.h" +#include "webrtc/p2p/base/relayport.h" +#include "webrtc/p2p/base/stunport.h" +#include "webrtc/p2p/base/tcpport.h" +#include "webrtc/p2p/base/turnport.h" +#include "webrtc/p2p/base/udpport.h" +#include "webrtc/base/common.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" + +using rtc::CreateRandomId; +using rtc::CreateRandomString; + +namespace { + +enum { + MSG_CONFIG_START, + MSG_CONFIG_READY, + MSG_ALLOCATE, + MSG_ALLOCATION_PHASE, + MSG_SHAKE, + MSG_SEQUENCEOBJECTS_CREATED, + MSG_CONFIG_STOP, +}; + +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 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 + CreateRandomId() % range; +} + +} // namespace + +namespace cricket { + +const uint32 DISABLE_ALL_PHASES = + PORTALLOCATOR_DISABLE_UDP + | PORTALLOCATOR_DISABLE_TCP + | PORTALLOCATOR_DISABLE_STUN + | PORTALLOCATOR_DISABLE_RELAY; + +// Performs the allocation of ports, in a sequenced (timed) manner, for a given +// network and IP address. +class AllocationSequence : public rtc::MessageHandler, + public sigslot::has_slots<> { + public: + enum State { + kInit, // Initial state. + kRunning, // Started allocating ports. + kStopped, // Stopped from running. + kCompleted, // All ports are allocated. + + // kInit --> kRunning --> {kCompleted|kStopped} + }; + + AllocationSequence(BasicPortAllocatorSession* session, + rtc::Network* network, + PortConfiguration* config, + uint32 flags); + ~AllocationSequence(); + bool Init(); + void Clear(); + + State state() const { return state_; } + + // Disables the phases for a new sequence that this one already covers for an + // equivalent network setup. + void DisableEquivalentPhases(rtc::Network* network, + PortConfiguration* config, uint32* flags); + + // 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(rtc::Message* msg); + + void EnableProtocol(ProtocolType proto); + bool ProtocolEnabled(ProtocolType proto) const; + + // Signal from AllocationSequence, when it's done with allocating ports. + // This signal is useful, when port allocation fails which doesn't result + // in any candidates. Using this signal BasicPortAllocatorSession can send + // its candidate discovery conclusion signal. Without this signal, + // BasicPortAllocatorSession doesn't have any event to trigger signal. This + // can also be achieved by starting timer in BPAS. + sigslot::signal1 SignalPortAllocationComplete; + + private: + typedef std::vector ProtocolList; + + bool IsFlagSet(uint32 flag) { + return ((flags_ & flag) != 0); + } + void CreateUDPPorts(); + void CreateTCPPorts(); + void CreateStunPorts(); + void CreateRelayPorts(); + void CreateGturnPort(const RelayServerConfig& config); + void CreateTurnPort(const RelayServerConfig& config); + + void OnReadPacket(rtc::AsyncPacketSocket* socket, + const char* data, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time); + + void OnPortDestroyed(PortInterface* port); + + BasicPortAllocatorSession* session_; + rtc::Network* network_; + rtc::IPAddress ip_; + PortConfiguration* config_; + State state_; + uint32 flags_; + ProtocolList protocols_; + rtc::scoped_ptr udp_socket_; + // There will be only one udp port per AllocationSequence. + UDPPort* udp_port_; + std::vector turn_ports_; + int phase_; +}; + +// BasicPortAllocator +BasicPortAllocator::BasicPortAllocator( + rtc::NetworkManager* network_manager, + rtc::PacketSocketFactory* socket_factory) + : network_manager_(network_manager), + socket_factory_(socket_factory) { + ASSERT(socket_factory_ != NULL); + Construct(); +} + +BasicPortAllocator::BasicPortAllocator( + rtc::NetworkManager* network_manager) + : network_manager_(network_manager), + socket_factory_(NULL) { + Construct(); +} + +BasicPortAllocator::BasicPortAllocator( + rtc::NetworkManager* network_manager, + rtc::PacketSocketFactory* socket_factory, + const ServerAddresses& stun_servers) + : network_manager_(network_manager), + socket_factory_(socket_factory), + stun_servers_(stun_servers) { + ASSERT(socket_factory_ != NULL); + Construct(); +} + +BasicPortAllocator::BasicPortAllocator( + rtc::NetworkManager* network_manager, + const ServerAddresses& stun_servers, + const rtc::SocketAddress& relay_address_udp, + const rtc::SocketAddress& relay_address_tcp, + const rtc::SocketAddress& relay_address_ssl) + : network_manager_(network_manager), + socket_factory_(NULL), + stun_servers_(stun_servers) { + + RelayServerConfig config(RELAY_GTURN); + if (!relay_address_udp.IsNil()) + config.ports.push_back(ProtocolAddress(relay_address_udp, PROTO_UDP)); + if (!relay_address_tcp.IsNil()) + config.ports.push_back(ProtocolAddress(relay_address_tcp, PROTO_TCP)); + if (!relay_address_ssl.IsNil()) + config.ports.push_back(ProtocolAddress(relay_address_ssl, PROTO_SSLTCP)); + + if (!config.ports.empty()) + AddRelay(config); + + Construct(); +} + +void BasicPortAllocator::Construct() { + allow_tcp_listen_ = true; +} + +BasicPortAllocator::~BasicPortAllocator() { +} + +PortAllocatorSession *BasicPortAllocator::CreateSessionInternal( + const std::string& content_name, int component, + const std::string& ice_ufrag, const std::string& ice_pwd) { + return new BasicPortAllocatorSession( + this, content_name, component, ice_ufrag, ice_pwd); +} + + +// BasicPortAllocatorSession +BasicPortAllocatorSession::BasicPortAllocatorSession( + BasicPortAllocator *allocator, + const std::string& content_name, + int component, + const std::string& ice_ufrag, + const std::string& ice_pwd) + : PortAllocatorSession(content_name, component, + ice_ufrag, ice_pwd, allocator->flags()), + allocator_(allocator), network_thread_(NULL), + socket_factory_(allocator->socket_factory()), + allocation_started_(false), + network_manager_started_(false), + running_(false), + allocation_sequences_created_(false) { + allocator_->network_manager()->SignalNetworksChanged.connect( + this, &BasicPortAllocatorSession::OnNetworksChanged); + allocator_->network_manager()->StartUpdating(); +} + +BasicPortAllocatorSession::~BasicPortAllocatorSession() { + allocator_->network_manager()->StopUpdating(); + if (network_thread_ != NULL) + network_thread_->Clear(this); + + for (uint32 i = 0; i < sequences_.size(); ++i) { + // AllocationSequence should clear it's map entry for turn ports before + // ports are destroyed. + sequences_[i]->Clear(); + } + + 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::StartGettingPorts() { + network_thread_ = rtc::Thread::Current(); + if (!socket_factory_) { + owned_socket_factory_.reset( + new rtc::BasicPacketSocketFactory(network_thread_)); + socket_factory_ = owned_socket_factory_.get(); + } + + running_ = true; + network_thread_->Post(this, MSG_CONFIG_START); + + if (flags() & PORTALLOCATOR_ENABLE_SHAKER) + network_thread_->PostDelayed(ShakeDelay(), this, MSG_SHAKE); +} + +void BasicPortAllocatorSession::StopGettingPorts() { + ASSERT(rtc::Thread::Current() == network_thread_); + running_ = false; + network_thread_->Clear(this, MSG_ALLOCATE); + for (uint32 i = 0; i < sequences_.size(); ++i) + sequences_[i]->Stop(); + network_thread_->Post(this, MSG_CONFIG_STOP); +} + +void BasicPortAllocatorSession::OnMessage(rtc::Message *message) { + switch (message->message_id) { + case MSG_CONFIG_START: + ASSERT(rtc::Thread::Current() == network_thread_); + GetPortConfigurations(); + break; + + case MSG_CONFIG_READY: + ASSERT(rtc::Thread::Current() == network_thread_); + OnConfigReady(static_cast(message->pdata)); + break; + + case MSG_ALLOCATE: + ASSERT(rtc::Thread::Current() == network_thread_); + OnAllocate(); + break; + + case MSG_SHAKE: + ASSERT(rtc::Thread::Current() == network_thread_); + OnShake(); + break; + case MSG_SEQUENCEOBJECTS_CREATED: + ASSERT(rtc::Thread::Current() == network_thread_); + OnAllocationSequenceObjectsCreated(); + break; + case MSG_CONFIG_STOP: + ASSERT(rtc::Thread::Current() == network_thread_); + OnConfigStop(); + break; + default: + ASSERT(false); + } +} + +void BasicPortAllocatorSession::GetPortConfigurations() { + PortConfiguration* config = new PortConfiguration(allocator_->stun_servers(), + username(), + password()); + + for (size_t i = 0; i < allocator_->relays().size(); ++i) { + config->AddRelay(allocator_->relays()[i]); + } + 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::OnConfigStop() { + ASSERT(rtc::Thread::Current() == network_thread_); + + // If any of the allocated ports have not completed the candidates allocation, + // mark those as error. Since session doesn't need any new candidates + // at this stage of the allocation, it's safe to discard any new candidates. + bool send_signal = false; + for (std::vector::iterator it = ports_.begin(); + it != ports_.end(); ++it) { + if (!it->complete()) { + // Updating port state to error, which didn't finish allocating candidates + // yet. + it->set_error(); + send_signal = true; + } + } + + // Did we stop any running sequences? + for (std::vector::iterator it = sequences_.begin(); + it != sequences_.end() && !send_signal; ++it) { + if ((*it)->state() == AllocationSequence::kStopped) { + send_signal = true; + } + } + + // If we stopped anything that was running, send a done signal now. + if (send_signal) { + MaybeSignalCandidatesAllocationDone(); + } +} + +void BasicPortAllocatorSession::AllocatePorts() { + ASSERT(rtc::Thread::Current() == network_thread_); + network_thread_->Post(this, MSG_ALLOCATE); +} + +void BasicPortAllocatorSession::OnAllocate() { + if (network_manager_started_) + DoAllocate(); + + allocation_started_ = true; +} + +// 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::DoAllocate() { + bool done_signal_needed = false; + std::vector networks; + allocator_->network_manager()->GetNetworks(&networks); + if (networks.empty()) { + LOG(LS_WARNING) << "Machine has no networks; no ports will be allocated"; + done_signal_needed = true; + } else { + for (uint32 i = 0; i < networks.size(); ++i) { + PortConfiguration* config = NULL; + if (configs_.size() > 0) + config = configs_.back(); + + uint32 sequence_flags = flags(); + if ((sequence_flags & DISABLE_ALL_PHASES) == DISABLE_ALL_PHASES) { + // If all the ports are disabled we should just fire the allocation + // done event and return. + done_signal_needed = true; + break; + } + + // Disables phases that are not specified in this config. + if (!config || config->StunServers().empty()) { + // No STUN ports specified in this config. + sequence_flags |= PORTALLOCATOR_DISABLE_STUN; + } + if (!config || config->relays.empty()) { + // No relay ports specified in this config. + sequence_flags |= PORTALLOCATOR_DISABLE_RELAY; + } + + if (!(sequence_flags & PORTALLOCATOR_ENABLE_IPV6) && +#ifdef USE_WEBRTC_DEV_BRANCH + networks[i]->GetBestIP().family() == AF_INET6) { +#else // USE_WEBRTC_DEV_BRANCH + networks[i]->ip().family() == AF_INET6) { +#endif // USE_WEBRTC_DEV_BRANCH + // Skip IPv6 networks unless the flag's been set. + continue; + } + + // Disable phases that would only create ports equivalent to + // ones that we have already made. + DisableEquivalentPhases(networks[i], config, &sequence_flags); + + if ((sequence_flags & DISABLE_ALL_PHASES) == DISABLE_ALL_PHASES) { + // New AllocationSequence would have nothing to do, so don't make it. + continue; + } + + AllocationSequence* sequence = + new AllocationSequence(this, networks[i], config, sequence_flags); + if (!sequence->Init()) { + delete sequence; + continue; + } + done_signal_needed = true; + sequence->SignalPortAllocationComplete.connect( + this, &BasicPortAllocatorSession::OnPortAllocationComplete); + if (running_) + sequence->Start(); + sequences_.push_back(sequence); + } + } + if (done_signal_needed) { + network_thread_->Post(this, MSG_SEQUENCEOBJECTS_CREATED); + } +} + +void BasicPortAllocatorSession::OnNetworksChanged() { + network_manager_started_ = true; + if (allocation_started_) + DoAllocate(); +} + +void BasicPortAllocatorSession::DisableEquivalentPhases( + rtc::Network* network, PortConfiguration* config, uint32* flags) { + for (uint32 i = 0; i < sequences_.size() && + (*flags & DISABLE_ALL_PHASES) != DISABLE_ALL_PHASES; ++i) { + sequences_[i]->DisableEquivalentPhases(network, config, flags); + } +} + +void BasicPortAllocatorSession::AddAllocatedPort(Port* port, + AllocationSequence * seq, + bool prepare_address) { + if (!port) + return; + + LOG(LS_INFO) << "Adding allocated port for " << content_name(); + port->set_content_name(content_name()); + port->set_component(component_); + port->set_generation(generation()); + if (allocator_->proxy().type != rtc::PROXY_NONE) + port->set_proxy(allocator_->user_agent(), allocator_->proxy()); + port->set_send_retransmit_count_attribute((allocator_->flags() & + PORTALLOCATOR_ENABLE_STUN_RETRANSMIT_ATTRIBUTE) != 0); + + // Push down the candidate_filter to individual port. + port->set_candidate_filter(allocator_->candidate_filter()); + + PortData data(port, seq); + ports_.push_back(data); + + port->SignalCandidateReady.connect( + this, &BasicPortAllocatorSession::OnCandidateReady); + port->SignalPortComplete.connect(this, + &BasicPortAllocatorSession::OnPortComplete); + port->SignalDestroyed.connect(this, + &BasicPortAllocatorSession::OnPortDestroyed); + port->SignalPortError.connect( + this, &BasicPortAllocatorSession::OnPortError); + LOG_J(LS_INFO, port) << "Added port to allocator"; + + if (prepare_address) + port->PrepareAddress(); +} + +void BasicPortAllocatorSession::OnAllocationSequenceObjectsCreated() { + allocation_sequences_created_ = true; + // Send candidate allocation complete signal if we have no sequences. + MaybeSignalCandidatesAllocationDone(); +} + +void BasicPortAllocatorSession::OnCandidateReady( + Port* port, const Candidate& c) { + ASSERT(rtc::Thread::Current() == network_thread_); + PortData* data = FindPort(port); + ASSERT(data != NULL); + // Discarding any candidate signal if port allocation status is + // already in completed state. + if (data->complete()) + return; + + // Send candidates whose protocol is enabled. + std::vector candidates; + ProtocolType pvalue; + bool candidate_allowed_to_send = CheckCandidateFilter(c); + if (StringToProto(c.protocol().c_str(), &pvalue) && + data->sequence()->ProtocolEnabled(pvalue) && + candidate_allowed_to_send) { + candidates.push_back(c); + } + + if (!candidates.empty()) { + SignalCandidatesReady(this, candidates); + } + + // Moving to READY state as we have atleast one candidate from the port. + // Since this port has atleast one candidate we should forward this port + // to listners, to allow connections from this port. + // Also we should make sure that candidate gathered from this port is allowed + // to send outside. + if (!data->ready() && candidate_allowed_to_send) { + data->set_ready(); + SignalPortReady(this, port); + } +} + +void BasicPortAllocatorSession::OnPortComplete(Port* port) { + ASSERT(rtc::Thread::Current() == network_thread_); + PortData* data = FindPort(port); + ASSERT(data != NULL); + + // Ignore any late signals. + if (data->complete()) + return; + + // Moving to COMPLETE state. + data->set_complete(); + // Send candidate allocation complete signal if this was the last port. + MaybeSignalCandidatesAllocationDone(); +} + +void BasicPortAllocatorSession::OnPortError(Port* port) { + ASSERT(rtc::Thread::Current() == network_thread_); + PortData* data = FindPort(port); + ASSERT(data != NULL); + // We might have already given up on this port and stopped it. + if (data->complete()) + return; + + // SignalAddressError is currently sent from StunPort/TurnPort. + // But this signal itself is generic. + data->set_error(); + // Send candidate allocation complete signal if this was the last port. + MaybeSignalCandidatesAllocationDone(); +} + +void BasicPortAllocatorSession::OnProtocolEnabled(AllocationSequence* seq, + ProtocolType proto) { + std::vector candidates; + for (std::vector::iterator it = ports_.begin(); + it != ports_.end(); ++it) { + if (it->sequence() != seq) + continue; + + const std::vector& potentials = it->port()->Candidates(); + for (size_t i = 0; i < potentials.size(); ++i) { + if (!CheckCandidateFilter(potentials[i])) + continue; + ProtocolType pvalue; + if (!StringToProto(potentials[i].protocol().c_str(), &pvalue)) + continue; + if (pvalue == proto) { + candidates.push_back(potentials[i]); + } + } + } + + if (!candidates.empty()) { + SignalCandidatesReady(this, candidates); + } +} + +bool BasicPortAllocatorSession::CheckCandidateFilter(const Candidate& c) { + uint32 filter = allocator_->candidate_filter(); + bool allowed = false; + if (filter & CF_RELAY) { + allowed |= (c.type() == RELAY_PORT_TYPE); + } + + if (filter & CF_REFLEXIVE) { + // We allow host candidates if the filter allows server-reflexive candidates + // and the candidate is a public IP. Because we don't generate + // server-reflexive candidates if they have the same IP as the host + // candidate (i.e. when the host candidate is a public IP), filtering to + // only server-reflexive candidates won't work right when the host + // candidates have public IPs. + allowed |= (c.type() == STUN_PORT_TYPE) || + (c.type() == LOCAL_PORT_TYPE && !c.address().IsPrivateIP()); + } + + if (filter & CF_HOST) { + allowed |= (c.type() == LOCAL_PORT_TYPE); + } + + return allowed; +} + +void BasicPortAllocatorSession::OnPortAllocationComplete( + AllocationSequence* seq) { + // Send candidate allocation complete signal if all ports are done. + MaybeSignalCandidatesAllocationDone(); +} + +void BasicPortAllocatorSession::MaybeSignalCandidatesAllocationDone() { + // Send signal only if all required AllocationSequence objects + // are created. + if (!allocation_sequences_created_) + return; + + // Check that all port allocation sequences are complete. + for (std::vector::iterator it = sequences_.begin(); + it != sequences_.end(); ++it) { + if ((*it)->state() == AllocationSequence::kRunning) + return; + } + + // If all allocated ports are in complete state, session must have got all + // expected candidates. Session will trigger candidates allocation complete + // signal. + for (std::vector::iterator it = ports_.begin(); + it != ports_.end(); ++it) { + if (!it->complete()) + return; + } + LOG(LS_INFO) << "All candidates gathered for " << content_name_ << ":" + << component_ << ":" << generation(); + SignalCandidatesAllocationDone(this); +} + +void BasicPortAllocatorSession::OnPortDestroyed( + PortInterface* port) { + ASSERT(rtc::Thread::Current() == network_thread_); + for (std::vector::iterator iter = ports_.begin(); + iter != ports_.end(); ++iter) { + if (port == iter->port()) { + ports_.erase(iter); + LOG_J(LS_INFO, port) << "Removed port from allocator (" + << static_cast(ports_.size()) << " remaining)"; + return; + } + } + ASSERT(false); +} + +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 " << ports.size() << " ports and " + << 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); +} + +BasicPortAllocatorSession::PortData* BasicPortAllocatorSession::FindPort( + Port* port) { + for (std::vector::iterator it = ports_.begin(); + it != ports_.end(); ++it) { + if (it->port() == port) { + return &*it; + } + } + return NULL; +} + +// AllocationSequence + +AllocationSequence::AllocationSequence(BasicPortAllocatorSession* session, + rtc::Network* network, + PortConfiguration* config, + uint32 flags) + : session_(session), + network_(network), + +#ifdef USE_WEBRTC_DEV_BRANCH + ip_(network->GetBestIP()), +#else // USE_WEBRTC_DEV_BRANCH + ip_(network->ip()), +#endif // USE_WEBRTC_DEV_BRANCH + config_(config), + state_(kInit), + flags_(flags), + udp_socket_(), + udp_port_(NULL), + phase_(0) { +} + +bool AllocationSequence::Init() { + if (IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_SOCKET) && + !IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_UFRAG)) { + LOG(LS_ERROR) << "Shared socket option can't be set without " + << "shared ufrag."; + ASSERT(false); + return false; + } + + if (IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_SOCKET)) { + udp_socket_.reset(session_->socket_factory()->CreateUdpSocket( + rtc::SocketAddress(ip_, 0), session_->allocator()->min_port(), + session_->allocator()->max_port())); + if (udp_socket_) { + udp_socket_->SignalReadPacket.connect( + this, &AllocationSequence::OnReadPacket); + } + // Continuing if |udp_socket_| is NULL, as local TCP and RelayPort using TCP + // are next available options to setup a communication channel. + } + return true; +} + +void AllocationSequence::Clear() { + udp_port_ = NULL; + turn_ports_.clear(); +} + +AllocationSequence::~AllocationSequence() { + session_->network_thread()->Clear(this); +} + +void AllocationSequence::DisableEquivalentPhases(rtc::Network* network, + PortConfiguration* config, uint32* flags) { +#ifdef USE_WEBRTC_DEV_BRANCH + if (!((network == network_) && (ip_ == network->GetBestIP()))) { +#else // USE_WEBRTC_DEV_BRANCH + if (!((network == network_) && (ip_ == network->ip()))) { +#endif // USE_WEBRTC_DEV_BRANCH + // Different network setup; nothing is equivalent. + return; + } + + // Else turn off the stuff that we've already got covered. + + // Every config implicitly specifies local, so turn that off right away. + *flags |= PORTALLOCATOR_DISABLE_UDP; + *flags |= PORTALLOCATOR_DISABLE_TCP; + + if (config_ && config) { + if (config_->StunServers() == config->StunServers()) { + // Already got this STUN servers covered. + *flags |= PORTALLOCATOR_DISABLE_STUN; + } + if (!config_->relays.empty()) { + // Already got relays covered. + // NOTE: This will even skip a _different_ set of relay servers if we + // were to be given one, but that never happens in our codebase. Should + // probably get rid of the list in PortConfiguration and just keep a + // single relay server in each one. + *flags |= PORTALLOCATOR_DISABLE_RELAY; + } + } +} + +void AllocationSequence::Start() { + state_ = kRunning; + session_->network_thread()->Post(this, MSG_ALLOCATION_PHASE); +} + +void AllocationSequence::Stop() { + // If the port is completed, don't set it to stopped. + if (state_ == kRunning) { + state_ = kStopped; + session_->network_thread()->Clear(this, MSG_ALLOCATION_PHASE); + } +} + +void AllocationSequence::OnMessage(rtc::Message* msg) { + ASSERT(rtc::Thread::Current() == session_->network_thread()); + ASSERT(msg->message_id == MSG_ALLOCATION_PHASE); + + const char* const PHASE_NAMES[kNumPhases] = { + "Udp", "Relay", "Tcp", "SslTcp" + }; + + // Perform all of the phases in the current step. + LOG_J(LS_INFO, network_) << "Allocation Phase=" + << PHASE_NAMES[phase_]; + + switch (phase_) { + case PHASE_UDP: + CreateUDPPorts(); + CreateStunPorts(); + EnableProtocol(PROTO_UDP); + break; + + case PHASE_RELAY: + CreateRelayPorts(); + break; + + case PHASE_TCP: + CreateTCPPorts(); + EnableProtocol(PROTO_TCP); + break; + + case PHASE_SSLTCP: + state_ = kCompleted; + EnableProtocol(PROTO_SSLTCP); + break; + + default: + ASSERT(false); + } + + if (state() == kRunning) { + ++phase_; + session_->network_thread()->PostDelayed( + session_->allocator()->step_delay(), + this, MSG_ALLOCATION_PHASE); + } else { + // If all phases in AllocationSequence are completed, no allocation + // steps needed further. Canceling pending signal. + session_->network_thread()->Clear(this, MSG_ALLOCATION_PHASE); + SignalPortAllocationComplete(this); + } +} + +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 (IsFlagSet(PORTALLOCATOR_DISABLE_UDP)) { + LOG(LS_VERBOSE) << "AllocationSequence: UDP ports disabled, skipping."; + return; + } + + // TODO(mallinath) - Remove UDPPort creating socket after shared socket + // is enabled completely. + UDPPort* port = NULL; + if (IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_SOCKET) && udp_socket_) { + port = UDPPort::Create(session_->network_thread(), + session_->socket_factory(), network_, + udp_socket_.get(), + session_->username(), session_->password()); + } else { + port = UDPPort::Create(session_->network_thread(), + session_->socket_factory(), + network_, ip_, + session_->allocator()->min_port(), + session_->allocator()->max_port(), + session_->username(), session_->password()); + } + + if (port) { + // If shared socket is enabled, STUN candidate will be allocated by the + // UDPPort. + if (IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_SOCKET)) { + udp_port_ = port; + + // If STUN is not disabled, setting stun server address to port. + if (!IsFlagSet(PORTALLOCATOR_DISABLE_STUN)) { + // If config has stun_servers, use it to get server reflexive candidate + // otherwise use first TURN server which supports UDP. + if (config_ && !config_->StunServers().empty()) { + LOG(LS_INFO) << "AllocationSequence: UDPPort will be handling the " + << "STUN candidate generation."; + port->set_server_addresses(config_->StunServers()); + } else if (config_ && + config_->SupportsProtocol(RELAY_TURN, PROTO_UDP)) { + port->set_server_addresses(config_->GetRelayServerAddresses( + RELAY_TURN, PROTO_UDP)); + LOG(LS_INFO) << "AllocationSequence: TURN Server address will be " + << " used for generating STUN candidate."; + } + } + } + + session_->AddAllocatedPort(port, this, true); + port->SignalDestroyed.connect(this, &AllocationSequence::OnPortDestroyed); + } +} + +void AllocationSequence::CreateTCPPorts() { + if (IsFlagSet(PORTALLOCATOR_DISABLE_TCP)) { + LOG(LS_VERBOSE) << "AllocationSequence: TCP ports disabled, skipping."; + return; + } + + Port* port = TCPPort::Create(session_->network_thread(), + session_->socket_factory(), + network_, ip_, + session_->allocator()->min_port(), + session_->allocator()->max_port(), + session_->username(), session_->password(), + session_->allocator()->allow_tcp_listen()); + if (port) { + session_->AddAllocatedPort(port, this, true); + // Since TCPPort is not created using shared socket, |port| will not be + // added to the dequeue. + } +} + +void AllocationSequence::CreateStunPorts() { + if (IsFlagSet(PORTALLOCATOR_DISABLE_STUN)) { + LOG(LS_VERBOSE) << "AllocationSequence: STUN ports disabled, skipping."; + return; + } + + if (IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_SOCKET)) { + return; + } + + // If BasicPortAllocatorSession::OnAllocate left STUN ports enabled then we + // ought to have an address for them here. + ASSERT(config_ && !config_->StunServers().empty()); + if (!(config_ && !config_->StunServers().empty())) { + LOG(LS_WARNING) + << "AllocationSequence: No STUN server configured, skipping."; + return; + } + + StunPort* port = StunPort::Create(session_->network_thread(), + session_->socket_factory(), + network_, ip_, + session_->allocator()->min_port(), + session_->allocator()->max_port(), + session_->username(), session_->password(), + config_->StunServers()); + if (port) { + session_->AddAllocatedPort(port, this, true); + // Since StunPort is not created using shared socket, |port| will not be + // added to the dequeue. + } +} + +void AllocationSequence::CreateRelayPorts() { + if (IsFlagSet(PORTALLOCATOR_DISABLE_RELAY)) { + LOG(LS_VERBOSE) << "AllocationSequence: Relay ports disabled, skipping."; + return; + } + + // If BasicPortAllocatorSession::OnAllocate left relay ports enabled then we + // ought to have a relay list for them here. + ASSERT(config_ && !config_->relays.empty()); + if (!(config_ && !config_->relays.empty())) { + LOG(LS_WARNING) + << "AllocationSequence: No relay server configured, skipping."; + return; + } + + PortConfiguration::RelayList::const_iterator relay; + for (relay = config_->relays.begin(); + relay != config_->relays.end(); ++relay) { + if (relay->type == RELAY_GTURN) { + CreateGturnPort(*relay); + } else if (relay->type == RELAY_TURN) { + CreateTurnPort(*relay); + } else { + ASSERT(false); + } + } +} + +void AllocationSequence::CreateGturnPort(const RelayServerConfig& config) { + // TODO(mallinath) - Rename RelayPort to GTurnPort. + RelayPort* port = RelayPort::Create(session_->network_thread(), + session_->socket_factory(), + network_, ip_, + session_->allocator()->min_port(), + session_->allocator()->max_port(), + config_->username, config_->password); + if (port) { + // Since RelayPort is not created using shared socket, |port| will not be + // added to the dequeue. + // 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, false); + + // Add the addresses of this protocol. + PortList::const_iterator relay_port; + for (relay_port = config.ports.begin(); + relay_port != config.ports.end(); + ++relay_port) { + port->AddServerAddress(*relay_port); + port->AddExternalAddress(*relay_port); + } + // Start fetching an address for this port. + port->PrepareAddress(); + } +} + +void AllocationSequence::CreateTurnPort(const RelayServerConfig& config) { + PortList::const_iterator relay_port; + for (relay_port = config.ports.begin(); + relay_port != config.ports.end(); ++relay_port) { + TurnPort* port = NULL; + // Shared socket mode must be enabled only for UDP based ports. Hence + // don't pass shared socket for ports which will create TCP sockets. + // TODO(mallinath) - Enable shared socket mode for TURN ports. Disabled + // due to webrtc bug https://code.google.com/p/webrtc/issues/detail?id=3537 + if (IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_SOCKET) && + relay_port->proto == PROTO_UDP) { + port = TurnPort::Create(session_->network_thread(), + session_->socket_factory(), + network_, udp_socket_.get(), + session_->username(), session_->password(), + *relay_port, config.credentials, config.priority); + + turn_ports_.push_back(port); + // Listen to the port destroyed signal, to allow AllocationSequence to + // remove entrt from it's map. + port->SignalDestroyed.connect(this, &AllocationSequence::OnPortDestroyed); + } else { + port = TurnPort::Create(session_->network_thread(), + session_->socket_factory(), + network_, ip_, + session_->allocator()->min_port(), + session_->allocator()->max_port(), + session_->username(), + session_->password(), + *relay_port, config.credentials, config.priority); + } + ASSERT(port != NULL); + session_->AddAllocatedPort(port, this, true); + } +} + +void AllocationSequence::OnReadPacket( + rtc::AsyncPacketSocket* socket, const char* data, size_t size, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time) { + ASSERT(socket == udp_socket_.get()); + + bool turn_port_found = false; + + // Try to find the TurnPort that matches the remote address. Note that the + // message could be a STUN binding response if the TURN server is also used as + // a STUN server. We don't want to parse every message here to check if it is + // a STUN binding response, so we pass the message to TurnPort regardless of + // the message type. The TurnPort will just ignore the message since it will + // not find any request by transaction ID. + for (std::vector::const_iterator it = turn_ports_.begin(); + it != turn_ports_.end(); ++it) { + TurnPort* port = *it; + if (port->server_address().address == remote_addr) { + port->HandleIncomingPacket(socket, data, size, remote_addr, packet_time); + turn_port_found = true; + break; + } + } + + if (udp_port_) { + const ServerAddresses& stun_servers = udp_port_->server_addresses(); + + // Pass the packet to the UdpPort if there is no matching TurnPort, or if + // the TURN server is also a STUN server. + if (!turn_port_found || + stun_servers.find(remote_addr) != stun_servers.end()) { + udp_port_->HandleIncomingPacket( + socket, data, size, remote_addr, packet_time); + } + } +} + +void AllocationSequence::OnPortDestroyed(PortInterface* port) { + if (udp_port_ == port) { + udp_port_ = NULL; + return; + } + + turn_ports_.erase(std::find(turn_ports_.begin(), turn_ports_.end(), port)); +} + +// PortConfiguration +PortConfiguration::PortConfiguration( + const rtc::SocketAddress& stun_address, + const std::string& username, + const std::string& password) + : stun_address(stun_address), username(username), password(password) { + if (!stun_address.IsNil()) + stun_servers.insert(stun_address); +} + +PortConfiguration::PortConfiguration(const ServerAddresses& stun_servers, + const std::string& username, + const std::string& password) + : stun_servers(stun_servers), + username(username), + password(password) { + if (!stun_servers.empty()) + stun_address = *(stun_servers.begin()); +} + +ServerAddresses PortConfiguration::StunServers() { + if (!stun_address.IsNil() && + stun_servers.find(stun_address) == stun_servers.end()) { + stun_servers.insert(stun_address); + } + return stun_servers; +} + +void PortConfiguration::AddRelay(const RelayServerConfig& config) { + relays.push_back(config); +} + +bool PortConfiguration::SupportsProtocol( + const RelayServerConfig& relay, ProtocolType type) const { + 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; +} + +bool PortConfiguration::SupportsProtocol(RelayType turn_type, + ProtocolType type) const { + for (size_t i = 0; i < relays.size(); ++i) { + if (relays[i].type == turn_type && + SupportsProtocol(relays[i], type)) + return true; + } + return false; +} + +ServerAddresses PortConfiguration::GetRelayServerAddresses( + RelayType turn_type, ProtocolType type) const { + ServerAddresses servers; + for (size_t i = 0; i < relays.size(); ++i) { + if (relays[i].type == turn_type && SupportsProtocol(relays[i], type)) { + servers.insert(relays[i].ports.front().address); + } + } + return servers; +} + +} // namespace cricket diff --git a/webrtc/p2p/client/basicportallocator.h b/webrtc/p2p/client/basicportallocator.h new file mode 100644 index 000000000..96468d32c --- /dev/null +++ b/webrtc/p2p/client/basicportallocator.h @@ -0,0 +1,241 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_CLIENT_BASICPORTALLOCATOR_H_ +#define WEBRTC_P2P_CLIENT_BASICPORTALLOCATOR_H_ + +#include +#include + +#include "webrtc/p2p/base/port.h" +#include "webrtc/p2p/base/portallocator.h" +#include "webrtc/base/messagequeue.h" +#include "webrtc/base/network.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/thread.h" + +namespace cricket { + +struct RelayCredentials { + RelayCredentials() {} + RelayCredentials(const std::string& username, + const std::string& password) + : username(username), + password(password) { + } + + std::string username; + std::string password; +}; + +typedef std::vector PortList; +struct RelayServerConfig { + RelayServerConfig(RelayType type) : type(type), priority(0) {} + + RelayType type; + PortList ports; + RelayCredentials credentials; + int priority; +}; + +class BasicPortAllocator : public PortAllocator { + public: + BasicPortAllocator(rtc::NetworkManager* network_manager, + rtc::PacketSocketFactory* socket_factory); + explicit BasicPortAllocator(rtc::NetworkManager* network_manager); + BasicPortAllocator(rtc::NetworkManager* network_manager, + rtc::PacketSocketFactory* socket_factory, + const ServerAddresses& stun_servers); + BasicPortAllocator(rtc::NetworkManager* network_manager, + const ServerAddresses& stun_servers, + const rtc::SocketAddress& relay_server_udp, + const rtc::SocketAddress& relay_server_tcp, + const rtc::SocketAddress& relay_server_ssl); + virtual ~BasicPortAllocator(); + + rtc::NetworkManager* network_manager() { return network_manager_; } + + // If socket_factory() is set to NULL each PortAllocatorSession + // creates its own socket factory. + rtc::PacketSocketFactory* socket_factory() { return socket_factory_; } + + const ServerAddresses& stun_servers() const { + return stun_servers_; + } + + const std::vector& relays() const { + return relays_; + } + virtual void AddRelay(const RelayServerConfig& relay) { + relays_.push_back(relay); + } + + virtual PortAllocatorSession* CreateSessionInternal( + const std::string& content_name, + int component, + const std::string& ice_ufrag, + const std::string& ice_pwd); + + private: + void Construct(); + + rtc::NetworkManager* network_manager_; + rtc::PacketSocketFactory* socket_factory_; + const ServerAddresses stun_servers_; + std::vector relays_; + bool allow_tcp_listen_; +}; + +struct PortConfiguration; +class AllocationSequence; + +class BasicPortAllocatorSession : public PortAllocatorSession, + public rtc::MessageHandler { + public: + BasicPortAllocatorSession(BasicPortAllocator* allocator, + const std::string& content_name, + int component, + const std::string& ice_ufrag, + const std::string& ice_pwd); + ~BasicPortAllocatorSession(); + + virtual BasicPortAllocator* allocator() { return allocator_; } + rtc::Thread* network_thread() { return network_thread_; } + rtc::PacketSocketFactory* socket_factory() { return socket_factory_; } + + virtual void StartGettingPorts(); + virtual void StopGettingPorts(); + virtual bool IsGettingPorts() { 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. + virtual void ConfigReady(PortConfiguration* config); + + // MessageHandler. Can be overriden if message IDs do not conflict. + virtual void OnMessage(rtc::Message *message); + + private: + class PortData { + public: + PortData() : port_(NULL), sequence_(NULL), state_(STATE_INIT) {} + PortData(Port* port, AllocationSequence* seq) + : port_(port), sequence_(seq), state_(STATE_INIT) { + } + + Port* port() { return port_; } + AllocationSequence* sequence() { return sequence_; } + bool ready() const { return state_ == STATE_READY; } + bool complete() const { + // Returns true if candidate allocation has completed one way or another. + return ((state_ == STATE_COMPLETE) || (state_ == STATE_ERROR)); + } + + void set_ready() { ASSERT(state_ == STATE_INIT); state_ = STATE_READY; } + void set_complete() { + state_ = STATE_COMPLETE; + } + void set_error() { + ASSERT(state_ == STATE_INIT || state_ == STATE_READY); + state_ = STATE_ERROR; + } + + private: + enum State { + STATE_INIT, // No candidates allocated yet. + STATE_READY, // At least one candidate is ready for process. + STATE_COMPLETE, // All candidates allocated and ready for process. + STATE_ERROR // Error in gathering candidates. + }; + Port* port_; + AllocationSequence* sequence_; + State state_; + }; + + void OnConfigReady(PortConfiguration* config); + void OnConfigStop(); + void AllocatePorts(); + void OnAllocate(); + void DoAllocate(); + void OnNetworksChanged(); + void OnAllocationSequenceObjectsCreated(); + void DisableEquivalentPhases(rtc::Network* network, + PortConfiguration* config, uint32* flags); + void AddAllocatedPort(Port* port, AllocationSequence* seq, + bool prepare_address); + void OnCandidateReady(Port* port, const Candidate& c); + void OnPortComplete(Port* port); + void OnPortError(Port* port); + void OnProtocolEnabled(AllocationSequence* seq, ProtocolType proto); + void OnPortDestroyed(PortInterface* port); + void OnShake(); + void MaybeSignalCandidatesAllocationDone(); + void OnPortAllocationComplete(AllocationSequence* seq); + PortData* FindPort(Port* port); + + bool CheckCandidateFilter(const Candidate& c); + + BasicPortAllocator* allocator_; + rtc::Thread* network_thread_; + rtc::scoped_ptr owned_socket_factory_; + rtc::PacketSocketFactory* socket_factory_; + bool allocation_started_; + bool network_manager_started_; + bool running_; // set when StartGetAllPorts is called + bool allocation_sequences_created_; + std::vector configs_; + std::vector sequences_; + std::vector ports_; + + friend class AllocationSequence; +}; + +// Records configuration information useful in creating ports. +struct PortConfiguration : public rtc::MessageData { + // TODO(jiayl): remove |stun_address| when Chrome is updated. + rtc::SocketAddress stun_address; + ServerAddresses stun_servers; + std::string username; + std::string password; + + typedef std::vector RelayList; + RelayList relays; + + // TODO(jiayl): remove this ctor when Chrome is updated. + PortConfiguration(const rtc::SocketAddress& stun_address, + const std::string& username, + const std::string& password); + + PortConfiguration(const ServerAddresses& stun_servers, + const std::string& username, + const std::string& password); + + // TODO(jiayl): remove when |stun_address| is removed. + ServerAddresses StunServers(); + + // Adds another relay server, with the given ports and modifier, to the list. + void AddRelay(const RelayServerConfig& config); + + // Determines whether the given relay server supports the given protocol. + bool SupportsProtocol(const RelayServerConfig& relay, + ProtocolType type) const; + bool SupportsProtocol(RelayType turn_type, ProtocolType type) const; + // Helper method returns the server addresses for the matching RelayType and + // Protocol type. + ServerAddresses GetRelayServerAddresses( + RelayType turn_type, ProtocolType type) const; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_CLIENT_BASICPORTALLOCATOR_H_ diff --git a/webrtc/p2p/client/connectivitychecker.cc b/webrtc/p2p/client/connectivitychecker.cc new file mode 100644 index 000000000..6e3598e79 --- /dev/null +++ b/webrtc/p2p/client/connectivitychecker.cc @@ -0,0 +1,573 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/p2p/client/connectivitychecker.h" + +#include "webrtc/p2p/base/candidate.h" +#include "webrtc/p2p/base/common.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/port.h" +#include "webrtc/p2p/base/relayport.h" +#include "webrtc/p2p/base/stunport.h" +#include "webrtc/base/asynchttprequest.h" +#include "webrtc/base/autodetectproxy.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/httpcommon-inl.h" +#include "webrtc/base/httpcommon.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/proxydetect.h" +#include "webrtc/base/thread.h" + +namespace cricket { + +static const char kDefaultStunHostname[] = "stun.l.google.com"; +static const int kDefaultStunPort = 19302; + +// Default maximum time in milliseconds we will wait for connections. +static const uint32 kDefaultTimeoutMs = 3000; + +enum { + MSG_START = 1, + MSG_STOP = 2, + MSG_TIMEOUT = 3, + MSG_SIGNAL_RESULTS = 4 +}; + +class TestHttpPortAllocator : public HttpPortAllocator { + public: + TestHttpPortAllocator(rtc::NetworkManager* network_manager, + const std::string& user_agent, + const std::string& relay_token) : + HttpPortAllocator(network_manager, user_agent) { + SetRelayToken(relay_token); + } + PortAllocatorSession* CreateSessionInternal( + const std::string& content_name, + int component, + const std::string& ice_ufrag, + const std::string& ice_pwd) { + return new TestHttpPortAllocatorSession(this, content_name, component, + ice_ufrag, ice_pwd, + stun_hosts(), relay_hosts(), + relay_token(), user_agent()); + } +}; + +void TestHttpPortAllocatorSession::ConfigReady(PortConfiguration* config) { + SignalConfigReady(username(), password(), config, proxy_); + delete config; +} + +void TestHttpPortAllocatorSession::OnRequestDone( + rtc::SignalThread* data) { + rtc::AsyncHttpRequest* request = + static_cast(data); + + // Tell the checker that the request is complete. + SignalRequestDone(request); + + // Pass on the response to super class. + HttpPortAllocatorSession::OnRequestDone(data); +} + +ConnectivityChecker::ConnectivityChecker( + rtc::Thread* worker, + const std::string& jid, + const std::string& session_id, + const std::string& user_agent, + const std::string& relay_token, + const std::string& connection) + : worker_(worker), + jid_(jid), + session_id_(session_id), + user_agent_(user_agent), + relay_token_(relay_token), + connection_(connection), + proxy_detect_(NULL), + timeout_ms_(kDefaultTimeoutMs), + stun_address_(kDefaultStunHostname, kDefaultStunPort), + started_(false) { +} + +ConnectivityChecker::~ConnectivityChecker() { + if (started_) { + // We try to clear the TIMEOUT below. But worker may still handle it and + // cause SignalCheckDone to happen on main-thread. So we finally clear any + // pending SIGNAL_RESULTS. + worker_->Clear(this, MSG_TIMEOUT); + worker_->Send(this, MSG_STOP); + nics_.clear(); + main_->Clear(this, MSG_SIGNAL_RESULTS); + } +} + +bool ConnectivityChecker::Initialize() { + network_manager_.reset(CreateNetworkManager()); + socket_factory_.reset(CreateSocketFactory(worker_)); + port_allocator_.reset(CreatePortAllocator(network_manager_.get(), + user_agent_, relay_token_)); + uint32 new_allocator_flags = port_allocator_->flags(); + new_allocator_flags |= cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG; + port_allocator_->set_flags(new_allocator_flags); + return true; +} + +void ConnectivityChecker::Start() { + main_ = rtc::Thread::Current(); + worker_->Post(this, MSG_START); + started_ = true; +} + +void ConnectivityChecker::CleanUp() { + ASSERT(worker_ == rtc::Thread::Current()); + if (proxy_detect_) { + proxy_detect_->Release(); + proxy_detect_ = NULL; + } + + for (uint32 i = 0; i < sessions_.size(); ++i) { + delete sessions_[i]; + } + sessions_.clear(); + for (uint32 i = 0; i < ports_.size(); ++i) { + delete ports_[i]; + } + ports_.clear(); +} + +bool ConnectivityChecker::AddNic(const rtc::IPAddress& ip, + const rtc::SocketAddress& proxy_addr) { + NicMap::iterator i = nics_.find(NicId(ip, proxy_addr)); + if (i != nics_.end()) { + // Already have it. + return false; + } + uint32 now = rtc::Time(); + NicInfo info; + info.ip = ip; + info.proxy_info = GetProxyInfo(); + info.stun.start_time_ms = now; + nics_.insert(std::pair(NicId(ip, proxy_addr), info)); + return true; +} + +void ConnectivityChecker::SetProxyInfo(const rtc::ProxyInfo& proxy_info) { + port_allocator_->set_proxy(user_agent_, proxy_info); + AllocatePorts(); +} + +rtc::ProxyInfo ConnectivityChecker::GetProxyInfo() const { + rtc::ProxyInfo proxy_info; + if (proxy_detect_) { + proxy_info = proxy_detect_->proxy(); + } + return proxy_info; +} + +void ConnectivityChecker::CheckNetworks() { + network_manager_->SignalNetworksChanged.connect( + this, &ConnectivityChecker::OnNetworksChanged); + network_manager_->StartUpdating(); +} + +void ConnectivityChecker::OnMessage(rtc::Message *msg) { + switch (msg->message_id) { + case MSG_START: + ASSERT(worker_ == rtc::Thread::Current()); + worker_->PostDelayed(timeout_ms_, this, MSG_TIMEOUT); + CheckNetworks(); + break; + case MSG_STOP: + // We're being stopped, free resources. + CleanUp(); + break; + case MSG_TIMEOUT: + // We need to signal results on the main thread. + main_->Post(this, MSG_SIGNAL_RESULTS); + break; + case MSG_SIGNAL_RESULTS: + ASSERT(main_ == rtc::Thread::Current()); + SignalCheckDone(this); + break; + default: + LOG(LS_ERROR) << "Unknown message: " << msg->message_id; + } +} + +void ConnectivityChecker::OnProxyDetect(rtc::SignalThread* thread) { + ASSERT(worker_ == rtc::Thread::Current()); + if (proxy_detect_->proxy().type != rtc::PROXY_NONE) { + SetProxyInfo(proxy_detect_->proxy()); + } +} + +void ConnectivityChecker::OnRequestDone(rtc::AsyncHttpRequest* request) { + ASSERT(worker_ == rtc::Thread::Current()); + // Since we don't know what nic were actually used for the http request, + // for now, just use the first one. + std::vector networks; + network_manager_->GetNetworks(&networks); + if (networks.empty()) { + LOG(LS_ERROR) << "No networks while registering http start."; + return; + } + rtc::ProxyInfo proxy_info = request->proxy(); + NicMap::iterator i = +#ifdef USE_WEBRTC_DEV_BRANCH + nics_.find(NicId(networks[0]->GetBestIP(), proxy_info.address)); +#else // USE_WEBRTC_DEV_BRANCH + nics_.find(NicId(networks[0]->ip(), proxy_info.address)); +#endif // USE_WEBRTC_DEV_BRANCH + if (i != nics_.end()) { + int port = request->port(); + uint32 now = rtc::Time(); + NicInfo* nic_info = &i->second; + if (port == rtc::HTTP_DEFAULT_PORT) { + nic_info->http.rtt = now - nic_info->http.start_time_ms; + } else if (port == rtc::HTTP_SECURE_PORT) { + nic_info->https.rtt = now - nic_info->https.start_time_ms; + } else { + LOG(LS_ERROR) << "Got response with unknown port: " << port; + } + } else { + LOG(LS_ERROR) << "No nic info found while receiving response."; + } +} + +void ConnectivityChecker::OnConfigReady( + const std::string& username, const std::string& password, + const PortConfiguration* config, const rtc::ProxyInfo& proxy_info) { + ASSERT(worker_ == rtc::Thread::Current()); + + // Since we send requests on both HTTP and HTTPS we will get two + // configs per nic. Results from the second will overwrite the + // result from the first. + // TODO: Handle multiple pings on one nic. + CreateRelayPorts(username, password, config, proxy_info); +} + +void ConnectivityChecker::OnRelayPortComplete(Port* port) { + ASSERT(worker_ == rtc::Thread::Current()); + RelayPort* relay_port = reinterpret_cast(port); + const ProtocolAddress* address = relay_port->ServerAddress(0); +#ifdef USE_WEBRTC_DEV_BRANCH + rtc::IPAddress ip = port->Network()->GetBestIP(); +#else // USE_WEBRTC_DEV_BRANCH + rtc::IPAddress ip = port->Network()->ip(); +#endif // USE_WEBRTC_DEV_BRANCH + NicMap::iterator i = nics_.find(NicId(ip, port->proxy().address)); + if (i != nics_.end()) { + // We have it already, add the new information. + NicInfo* nic_info = &i->second; + ConnectInfo* connect_info = NULL; + if (address) { + switch (address->proto) { + case PROTO_UDP: + connect_info = &nic_info->udp; + break; + case PROTO_TCP: + connect_info = &nic_info->tcp; + break; + case PROTO_SSLTCP: + connect_info = &nic_info->ssltcp; + break; + default: + LOG(LS_ERROR) << " relay address with bad protocol added"; + } + if (connect_info) { + connect_info->rtt = + rtc::TimeSince(connect_info->start_time_ms); + } + } + } else { + LOG(LS_ERROR) << " got relay address for non-existing nic"; + } +} + +void ConnectivityChecker::OnStunPortComplete(Port* port) { + ASSERT(worker_ == rtc::Thread::Current()); + const std::vector candidates = port->Candidates(); + Candidate c = candidates[0]; +#ifdef USE_WEBRTC_DEV_BRANCH + rtc::IPAddress ip = port->Network()->GetBestIP(); +#else // USE_WEBRTC_DEV_BRANCH + rtc::IPAddress ip = port->Network()->ip(); +#endif // USE_WEBRTC_DEV_BRANCH + NicMap::iterator i = nics_.find(NicId(ip, port->proxy().address)); + if (i != nics_.end()) { + // We have it already, add the new information. + uint32 now = rtc::Time(); + NicInfo* nic_info = &i->second; + nic_info->external_address = c.address(); + + nic_info->stun_server_addresses = + static_cast(port)->server_addresses(); + nic_info->stun.rtt = now - nic_info->stun.start_time_ms; + } else { + LOG(LS_ERROR) << "Got stun address for non-existing nic"; + } +} + +void ConnectivityChecker::OnStunPortError(Port* port) { + ASSERT(worker_ == rtc::Thread::Current()); + LOG(LS_ERROR) << "Stun address error."; +#ifdef USE_WEBRTC_DEV_BRANCH + rtc::IPAddress ip = port->Network()->GetBestIP(); +#else // USE_WEBRTC_DEV_BRANCH + rtc::IPAddress ip = port->Network()->ip(); +#endif // USE_WEBRTC_DEV_BRANCH + NicMap::iterator i = nics_.find(NicId(ip, port->proxy().address)); + if (i != nics_.end()) { + // We have it already, add the new information. + NicInfo* nic_info = &i->second; + + nic_info->stun_server_addresses = + static_cast(port)->server_addresses(); + } +} + +void ConnectivityChecker::OnRelayPortError(Port* port) { + ASSERT(worker_ == rtc::Thread::Current()); + LOG(LS_ERROR) << "Relay address error."; +} + +void ConnectivityChecker::OnNetworksChanged() { + ASSERT(worker_ == rtc::Thread::Current()); + std::vector networks; + network_manager_->GetNetworks(&networks); + if (networks.empty()) { + LOG(LS_ERROR) << "Machine has no networks; nothing to do"; + return; + } + AllocatePorts(); +} + +HttpPortAllocator* ConnectivityChecker::CreatePortAllocator( + rtc::NetworkManager* network_manager, + const std::string& user_agent, + const std::string& relay_token) { + return new TestHttpPortAllocator(network_manager, user_agent, relay_token); +} + +StunPort* ConnectivityChecker::CreateStunPort( + const std::string& username, const std::string& password, + const PortConfiguration* config, rtc::Network* network) { + return StunPort::Create(worker_, + socket_factory_.get(), + network, +#ifdef USE_WEBRTC_DEV_BRANCH + network->GetBestIP(), +#else // USE_WEBRTC_DEV_BRANCH + network->ip(), +#endif // USE_WEBRTC_DEV_BRANCH + 0, + 0, + username, + password, + config->stun_servers); +} + +RelayPort* ConnectivityChecker::CreateRelayPort( + const std::string& username, const std::string& password, + const PortConfiguration* config, rtc::Network* network) { + return RelayPort::Create(worker_, + socket_factory_.get(), + network, +#ifdef USE_WEBRTC_DEV_BRANCH + network->GetBestIP(), +#else // USE_WEBRTC_DEV_BRANCH + network->ip(), +#endif // USE_WEBRTC_DEV_BRANCH + port_allocator_->min_port(), + port_allocator_->max_port(), + username, + password); +} + +void ConnectivityChecker::CreateRelayPorts( + const std::string& username, const std::string& password, + const PortConfiguration* config, const rtc::ProxyInfo& proxy_info) { + PortConfiguration::RelayList::const_iterator relay; + std::vector networks; + network_manager_->GetNetworks(&networks); + if (networks.empty()) { + LOG(LS_ERROR) << "Machine has no networks; no relay ports created."; + return; + } + for (relay = config->relays.begin(); + relay != config->relays.end(); ++relay) { + for (uint32 i = 0; i < networks.size(); ++i) { + NicMap::iterator iter = +#ifdef USE_WEBRTC_DEV_BRANCH + nics_.find(NicId(networks[i]->GetBestIP(), proxy_info.address)); +#else // USE_WEBRTC_DEV_BRANCH + nics_.find(NicId(networks[i]->ip(), proxy_info.address)); +#endif // USE_WEBRTC_DEV_BRANCH + if (iter != nics_.end()) { + // TODO: Now setting the same start time for all protocols. + // This might affect accuracy, but since we are mainly looking for + // connect failures or number that stick out, this is good enough. + uint32 now = rtc::Time(); + NicInfo* nic_info = &iter->second; + nic_info->udp.start_time_ms = now; + nic_info->tcp.start_time_ms = now; + nic_info->ssltcp.start_time_ms = now; + + // Add the addresses of this protocol. + PortList::const_iterator relay_port; + for (relay_port = relay->ports.begin(); + relay_port != relay->ports.end(); + ++relay_port) { + RelayPort* port = CreateRelayPort(username, password, + config, networks[i]); + port->AddServerAddress(*relay_port); + port->AddExternalAddress(*relay_port); + + nic_info->media_server_address = port->ServerAddress(0)->address; + + // Listen to network events. + port->SignalPortComplete.connect( + this, &ConnectivityChecker::OnRelayPortComplete); + port->SignalPortError.connect( + this, &ConnectivityChecker::OnRelayPortError); + + port->set_proxy(user_agent_, proxy_info); + + // Start fetching an address for this port. + port->PrepareAddress(); + ports_.push_back(port); + } + } else { + LOG(LS_ERROR) << "Failed to find nic info when creating relay ports."; + } + } + } +} + +void ConnectivityChecker::AllocatePorts() { + const std::string username = rtc::CreateRandomString(ICE_UFRAG_LENGTH); + const std::string password = rtc::CreateRandomString(ICE_PWD_LENGTH); + ServerAddresses stun_servers; + stun_servers.insert(stun_address_); + PortConfiguration config(stun_servers, username, password); + std::vector networks; + network_manager_->GetNetworks(&networks); + if (networks.empty()) { + LOG(LS_ERROR) << "Machine has no networks; no ports will be allocated"; + return; + } + rtc::ProxyInfo proxy_info = GetProxyInfo(); + bool allocate_relay_ports = false; + for (uint32 i = 0; i < networks.size(); ++i) { +#ifdef USE_WEBRTC_DEV_BRANCH + if (AddNic(networks[i]->GetBestIP(), proxy_info.address)) { +#else // USE_WEBRTC_DEV_BRANCH + if (AddNic(networks[i]->ip(), proxy_info.address)) { +#endif // USE_WEBRTC_DEV_BRANCH + Port* port = CreateStunPort(username, password, &config, networks[i]); + if (port) { + + // Listen to network events. + port->SignalPortComplete.connect( + this, &ConnectivityChecker::OnStunPortComplete); + port->SignalPortError.connect( + this, &ConnectivityChecker::OnStunPortError); + + port->set_proxy(user_agent_, proxy_info); + port->PrepareAddress(); + ports_.push_back(port); + allocate_relay_ports = true; + } + } + } + + // If any new ip/proxy combinations were added, send a relay allocate. + if (allocate_relay_ports) { + AllocateRelayPorts(); + } + + // Initiate proxy detection. + InitiateProxyDetection(); +} + +void ConnectivityChecker::InitiateProxyDetection() { + // Only start if we haven't been started before. + if (!proxy_detect_) { + proxy_detect_ = new rtc::AutoDetectProxy(user_agent_); + rtc::Url host_url("/", "relay.google.com", + rtc::HTTP_DEFAULT_PORT); + host_url.set_secure(true); + proxy_detect_->set_server_url(host_url.url()); + proxy_detect_->SignalWorkDone.connect( + this, &ConnectivityChecker::OnProxyDetect); + proxy_detect_->Start(); + } +} + +void ConnectivityChecker::AllocateRelayPorts() { + // Currently we are using the 'default' nic for http(s) requests. + TestHttpPortAllocatorSession* allocator_session = + reinterpret_cast( + port_allocator_->CreateSessionInternal( + "connectivity checker test content", + ICE_CANDIDATE_COMPONENT_RTP, + rtc::CreateRandomString(ICE_UFRAG_LENGTH), + rtc::CreateRandomString(ICE_PWD_LENGTH))); + allocator_session->set_proxy(port_allocator_->proxy()); + allocator_session->SignalConfigReady.connect( + this, &ConnectivityChecker::OnConfigReady); + allocator_session->SignalRequestDone.connect( + this, &ConnectivityChecker::OnRequestDone); + + // Try both http and https. + RegisterHttpStart(rtc::HTTP_SECURE_PORT); + allocator_session->SendSessionRequest("relay.l.google.com", + rtc::HTTP_SECURE_PORT); + RegisterHttpStart(rtc::HTTP_DEFAULT_PORT); + allocator_session->SendSessionRequest("relay.l.google.com", + rtc::HTTP_DEFAULT_PORT); + + sessions_.push_back(allocator_session); +} + +void ConnectivityChecker::RegisterHttpStart(int port) { + // Since we don't know what nic were actually used for the http request, + // for now, just use the first one. + std::vector networks; + network_manager_->GetNetworks(&networks); + if (networks.empty()) { + LOG(LS_ERROR) << "No networks while registering http start."; + return; + } + rtc::ProxyInfo proxy_info = GetProxyInfo(); + NicMap::iterator i = +#ifdef USE_WEBRTC_DEV_BRANCH + nics_.find(NicId(networks[0]->GetBestIP(), proxy_info.address)); +#else // USE_WEBRTC_DEV_BRANCH + nics_.find(NicId(networks[0]->ip(), proxy_info.address)); +#endif // USE_WEBRTC_DEV_BRANCH + if (i != nics_.end()) { + uint32 now = rtc::Time(); + NicInfo* nic_info = &i->second; + if (port == rtc::HTTP_DEFAULT_PORT) { + nic_info->http.start_time_ms = now; + } else if (port == rtc::HTTP_SECURE_PORT) { + nic_info->https.start_time_ms = now; + } else { + LOG(LS_ERROR) << "Registering start time for unknown port: " << port; + } + } else { + LOG(LS_ERROR) << "Error, no nic info found while registering http start."; + } +} + +} // namespace rtc diff --git a/webrtc/p2p/client/connectivitychecker.h b/webrtc/p2p/client/connectivitychecker.h new file mode 100644 index 000000000..427749e5b --- /dev/null +++ b/webrtc/p2p/client/connectivitychecker.h @@ -0,0 +1,281 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_CLIENT_CONNECTIVITYCHECKER_H_ +#define WEBRTC_P2P_CLIENT_CONNECTIVITYCHECKER_H_ + +#include +#include + +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/client/httpportallocator.h" +#include "webrtc/base/basictypes.h" +#include "webrtc/base/messagehandler.h" +#include "webrtc/base/network.h" +#include "webrtc/base/proxyinfo.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/socketaddress.h" + +namespace rtc { +class AsyncHttpRequest; +class AutoDetectProxy; +class BasicPacketSocketFactory; +class NetworkManager; +class PacketSocketFactory; +class SignalThread; +class TestHttpPortAllocatorSession; +class Thread; +} + +namespace cricket { +class HttpPortAllocator; +class Port; +class PortAllocatorSession; +struct PortConfiguration; +class RelayPort; +class StunPort; + +// Contains details about a discovered firewall that are of interest +// when debugging call failures. +struct FirewallInfo { + std::string brand; + std::string model; + + // TODO: List of current port mappings. +}; + +// Contains details about a specific connect attempt. +struct ConnectInfo { + ConnectInfo() + : rtt(-1), error(0) {} + // Time when the connection was initiated. Needed for calculating + // the round trip time. + uint32 start_time_ms; + // Round trip time in milliseconds or -1 for failed connection. + int32 rtt; + // Error code representing low level errors like socket errors. + int error; +}; + +// Identifier for a network interface and proxy address pair. +struct NicId { + NicId(const rtc::IPAddress& ip, + const rtc::SocketAddress& proxy_address) + : ip(ip), + proxy_address(proxy_address) { + } + rtc::IPAddress ip; + rtc::SocketAddress proxy_address; +}; + +// Comparator implementation identifying unique network interface and +// proxy address pairs. +class NicIdComparator { + public: + int compare(const NicId &first, const NicId &second) const { + if (first.ip == second.ip) { + // Compare proxy address. + if (first.proxy_address == second.proxy_address) { + return 0; + } else { + return first.proxy_address < second.proxy_address? -1 : 1; + } + } + return first.ip < second.ip ? -1 : 1; + } + + bool operator()(const NicId &first, const NicId &second) const { + return (compare(first, second) < 0); + } +}; + +// Contains information of a network interface and proxy address pair. +struct NicInfo { + NicInfo() {} + rtc::IPAddress ip; + rtc::ProxyInfo proxy_info; + rtc::SocketAddress external_address; + ServerAddresses stun_server_addresses; + rtc::SocketAddress media_server_address; + ConnectInfo stun; + ConnectInfo http; + ConnectInfo https; + ConnectInfo udp; + ConnectInfo tcp; + ConnectInfo ssltcp; + FirewallInfo firewall; +}; + +// Holds the result of the connectivity check. +class NicMap : public std::map { +}; + +class TestHttpPortAllocatorSession : public HttpPortAllocatorSession { + public: + TestHttpPortAllocatorSession( + HttpPortAllocator* allocator, + const std::string& content_name, + int component, + const std::string& ice_ufrag, + const std::string& ice_pwd, + const std::vector& stun_hosts, + const std::vector& relay_hosts, + const std::string& relay_token, + const std::string& user_agent) + : HttpPortAllocatorSession( + allocator, content_name, component, ice_ufrag, ice_pwd, stun_hosts, + relay_hosts, relay_token, user_agent) { + } + void set_proxy(const rtc::ProxyInfo& proxy) { + proxy_ = proxy; + } + + void ConfigReady(PortConfiguration* config); + + void OnRequestDone(rtc::SignalThread* data); + + sigslot::signal4 SignalConfigReady; + sigslot::signal1 SignalRequestDone; + + private: + rtc::ProxyInfo proxy_; +}; + +// Runs a request/response check on all network interface and proxy +// address combinations. The check is considered done either when all +// checks has been successful or when the check times out. +class ConnectivityChecker + : public rtc::MessageHandler, public sigslot::has_slots<> { + public: + ConnectivityChecker(rtc::Thread* worker, + const std::string& jid, + const std::string& session_id, + const std::string& user_agent, + const std::string& relay_token, + const std::string& connection); + virtual ~ConnectivityChecker(); + + // Virtual for gMock. + virtual bool Initialize(); + virtual void Start(); + + // MessageHandler implementation. + virtual void OnMessage(rtc::Message *msg); + + // Instruct checker to stop and wait until that's done. + // Virtual for gMock. + virtual void Stop() { + worker_->Stop(); + } + + const NicMap& GetResults() const { + return nics_; + } + + void set_timeout_ms(uint32 timeout) { + timeout_ms_ = timeout; + } + + void set_stun_address(const rtc::SocketAddress& stun_address) { + stun_address_ = stun_address; + } + + const std::string& connection() const { + return connection_; + } + + const std::string& jid() const { + return jid_; + } + + const std::string& session_id() const { + return session_id_; + } + + // Context: Main Thread. Signalled when the connectivity check is complete. + sigslot::signal1 SignalCheckDone; + + protected: + // Can be overridden for test. + virtual rtc::NetworkManager* CreateNetworkManager() { + return new rtc::BasicNetworkManager(); + } + virtual rtc::BasicPacketSocketFactory* CreateSocketFactory( + rtc::Thread* thread) { + return new rtc::BasicPacketSocketFactory(thread); + } + virtual HttpPortAllocator* CreatePortAllocator( + rtc::NetworkManager* network_manager, + const std::string& user_agent, + const std::string& relay_token); + virtual StunPort* CreateStunPort( + const std::string& username, const std::string& password, + const PortConfiguration* config, rtc::Network* network); + virtual RelayPort* CreateRelayPort( + const std::string& username, const std::string& password, + const PortConfiguration* config, rtc::Network* network); + virtual void InitiateProxyDetection(); + virtual void SetProxyInfo(const rtc::ProxyInfo& info); + virtual rtc::ProxyInfo GetProxyInfo() const; + + rtc::Thread* worker() { + return worker_; + } + + private: + bool AddNic(const rtc::IPAddress& ip, + const rtc::SocketAddress& proxy_address); + void AllocatePorts(); + void AllocateRelayPorts(); + void CheckNetworks(); + void CreateRelayPorts( + const std::string& username, const std::string& password, + const PortConfiguration* config, const rtc::ProxyInfo& proxy_info); + + // Must be called by the worker thread. + void CleanUp(); + + void OnRequestDone(rtc::AsyncHttpRequest* request); + void OnRelayPortComplete(Port* port); + void OnStunPortComplete(Port* port); + void OnRelayPortError(Port* port); + void OnStunPortError(Port* port); + void OnNetworksChanged(); + void OnProxyDetect(rtc::SignalThread* thread); + void OnConfigReady( + const std::string& username, const std::string& password, + const PortConfiguration* config, const rtc::ProxyInfo& proxy); + void OnConfigWithProxyReady(const PortConfiguration*); + void RegisterHttpStart(int port); + rtc::Thread* worker_; + std::string jid_; + std::string session_id_; + std::string user_agent_; + std::string relay_token_; + std::string connection_; + rtc::AutoDetectProxy* proxy_detect_; + rtc::scoped_ptr network_manager_; + rtc::scoped_ptr socket_factory_; + rtc::scoped_ptr port_allocator_; + NicMap nics_; + std::vector ports_; + std::vector sessions_; + uint32 timeout_ms_; + rtc::SocketAddress stun_address_; + rtc::Thread* main_; + bool started_; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_CLIENT_CONNECTIVITYCHECKER_H_ diff --git a/webrtc/p2p/client/connectivitychecker_unittest.cc b/webrtc/p2p/client/connectivitychecker_unittest.cc new file mode 100644 index 000000000..838dc8864 --- /dev/null +++ b/webrtc/p2p/client/connectivitychecker_unittest.cc @@ -0,0 +1,375 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/relayport.h" +#include "webrtc/p2p/base/stunport.h" +#include "webrtc/p2p/client/connectivitychecker.h" +#include "webrtc/p2p/client/httpportallocator.h" +#include "webrtc/base/asynchttprequest.h" +#include "webrtc/base/fakenetwork.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/socketaddress.h" + +namespace cricket { + +static const rtc::SocketAddress kClientAddr1("11.11.11.11", 0); +static const rtc::SocketAddress kClientAddr2("22.22.22.22", 0); +static const rtc::SocketAddress kExternalAddr("33.33.33.33", 3333); +static const rtc::SocketAddress kStunAddr("44.44.44.44", 4444); +static const rtc::SocketAddress kRelayAddr("55.55.55.55", 5555); +static const rtc::SocketAddress kProxyAddr("66.66.66.66", 6666); +static const rtc::ProxyType kProxyType = rtc::PROXY_HTTPS; +static const char kRelayHost[] = "relay.google.com"; +static const char kRelayToken[] = + "CAESFwoOb2phQGdvb2dsZS5jb20Q043h47MmGhBTB1rbfIXkhuarDCZe+xF6"; +static const char kBrowserAgent[] = "browser_test"; +static const char kJid[] = "a.b@c"; +static const char kUserName[] = "testuser"; +static const char kPassword[] = "testpassword"; +static const char kMagicCookie[] = "testcookie"; +static const char kRelayUdpPort[] = "4444"; +static const char kRelayTcpPort[] = "5555"; +static const char kRelaySsltcpPort[] = "6666"; +static const char kSessionId[] = "testsession"; +static const char kConnection[] = "testconnection"; +static const int kMinPort = 1000; +static const int kMaxPort = 2000; + +// Fake implementation to mock away real network usage. +class FakeRelayPort : public RelayPort { + public: + FakeRelayPort(rtc::Thread* thread, + rtc::PacketSocketFactory* factory, + rtc::Network* network, const rtc::IPAddress& ip, + int min_port, int max_port, + const std::string& username, const std::string& password) + : RelayPort(thread, factory, network, ip, min_port, max_port, + username, password) { + } + + // Just signal that we are done. + virtual void PrepareAddress() { + SignalPortComplete(this); + } +}; + +// Fake implementation to mock away real network usage. +class FakeStunPort : public StunPort { + public: + FakeStunPort(rtc::Thread* thread, + rtc::PacketSocketFactory* factory, + rtc::Network* network, + const rtc::IPAddress& ip, + int min_port, int max_port, + const std::string& username, const std::string& password, + const ServerAddresses& server_addr) + : StunPort(thread, factory, network, ip, min_port, max_port, + username, password, server_addr) { + } + + // Just set external address and signal that we are done. + virtual void PrepareAddress() { + AddAddress(kExternalAddr, kExternalAddr, rtc::SocketAddress(), "udp", "", + STUN_PORT_TYPE, ICE_TYPE_PREFERENCE_SRFLX, 0, true); + SignalPortComplete(this); + } +}; + +// Fake implementation to mock away real network usage by responding +// to http requests immediately. +class FakeHttpPortAllocatorSession : public TestHttpPortAllocatorSession { + public: + FakeHttpPortAllocatorSession( + HttpPortAllocator* allocator, + const std::string& content_name, + int component, + const std::string& ice_ufrag, const std::string& ice_pwd, + const std::vector& stun_hosts, + const std::vector& relay_hosts, + const std::string& relay_token, + const std::string& agent) + : TestHttpPortAllocatorSession(allocator, + content_name, + component, + ice_ufrag, + ice_pwd, + stun_hosts, + relay_hosts, + relay_token, + agent) { + } + virtual void SendSessionRequest(const std::string& host, int port) { + FakeReceiveSessionResponse(host, port); + } + + // Pass results to the real implementation. + void FakeReceiveSessionResponse(const std::string& host, int port) { + rtc::AsyncHttpRequest* response = CreateAsyncHttpResponse(port); + TestHttpPortAllocatorSession::OnRequestDone(response); + response->Destroy(true); + } + + private: + // Helper method for creating a response to a relay session request. + rtc::AsyncHttpRequest* CreateAsyncHttpResponse(int port) { + rtc::AsyncHttpRequest* request = + new rtc::AsyncHttpRequest(kBrowserAgent); + std::stringstream ss; + ss << "username=" << kUserName << std::endl + << "password=" << kPassword << std::endl + << "magic_cookie=" << kMagicCookie << std::endl + << "relay.ip=" << kRelayAddr.ipaddr().ToString() << std::endl + << "relay.udp_port=" << kRelayUdpPort << std::endl + << "relay.tcp_port=" << kRelayTcpPort << std::endl + << "relay.ssltcp_port=" << kRelaySsltcpPort << std::endl; + request->response().document.reset( + new rtc::MemoryStream(ss.str().c_str())); + request->response().set_success(); + request->set_port(port); + request->set_secure(port == rtc::HTTP_SECURE_PORT); + return request; + } +}; + +// Fake implementation for creating fake http sessions. +class FakeHttpPortAllocator : public HttpPortAllocator { + public: + FakeHttpPortAllocator(rtc::NetworkManager* network_manager, + const std::string& user_agent) + : HttpPortAllocator(network_manager, user_agent) { + } + + virtual PortAllocatorSession* CreateSessionInternal( + const std::string& content_name, int component, + const std::string& ice_ufrag, const std::string& ice_pwd) { + std::vector stun_hosts; + stun_hosts.push_back(kStunAddr); + std::vector relay_hosts; + relay_hosts.push_back(kRelayHost); + return new FakeHttpPortAllocatorSession(this, + content_name, + component, + ice_ufrag, + ice_pwd, + stun_hosts, + relay_hosts, + kRelayToken, + kBrowserAgent); + } +}; + +class ConnectivityCheckerForTest : public ConnectivityChecker { + public: + ConnectivityCheckerForTest(rtc::Thread* worker, + const std::string& jid, + const std::string& session_id, + const std::string& user_agent, + const std::string& relay_token, + const std::string& connection) + : ConnectivityChecker(worker, + jid, + session_id, + user_agent, + relay_token, + connection), + proxy_initiated_(false) { + } + + rtc::FakeNetworkManager* network_manager() const { + return network_manager_; + } + + FakeHttpPortAllocator* port_allocator() const { + return fake_port_allocator_; + } + + protected: + // Overridden methods for faking a real network. + virtual rtc::NetworkManager* CreateNetworkManager() { + network_manager_ = new rtc::FakeNetworkManager(); + return network_manager_; + } + virtual rtc::BasicPacketSocketFactory* CreateSocketFactory( + rtc::Thread* thread) { + // Create socket factory, for simplicity, let it run on the current thread. + socket_factory_ = + new rtc::BasicPacketSocketFactory(rtc::Thread::Current()); + return socket_factory_; + } + virtual HttpPortAllocator* CreatePortAllocator( + rtc::NetworkManager* network_manager, + const std::string& user_agent, + const std::string& relay_token) { + fake_port_allocator_ = + new FakeHttpPortAllocator(network_manager, user_agent); + return fake_port_allocator_; + } + virtual StunPort* CreateStunPort( + const std::string& username, const std::string& password, + const PortConfiguration* config, rtc::Network* network) { + return new FakeStunPort(worker(), + socket_factory_, + network, +#ifdef USE_WEBRTC_DEV_BRANCH + network->GetBestIP(), +#else // USE_WEBRTC_DEV_BRANCH + network->ip(), +#endif // USE_WEBRTC_DEV_BRANCH + kMinPort, + kMaxPort, + username, + password, + config->stun_servers); + } + virtual RelayPort* CreateRelayPort( + const std::string& username, const std::string& password, + const PortConfiguration* config, rtc::Network* network) { + return new FakeRelayPort(worker(), + socket_factory_, + network, +#ifdef USE_WEBRTC_DEV_BRANCH + network->GetBestIP(), +#else // USE_WEBRTC_DEV_BRANCH + network->ip(), +#endif // USE_WEBRTC_DEV_BRANCH + kMinPort, + kMaxPort, + username, + password); + } + virtual void InitiateProxyDetection() { + if (!proxy_initiated_) { + proxy_initiated_ = true; + proxy_info_.address = kProxyAddr; + proxy_info_.type = kProxyType; + SetProxyInfo(proxy_info_); + } + } + + virtual rtc::ProxyInfo GetProxyInfo() const { + return proxy_info_; + } + + private: + rtc::BasicPacketSocketFactory* socket_factory_; + FakeHttpPortAllocator* fake_port_allocator_; + rtc::FakeNetworkManager* network_manager_; + rtc::ProxyInfo proxy_info_; + bool proxy_initiated_; +}; + +class ConnectivityCheckerTest : public testing::Test { + protected: + void VerifyNic(const NicInfo& info, + const rtc::SocketAddress& local_address) { + // Verify that the external address has been set. + EXPECT_EQ(kExternalAddr, info.external_address); + + // Verify that the stun server address has been set. + EXPECT_EQ(1U, info.stun_server_addresses.size()); + EXPECT_EQ(kStunAddr, *(info.stun_server_addresses.begin())); + + // Verify that the media server address has been set. Don't care + // about port since it is different for different protocols. + EXPECT_EQ(kRelayAddr.ipaddr(), info.media_server_address.ipaddr()); + + // Verify that local ip matches. + EXPECT_EQ(local_address.ipaddr(), info.ip); + + // Verify that we have received responses for our + // pings. Unsuccessful ping has rtt value -1, successful >= 0. + EXPECT_GE(info.stun.rtt, 0); + EXPECT_GE(info.udp.rtt, 0); + EXPECT_GE(info.tcp.rtt, 0); + EXPECT_GE(info.ssltcp.rtt, 0); + + // If proxy has been set, verify address and type. + if (!info.proxy_info.address.IsNil()) { + EXPECT_EQ(kProxyAddr, info.proxy_info.address); + EXPECT_EQ(kProxyType, info.proxy_info.type); + } + } +}; + +// Tests a configuration with two network interfaces. Verifies that 4 +// combinations of ip/proxy are created and that all protocols are +// tested on each combination. +TEST_F(ConnectivityCheckerTest, TestStart) { + ConnectivityCheckerForTest connectivity_checker(rtc::Thread::Current(), + kJid, + kSessionId, + kBrowserAgent, + kRelayToken, + kConnection); + connectivity_checker.Initialize(); + connectivity_checker.set_stun_address(kStunAddr); + connectivity_checker.network_manager()->AddInterface(kClientAddr1); + connectivity_checker.network_manager()->AddInterface(kClientAddr2); + + connectivity_checker.Start(); + rtc::Thread::Current()->ProcessMessages(1000); + + NicMap nics = connectivity_checker.GetResults(); + + // There should be 4 nics in our map. 2 for each interface added, + // one with proxy set and one without. + EXPECT_EQ(4U, nics.size()); + + // First verify interfaces without proxy. + rtc::SocketAddress nilAddress; + + // First lookup the address of the first nic combined with no proxy. + NicMap::iterator i = nics.find(NicId(kClientAddr1.ipaddr(), nilAddress)); + ASSERT(i != nics.end()); + NicInfo info = i->second; + VerifyNic(info, kClientAddr1); + + // Then make sure the second device has been tested without proxy. + i = nics.find(NicId(kClientAddr2.ipaddr(), nilAddress)); + ASSERT(i != nics.end()); + info = i->second; + VerifyNic(info, kClientAddr2); + + // Now verify both interfaces with proxy. + i = nics.find(NicId(kClientAddr1.ipaddr(), kProxyAddr)); + ASSERT(i != nics.end()); + info = i->second; + VerifyNic(info, kClientAddr1); + + i = nics.find(NicId(kClientAddr2.ipaddr(), kProxyAddr)); + ASSERT(i != nics.end()); + info = i->second; + VerifyNic(info, kClientAddr2); +}; + +// Tests that nothing bad happens if thera are no network interfaces +// available to check. +TEST_F(ConnectivityCheckerTest, TestStartNoNetwork) { + ConnectivityCheckerForTest connectivity_checker(rtc::Thread::Current(), + kJid, + kSessionId, + kBrowserAgent, + kRelayToken, + kConnection); + connectivity_checker.Initialize(); + connectivity_checker.Start(); + rtc::Thread::Current()->ProcessMessages(1000); + + NicMap nics = connectivity_checker.GetResults(); + + // Verify that no nics where checked. + EXPECT_EQ(0U, nics.size()); +} + +} // namespace cricket diff --git a/webrtc/p2p/client/fakeportallocator.h b/webrtc/p2p/client/fakeportallocator.h new file mode 100644 index 000000000..73dae3afc --- /dev/null +++ b/webrtc/p2p/client/fakeportallocator.h @@ -0,0 +1,121 @@ +/* + * Copyright 2010 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_CLIENT_FAKEPORTALLOCATOR_H_ +#define WEBRTC_P2P_CLIENT_FAKEPORTALLOCATOR_H_ + +#include +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/portallocator.h" +#include "webrtc/p2p/base/udpport.h" +#include "webrtc/base/scoped_ptr.h" + +namespace rtc { +class SocketFactory; +class Thread; +} + +namespace cricket { + +class FakePortAllocatorSession : public PortAllocatorSession { + public: + FakePortAllocatorSession(rtc::Thread* worker_thread, + rtc::PacketSocketFactory* factory, + const std::string& content_name, + int component, + const std::string& ice_ufrag, + const std::string& ice_pwd) + : PortAllocatorSession(content_name, component, ice_ufrag, ice_pwd, + cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG), + worker_thread_(worker_thread), + factory_(factory), + network_("network", "unittest", + rtc::IPAddress(INADDR_LOOPBACK), 8), + port_(), running_(false), + port_config_count_(0) { + network_.AddIP(rtc::IPAddress(INADDR_LOOPBACK)); + } + + virtual void StartGettingPorts() { + if (!port_) { + port_.reset(cricket::UDPPort::Create(worker_thread_, + factory_, + &network_, +#ifdef USE_WEBRTC_DEV_BRANCH + network_.GetBestIP(), +#else // USE_WEBRTC_DEV_BRANCH + network_.ip(), +#endif // USE_WEBRTC_DEV_BRANCH + 0, + 0, + username(), + password())); + AddPort(port_.get()); + } + ++port_config_count_; + running_ = true; + } + + virtual void StopGettingPorts() { running_ = false; } + virtual bool IsGettingPorts() { return running_; } + int port_config_count() { return port_config_count_; } + + void AddPort(cricket::Port* port) { + port->set_component(component_); + port->set_generation(0); + port->SignalPortComplete.connect( + this, &FakePortAllocatorSession::OnPortComplete); + port->PrepareAddress(); + SignalPortReady(this, port); + } + void OnPortComplete(cricket::Port* port) { + SignalCandidatesReady(this, port->Candidates()); + SignalCandidatesAllocationDone(this); + } + + private: + rtc::Thread* worker_thread_; + rtc::PacketSocketFactory* factory_; + rtc::Network network_; + rtc::scoped_ptr port_; + bool running_; + int port_config_count_; +}; + +class FakePortAllocator : public cricket::PortAllocator { + public: + FakePortAllocator(rtc::Thread* worker_thread, + rtc::PacketSocketFactory* factory) + : worker_thread_(worker_thread), factory_(factory) { + if (factory_ == NULL) { + owned_factory_.reset(new rtc::BasicPacketSocketFactory( + worker_thread_)); + factory_ = owned_factory_.get(); + } + } + + virtual cricket::PortAllocatorSession* CreateSessionInternal( + const std::string& content_name, + int component, + const std::string& ice_ufrag, + const std::string& ice_pwd) { + return new FakePortAllocatorSession( + worker_thread_, factory_, content_name, component, ice_ufrag, ice_pwd); + } + + private: + rtc::Thread* worker_thread_; + rtc::PacketSocketFactory* factory_; + rtc::scoped_ptr owned_factory_; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_CLIENT_FAKEPORTALLOCATOR_H_ diff --git a/webrtc/p2p/client/httpportallocator.cc b/webrtc/p2p/client/httpportallocator.cc new file mode 100644 index 000000000..c072da27e --- /dev/null +++ b/webrtc/p2p/client/httpportallocator.cc @@ -0,0 +1,326 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/client/httpportallocator.h" + +#include +#include + +#include "webrtc/base/asynchttprequest.h" +#include "webrtc/base/basicdefs.h" +#include "webrtc/base/common.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/nethelpers.h" +#include "webrtc/base/signalthread.h" +#include "webrtc/base/stringencode.h" + +namespace { + +// Helper routine to remove whitespace from the ends of a string. +void Trim(std::string& str) { + size_t first = str.find_first_not_of(" \t\r\n"); + if (first == std::string::npos) { + str.clear(); + return; + } + + ASSERT(str.find_last_not_of(" \t\r\n") != std::string::npos); +} + +// Parses the lines in the result of the HTTP request that are of the form +// 'a=b' and returns them in a map. +typedef std::map StringMap; +void ParseMap(const std::string& string, StringMap& map) { + size_t start_of_line = 0; + size_t end_of_line = 0; + + for (;;) { // for each line + start_of_line = string.find_first_not_of("\r\n", end_of_line); + if (start_of_line == std::string::npos) + break; + + end_of_line = string.find_first_of("\r\n", start_of_line); + if (end_of_line == std::string::npos) { + end_of_line = string.length(); + } + + size_t equals = string.find('=', start_of_line); + if ((equals >= end_of_line) || (equals == std::string::npos)) + continue; + + std::string key(string, start_of_line, equals - start_of_line); + std::string value(string, equals + 1, end_of_line - equals - 1); + + Trim(key); + Trim(value); + + if ((key.size() > 0) && (value.size() > 0)) + map[key] = value; + } +} + +} // namespace + +namespace cricket { + +// HttpPortAllocatorBase + +const int HttpPortAllocatorBase::kNumRetries = 5; + +const char HttpPortAllocatorBase::kCreateSessionURL[] = "/create_session"; + +HttpPortAllocatorBase::HttpPortAllocatorBase( + rtc::NetworkManager* network_manager, + rtc::PacketSocketFactory* socket_factory, + const std::string &user_agent) + : BasicPortAllocator(network_manager, socket_factory), agent_(user_agent) { + relay_hosts_.push_back("relay.google.com"); + stun_hosts_.push_back( + rtc::SocketAddress("stun.l.google.com", 19302)); +} + +HttpPortAllocatorBase::HttpPortAllocatorBase( + rtc::NetworkManager* network_manager, + const std::string &user_agent) + : BasicPortAllocator(network_manager), agent_(user_agent) { + relay_hosts_.push_back("relay.google.com"); + stun_hosts_.push_back( + rtc::SocketAddress("stun.l.google.com", 19302)); +} + +HttpPortAllocatorBase::~HttpPortAllocatorBase() { +} + +// HttpPortAllocatorSessionBase + +HttpPortAllocatorSessionBase::HttpPortAllocatorSessionBase( + HttpPortAllocatorBase* allocator, + const std::string& content_name, + int component, + const std::string& ice_ufrag, + const std::string& ice_pwd, + const std::vector& stun_hosts, + const std::vector& relay_hosts, + const std::string& relay_token, + const std::string& user_agent) + : BasicPortAllocatorSession(allocator, content_name, component, + ice_ufrag, ice_pwd), + relay_hosts_(relay_hosts), stun_hosts_(stun_hosts), + relay_token_(relay_token), agent_(user_agent), attempts_(0) { +} + +HttpPortAllocatorSessionBase::~HttpPortAllocatorSessionBase() {} + +void HttpPortAllocatorSessionBase::GetPortConfigurations() { + // Creating relay sessions can take time and is done asynchronously. + // Creating stun sessions could also take time and could be done aysnc also, + // but for now is done here and added to the initial config. Note any later + // configs will have unresolved stun ips and will be discarded by the + // AllocationSequence. + ServerAddresses hosts; + for (std::vector::iterator it = stun_hosts_.begin(); + it != stun_hosts_.end(); ++it) { + hosts.insert(*it); + } + + PortConfiguration* config = new PortConfiguration(hosts, + username(), + password()); + ConfigReady(config); + TryCreateRelaySession(); +} + +void HttpPortAllocatorSessionBase::TryCreateRelaySession() { + if (allocator()->flags() & PORTALLOCATOR_DISABLE_RELAY) { + LOG(LS_VERBOSE) << "HttpPortAllocator: Relay ports disabled, skipping."; + return; + } + + if (attempts_ == HttpPortAllocator::kNumRetries) { + LOG(LS_ERROR) << "HttpPortAllocator: maximum number of requests reached; " + << "giving up on relay."; + return; + } + + if (relay_hosts_.size() == 0) { + LOG(LS_ERROR) << "HttpPortAllocator: no relay hosts configured."; + return; + } + + // Choose the next host to try. + std::string host = relay_hosts_[attempts_ % relay_hosts_.size()]; + attempts_++; + LOG(LS_INFO) << "HTTPPortAllocator: sending to relay host " << host; + if (relay_token_.empty()) { + LOG(LS_WARNING) << "No relay auth token found."; + } + + SendSessionRequest(host, rtc::HTTP_SECURE_PORT); +} + +std::string HttpPortAllocatorSessionBase::GetSessionRequestUrl() { + std::string url = std::string(HttpPortAllocator::kCreateSessionURL); + if (allocator()->flags() & PORTALLOCATOR_ENABLE_SHARED_UFRAG) { + ASSERT(!username().empty()); + ASSERT(!password().empty()); + url = url + "?username=" + rtc::s_url_encode(username()) + + "&password=" + rtc::s_url_encode(password()); + } + return url; +} + +void HttpPortAllocatorSessionBase::ReceiveSessionResponse( + const std::string& response) { + + StringMap map; + ParseMap(response, map); + + if (!username().empty() && map["username"] != username()) { + LOG(LS_WARNING) << "Received unexpected username value from relay server."; + } + if (!password().empty() && map["password"] != password()) { + LOG(LS_WARNING) << "Received unexpected password value from relay server."; + } + + std::string relay_ip = map["relay.ip"]; + std::string relay_udp_port = map["relay.udp_port"]; + std::string relay_tcp_port = map["relay.tcp_port"]; + std::string relay_ssltcp_port = map["relay.ssltcp_port"]; + + ServerAddresses hosts; + for (std::vector::iterator it = stun_hosts_.begin(); + it != stun_hosts_.end(); ++it) { + hosts.insert(*it); + } + + PortConfiguration* config = new PortConfiguration(hosts, + map["username"], + map["password"]); + + RelayServerConfig relay_config(RELAY_GTURN); + if (!relay_udp_port.empty()) { + rtc::SocketAddress address(relay_ip, atoi(relay_udp_port.c_str())); + relay_config.ports.push_back(ProtocolAddress(address, PROTO_UDP)); + } + if (!relay_tcp_port.empty()) { + rtc::SocketAddress address(relay_ip, atoi(relay_tcp_port.c_str())); + relay_config.ports.push_back(ProtocolAddress(address, PROTO_TCP)); + } + if (!relay_ssltcp_port.empty()) { + rtc::SocketAddress address(relay_ip, atoi(relay_ssltcp_port.c_str())); + relay_config.ports.push_back(ProtocolAddress(address, PROTO_SSLTCP)); + } + config->AddRelay(relay_config); + ConfigReady(config); +} + +// HttpPortAllocator + +HttpPortAllocator::HttpPortAllocator( + rtc::NetworkManager* network_manager, + rtc::PacketSocketFactory* socket_factory, + const std::string &user_agent) + : HttpPortAllocatorBase(network_manager, socket_factory, user_agent) { +} + +HttpPortAllocator::HttpPortAllocator( + rtc::NetworkManager* network_manager, + const std::string &user_agent) + : HttpPortAllocatorBase(network_manager, user_agent) { +} +HttpPortAllocator::~HttpPortAllocator() {} + +PortAllocatorSession* HttpPortAllocator::CreateSessionInternal( + const std::string& content_name, + int component, + const std::string& ice_ufrag, const std::string& ice_pwd) { + return new HttpPortAllocatorSession(this, content_name, component, + ice_ufrag, ice_pwd, stun_hosts(), + relay_hosts(), relay_token(), + user_agent()); +} + +// HttpPortAllocatorSession + +HttpPortAllocatorSession::HttpPortAllocatorSession( + HttpPortAllocator* allocator, + const std::string& content_name, + int component, + const std::string& ice_ufrag, + const std::string& ice_pwd, + const std::vector& stun_hosts, + const std::vector& relay_hosts, + const std::string& relay, + const std::string& agent) + : HttpPortAllocatorSessionBase(allocator, content_name, component, + ice_ufrag, ice_pwd, stun_hosts, + relay_hosts, relay, agent) { +} + +HttpPortAllocatorSession::~HttpPortAllocatorSession() { + for (std::list::iterator it = requests_.begin(); + it != requests_.end(); ++it) { + (*it)->Destroy(true); + } +} + +void HttpPortAllocatorSession::SendSessionRequest(const std::string& host, + int port) { + // Initiate an HTTP request to create a session through the chosen host. + rtc::AsyncHttpRequest* request = + new rtc::AsyncHttpRequest(user_agent()); + request->SignalWorkDone.connect(this, + &HttpPortAllocatorSession::OnRequestDone); + + request->set_secure(port == rtc::HTTP_SECURE_PORT); + request->set_proxy(allocator()->proxy()); + request->response().document.reset(new rtc::MemoryStream); + request->request().verb = rtc::HV_GET; + request->request().path = GetSessionRequestUrl(); + request->request().addHeader("X-Talk-Google-Relay-Auth", relay_token(), true); + request->request().addHeader("X-Stream-Type", "video_rtp", true); + request->set_host(host); + request->set_port(port); + request->Start(); + request->Release(); + + requests_.push_back(request); +} + +void HttpPortAllocatorSession::OnRequestDone(rtc::SignalThread* data) { + rtc::AsyncHttpRequest* request = + static_cast(data); + + // Remove the request from the list of active requests. + std::list::iterator it = + std::find(requests_.begin(), requests_.end(), request); + if (it != requests_.end()) { + requests_.erase(it); + } + + if (request->response().scode != 200) { + LOG(LS_WARNING) << "HTTPPortAllocator: request " + << " received error " << request->response().scode; + TryCreateRelaySession(); + return; + } + LOG(LS_INFO) << "HTTPPortAllocator: request succeeded"; + + rtc::MemoryStream* stream = + static_cast(request->response().document.get()); + stream->Rewind(); + size_t length; + stream->GetSize(&length); + std::string resp = std::string(stream->GetBuffer(), length); + ReceiveSessionResponse(resp); +} + +} // namespace cricket diff --git a/webrtc/p2p/client/httpportallocator.h b/webrtc/p2p/client/httpportallocator.h new file mode 100644 index 000000000..e2fa74354 --- /dev/null +++ b/webrtc/p2p/client/httpportallocator.h @@ -0,0 +1,173 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_CLIENT_HTTPPORTALLOCATOR_H_ +#define WEBRTC_P2P_CLIENT_HTTPPORTALLOCATOR_H_ + +#include +#include +#include + +#include "webrtc/p2p/client/basicportallocator.h" + +class HttpPortAllocatorTest_TestSessionRequestUrl_Test; + +namespace rtc { +class AsyncHttpRequest; +class SignalThread; +} + +namespace cricket { + +class HttpPortAllocatorBase : public BasicPortAllocator { + public: + // The number of HTTP requests we should attempt before giving up. + static const int kNumRetries; + + // Records the URL that we will GET in order to create a session. + static const char kCreateSessionURL[]; + + HttpPortAllocatorBase(rtc::NetworkManager* network_manager, + const std::string& user_agent); + HttpPortAllocatorBase(rtc::NetworkManager* network_manager, + rtc::PacketSocketFactory* socket_factory, + const std::string& user_agent); + virtual ~HttpPortAllocatorBase(); + + // CreateSession is defined in BasicPortAllocator but is + // redefined here as pure virtual. + virtual PortAllocatorSession* CreateSessionInternal( + const std::string& content_name, + int component, + const std::string& ice_ufrag, + const std::string& ice_pwd) = 0; + + void SetStunHosts(const std::vector& hosts) { + if (!hosts.empty()) { + stun_hosts_ = hosts; + } + } + void SetRelayHosts(const std::vector& hosts) { + if (!hosts.empty()) { + relay_hosts_ = hosts; + } + } + void SetRelayToken(const std::string& relay) { relay_token_ = relay; } + + const std::vector& stun_hosts() const { + return stun_hosts_; + } + + const std::vector& relay_hosts() const { + return relay_hosts_; + } + + const std::string& relay_token() const { + return relay_token_; + } + + const std::string& user_agent() const { + return agent_; + } + + private: + std::vector stun_hosts_; + std::vector relay_hosts_; + std::string relay_token_; + std::string agent_; +}; + +class RequestData; + +class HttpPortAllocatorSessionBase : public BasicPortAllocatorSession { + public: + HttpPortAllocatorSessionBase( + HttpPortAllocatorBase* allocator, + const std::string& content_name, + int component, + const std::string& ice_ufrag, + const std::string& ice_pwd, + const std::vector& stun_hosts, + const std::vector& relay_hosts, + const std::string& relay, + const std::string& agent); + virtual ~HttpPortAllocatorSessionBase(); + + const std::string& relay_token() const { + return relay_token_; + } + + const std::string& user_agent() const { + return agent_; + } + + virtual void SendSessionRequest(const std::string& host, int port) = 0; + virtual void ReceiveSessionResponse(const std::string& response); + + // Made public for testing. Should be protected. + std::string GetSessionRequestUrl(); + + protected: + virtual void GetPortConfigurations(); + void TryCreateRelaySession(); + virtual HttpPortAllocatorBase* allocator() { + return static_cast( + BasicPortAllocatorSession::allocator()); + } + + private: + std::vector relay_hosts_; + std::vector stun_hosts_; + std::string relay_token_; + std::string agent_; + int attempts_; +}; + +class HttpPortAllocator : public HttpPortAllocatorBase { + public: + HttpPortAllocator(rtc::NetworkManager* network_manager, + const std::string& user_agent); + HttpPortAllocator(rtc::NetworkManager* network_manager, + rtc::PacketSocketFactory* socket_factory, + const std::string& user_agent); + virtual ~HttpPortAllocator(); + virtual PortAllocatorSession* CreateSessionInternal( + const std::string& content_name, + int component, + const std::string& ice_ufrag, const std::string& ice_pwd); +}; + +class HttpPortAllocatorSession : public HttpPortAllocatorSessionBase { + public: + HttpPortAllocatorSession( + HttpPortAllocator* allocator, + const std::string& content_name, + int component, + const std::string& ice_ufrag, + const std::string& ice_pwd, + const std::vector& stun_hosts, + const std::vector& relay_hosts, + const std::string& relay, + const std::string& agent); + virtual ~HttpPortAllocatorSession(); + + virtual void SendSessionRequest(const std::string& host, int port); + + protected: + // Protected for diagnostics. + virtual void OnRequestDone(rtc::SignalThread* request); + + private: + std::list requests_; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_CLIENT_HTTPPORTALLOCATOR_H_ diff --git a/webrtc/p2p/client/portallocator_unittest.cc b/webrtc/p2p/client/portallocator_unittest.cc new file mode 100644 index 000000000..5dcc8f988 --- /dev/null +++ b/webrtc/p2p/client/portallocator_unittest.cc @@ -0,0 +1,1066 @@ +/* + * Copyright 2009 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/constants.h" +#include "webrtc/p2p/base/p2ptransportchannel.h" +#include "webrtc/p2p/base/portallocatorsessionproxy.h" +#include "webrtc/p2p/base/testrelayserver.h" +#include "webrtc/p2p/base/teststunserver.h" +#include "webrtc/p2p/base/testturnserver.h" +#include "webrtc/p2p/client/basicportallocator.h" +#include "webrtc/p2p/client/httpportallocator.h" +#include "webrtc/base/fakenetwork.h" +#include "webrtc/base/firewallsocketserver.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/natserver.h" +#include "webrtc/base/natsocketfactory.h" +#include "webrtc/base/network.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/ssladapter.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/virtualsocketserver.h" + +using cricket::ServerAddresses; +using rtc::SocketAddress; +using rtc::Thread; + +static const SocketAddress kClientAddr("11.11.11.11", 0); +static const SocketAddress kPrivateAddr("192.168.1.11", 0); +static const SocketAddress kClientIPv6Addr( + "2401:fa00:4:1000:be30:5bff:fee5:c3", 0); +static const SocketAddress kClientAddr2("22.22.22.22", 0); +static const SocketAddress kNatAddr("77.77.77.77", rtc::NAT_SERVER_PORT); +static const SocketAddress kRemoteClientAddr("22.22.22.22", 0); +static const SocketAddress kStunAddr("99.99.99.1", cricket::STUN_SERVER_PORT); +static const SocketAddress kRelayUdpIntAddr("99.99.99.2", 5000); +static const SocketAddress kRelayUdpExtAddr("99.99.99.3", 5001); +static const SocketAddress kRelayTcpIntAddr("99.99.99.2", 5002); +static const SocketAddress kRelayTcpExtAddr("99.99.99.3", 5003); +static const SocketAddress kRelaySslTcpIntAddr("99.99.99.2", 5004); +static const SocketAddress kRelaySslTcpExtAddr("99.99.99.3", 5005); +static const SocketAddress kTurnUdpIntAddr("99.99.99.4", 3478); +static const SocketAddress kTurnTcpIntAddr("99.99.99.5", 3478); +static const SocketAddress kTurnUdpExtAddr("99.99.99.6", 0); + +// Minimum and maximum port for port range tests. +static const int kMinPort = 10000; +static const int kMaxPort = 10099; + +// Based on ICE_UFRAG_LENGTH +static const char kIceUfrag0[] = "TESTICEUFRAG0000"; +// Based on ICE_PWD_LENGTH +static const char kIcePwd0[] = "TESTICEPWD00000000000000"; + +static const char kContentName[] = "test content"; + +static const int kDefaultAllocationTimeout = 1000; +static const char kTurnUsername[] = "test"; +static const char kTurnPassword[] = "test"; + +namespace cricket { + +// Helper for dumping candidates +std::ostream& operator<<(std::ostream& os, const cricket::Candidate& c) { + os << c.ToString(); + return os; +} + +} // namespace cricket + +class PortAllocatorTest : public testing::Test, public sigslot::has_slots<> { + public: + PortAllocatorTest() + : pss_(new rtc::PhysicalSocketServer), + vss_(new rtc::VirtualSocketServer(pss_.get())), + fss_(new rtc::FirewallSocketServer(vss_.get())), + ss_scope_(fss_.get()), + nat_factory_(vss_.get(), kNatAddr), + nat_socket_factory_(&nat_factory_), + stun_server_(cricket::TestStunServer::Create(Thread::Current(), + kStunAddr)), + relay_server_(Thread::Current(), kRelayUdpIntAddr, kRelayUdpExtAddr, + kRelayTcpIntAddr, kRelayTcpExtAddr, + kRelaySslTcpIntAddr, kRelaySslTcpExtAddr), + turn_server_(Thread::Current(), kTurnUdpIntAddr, kTurnUdpExtAddr), + candidate_allocation_done_(false) { + cricket::ServerAddresses stun_servers; + stun_servers.insert(kStunAddr); + // Passing the addresses of GTURN servers will enable GTURN in + // Basicportallocator. + allocator_.reset(new cricket::BasicPortAllocator( + &network_manager_, + stun_servers, + kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr)); + allocator_->set_step_delay(cricket::kMinimumStepDelay); + } + + void AddInterface(const SocketAddress& addr) { + network_manager_.AddInterface(addr); + } + bool SetPortRange(int min_port, int max_port) { + return allocator_->SetPortRange(min_port, max_port); + } + void ResetWithNatServer(const rtc::SocketAddress& stun_server) { + nat_server_.reset(new rtc::NATServer( + rtc::NAT_OPEN_CONE, vss_.get(), kNatAddr, vss_.get(), kNatAddr)); + + ServerAddresses stun_servers; + stun_servers.insert(stun_server); + allocator_.reset(new cricket::BasicPortAllocator( + &network_manager_, &nat_socket_factory_, stun_servers)); + allocator().set_step_delay(cricket::kMinimumStepDelay); + } + + // Create a BasicPortAllocator without GTURN and add the TURN servers. + void ResetWithTurnServers(const rtc::SocketAddress& udp_turn, + const rtc::SocketAddress& tcp_turn) { + allocator_.reset(new cricket::BasicPortAllocator(&network_manager_)); + allocator().set_step_delay(cricket::kMinimumStepDelay); + AddTurnServers(udp_turn, tcp_turn); + } + + void AddTurnServers(const rtc::SocketAddress& udp_turn, + const rtc::SocketAddress& tcp_turn) { + cricket::RelayServerConfig relay_server(cricket::RELAY_TURN); + cricket::RelayCredentials credentials(kTurnUsername, kTurnPassword); + relay_server.credentials = credentials; + + if (!udp_turn.IsNil()) { + relay_server.ports.push_back(cricket::ProtocolAddress( + kTurnUdpIntAddr, cricket::PROTO_UDP, false)); + } + if (!tcp_turn.IsNil()) { + relay_server.ports.push_back(cricket::ProtocolAddress( + kTurnTcpIntAddr, cricket::PROTO_TCP, false)); + } + allocator_->AddRelay(relay_server); + } + + bool CreateSession(int component) { + session_.reset(CreateSession("session", component)); + if (!session_) + return false; + return true; + } + + bool CreateSession(int component, const std::string& content_name) { + session_.reset(CreateSession("session", content_name, component)); + if (!session_) + return false; + return true; + } + + cricket::PortAllocatorSession* CreateSession( + const std::string& sid, int component) { + return CreateSession(sid, kContentName, component); + } + + cricket::PortAllocatorSession* CreateSession( + const std::string& sid, const std::string& content_name, int component) { + return CreateSession(sid, content_name, component, kIceUfrag0, kIcePwd0); + } + + cricket::PortAllocatorSession* CreateSession( + const std::string& sid, const std::string& content_name, int component, + const std::string& ice_ufrag, const std::string& ice_pwd) { + cricket::PortAllocatorSession* session = + allocator_->CreateSession( + sid, content_name, component, ice_ufrag, ice_pwd); + session->SignalPortReady.connect(this, + &PortAllocatorTest::OnPortReady); + session->SignalCandidatesReady.connect(this, + &PortAllocatorTest::OnCandidatesReady); + session->SignalCandidatesAllocationDone.connect(this, + &PortAllocatorTest::OnCandidatesAllocationDone); + return session; + } + + static bool CheckCandidate(const cricket::Candidate& c, + int component, const std::string& type, + const std::string& proto, + const SocketAddress& addr) { + return (c.component() == component && c.type() == type && + c.protocol() == proto && c.address().ipaddr() == addr.ipaddr() && + ((addr.port() == 0 && (c.address().port() != 0)) || + (c.address().port() == addr.port()))); + } + static bool CheckPort(const rtc::SocketAddress& addr, + int min_port, int max_port) { + return (addr.port() >= min_port && addr.port() <= max_port); + } + + void OnCandidatesAllocationDone(cricket::PortAllocatorSession* session) { + // We should only get this callback once, except in the mux test where + // we have multiple port allocation sessions. + if (session == session_.get()) { + ASSERT_FALSE(candidate_allocation_done_); + candidate_allocation_done_ = true; + } + } + + // Check if all ports allocated have send-buffer size |expected|. If + // |expected| == -1, check if GetOptions returns SOCKET_ERROR. + void CheckSendBufferSizesOfAllPorts(int expected) { + std::vector::iterator it; + for (it = ports_.begin(); it < ports_.end(); ++it) { + int send_buffer_size; + if (expected == -1) { + EXPECT_EQ(SOCKET_ERROR, + (*it)->GetOption(rtc::Socket::OPT_SNDBUF, + &send_buffer_size)); + } else { + EXPECT_EQ(0, (*it)->GetOption(rtc::Socket::OPT_SNDBUF, + &send_buffer_size)); + ASSERT_EQ(expected, send_buffer_size); + } + } + } + + protected: + cricket::BasicPortAllocator& allocator() { + return *allocator_; + } + + void OnPortReady(cricket::PortAllocatorSession* ses, + cricket::PortInterface* port) { + LOG(LS_INFO) << "OnPortReady: " << port->ToString(); + ports_.push_back(port); + } + void OnCandidatesReady(cricket::PortAllocatorSession* ses, + const std::vector& candidates) { + for (size_t i = 0; i < candidates.size(); ++i) { + LOG(LS_INFO) << "OnCandidatesReady: " << candidates[i].ToString(); + candidates_.push_back(candidates[i]); + } + } + + bool HasRelayAddress(const cricket::ProtocolAddress& proto_addr) { + for (size_t i = 0; i < allocator_->relays().size(); ++i) { + cricket::RelayServerConfig server_config = allocator_->relays()[i]; + cricket::PortList::const_iterator relay_port; + for (relay_port = server_config.ports.begin(); + relay_port != server_config.ports.end(); ++relay_port) { + if (proto_addr.address == relay_port->address && + proto_addr.proto == relay_port->proto) + return true; + } + } + return false; + } + + rtc::scoped_ptr pss_; + rtc::scoped_ptr vss_; + rtc::scoped_ptr fss_; + rtc::SocketServerScope ss_scope_; + rtc::scoped_ptr nat_server_; + rtc::NATSocketFactory nat_factory_; + rtc::BasicPacketSocketFactory nat_socket_factory_; + rtc::scoped_ptr stun_server_; + cricket::TestRelayServer relay_server_; + cricket::TestTurnServer turn_server_; + rtc::FakeNetworkManager network_manager_; + rtc::scoped_ptr allocator_; + rtc::scoped_ptr session_; + std::vector ports_; + std::vector candidates_; + bool candidate_allocation_done_; +}; + +// Tests that we can init the port allocator and create a session. +TEST_F(PortAllocatorTest, TestBasic) { + EXPECT_EQ(&network_manager_, allocator().network_manager()); + EXPECT_EQ(kStunAddr, *allocator().stun_servers().begin()); + ASSERT_EQ(1u, allocator().relays().size()); + EXPECT_EQ(cricket::RELAY_GTURN, allocator().relays()[0].type); + // Empty relay credentials are used for GTURN. + EXPECT_TRUE(allocator().relays()[0].credentials.username.empty()); + EXPECT_TRUE(allocator().relays()[0].credentials.password.empty()); + EXPECT_TRUE(HasRelayAddress(cricket::ProtocolAddress( + kRelayUdpIntAddr, cricket::PROTO_UDP))); + EXPECT_TRUE(HasRelayAddress(cricket::ProtocolAddress( + kRelayTcpIntAddr, cricket::PROTO_TCP))); + EXPECT_TRUE(HasRelayAddress(cricket::ProtocolAddress( + kRelaySslTcpIntAddr, cricket::PROTO_SSLTCP))); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); +} + +// Tests that we allocator session not trying to allocate ports for every 250ms. +TEST_F(PortAllocatorTest, TestNoNetworkInterface) { + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + // Waiting for one second to make sure BasicPortAllocatorSession has not + // called OnAllocate multiple times. In old behavior it's called every 250ms. + // When there are no network interfaces, each execution of OnAllocate will + // result in SignalCandidatesAllocationDone signal. + rtc::Thread::Current()->ProcessMessages(1000); + EXPECT_TRUE(candidate_allocation_done_); + EXPECT_EQ(0U, candidates_.size()); +} + +// Tests that we can get all the desired addresses successfully. +TEST_F(PortAllocatorTest, TestGetAllPortsWithMinimumStepDelay) { + AddInterface(kClientAddr); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + ASSERT_EQ_WAIT(7U, candidates_.size(), kDefaultAllocationTimeout); + EXPECT_EQ(4U, ports_.size()); + EXPECT_PRED5(CheckCandidate, candidates_[0], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", kClientAddr); + EXPECT_PRED5(CheckCandidate, candidates_[1], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "stun", "udp", kClientAddr); + EXPECT_PRED5(CheckCandidate, candidates_[2], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", kRelayUdpIntAddr); + EXPECT_PRED5(CheckCandidate, candidates_[3], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", kRelayUdpExtAddr); + EXPECT_PRED5(CheckCandidate, candidates_[4], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "tcp", kRelayTcpIntAddr); + EXPECT_PRED5(CheckCandidate, candidates_[5], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "tcp", kClientAddr); + EXPECT_PRED5(CheckCandidate, candidates_[6], + cricket::ICE_CANDIDATE_COMPONENT_RTP, + "relay", "ssltcp", kRelaySslTcpIntAddr); + EXPECT_TRUE(candidate_allocation_done_); +} + +// Verify candidates with default step delay of 1sec. +TEST_F(PortAllocatorTest, TestGetAllPortsWithOneSecondStepDelay) { + AddInterface(kClientAddr); + allocator_->set_step_delay(cricket::kDefaultStepDelay); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + ASSERT_EQ_WAIT(2U, candidates_.size(), 1000); + EXPECT_EQ(2U, ports_.size()); + ASSERT_EQ_WAIT(4U, candidates_.size(), 2000); + EXPECT_EQ(3U, ports_.size()); + EXPECT_PRED5(CheckCandidate, candidates_[2], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", kRelayUdpIntAddr); + EXPECT_PRED5(CheckCandidate, candidates_[3], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", kRelayUdpExtAddr); + ASSERT_EQ_WAIT(6U, candidates_.size(), 1500); + EXPECT_PRED5(CheckCandidate, candidates_[4], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "tcp", kRelayTcpIntAddr); + EXPECT_PRED5(CheckCandidate, candidates_[5], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "tcp", kClientAddr); + EXPECT_EQ(4U, ports_.size()); + ASSERT_EQ_WAIT(7U, candidates_.size(), 2000); + EXPECT_PRED5(CheckCandidate, candidates_[6], + cricket::ICE_CANDIDATE_COMPONENT_RTP, + "relay", "ssltcp", kRelaySslTcpIntAddr); + EXPECT_EQ(4U, ports_.size()); + EXPECT_TRUE(candidate_allocation_done_); + // If we Stop gathering now, we shouldn't get a second "done" callback. + session_->StopGettingPorts(); +} + +TEST_F(PortAllocatorTest, TestSetupVideoRtpPortsWithNormalSendBuffers) { + AddInterface(kClientAddr); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP, + cricket::CN_VIDEO)); + session_->StartGettingPorts(); + ASSERT_EQ_WAIT(7U, candidates_.size(), kDefaultAllocationTimeout); + EXPECT_TRUE(candidate_allocation_done_); + // If we Stop gathering now, we shouldn't get a second "done" callback. + session_->StopGettingPorts(); + + // All ports should have unset send-buffer sizes. + CheckSendBufferSizesOfAllPorts(-1); +} + +// Tests that we can get callback after StopGetAllPorts. +TEST_F(PortAllocatorTest, TestStopGetAllPorts) { + AddInterface(kClientAddr); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + ASSERT_EQ_WAIT(2U, candidates_.size(), kDefaultAllocationTimeout); + EXPECT_EQ(2U, ports_.size()); + session_->StopGettingPorts(); + EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout); +} + +// Test that we restrict client ports appropriately when a port range is set. +// We check the candidates for udp/stun/tcp ports, and the from address +// for relay ports. +TEST_F(PortAllocatorTest, TestGetAllPortsPortRange) { + AddInterface(kClientAddr); + // Check that an invalid port range fails. + EXPECT_FALSE(SetPortRange(kMaxPort, kMinPort)); + // Check that a null port range succeeds. + EXPECT_TRUE(SetPortRange(0, 0)); + // Check that a valid port range succeeds. + EXPECT_TRUE(SetPortRange(kMinPort, kMaxPort)); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + ASSERT_EQ_WAIT(7U, candidates_.size(), kDefaultAllocationTimeout); + EXPECT_EQ(4U, ports_.size()); + // Check the port number for the UDP port object. + EXPECT_PRED3(CheckPort, candidates_[0].address(), kMinPort, kMaxPort); + // Check the port number for the STUN port object. + EXPECT_PRED3(CheckPort, candidates_[1].address(), kMinPort, kMaxPort); + // Check the port number used to connect to the relay server. + EXPECT_PRED3(CheckPort, relay_server_.GetConnection(0).source(), + kMinPort, kMaxPort); + // Check the port number for the TCP port object. + EXPECT_PRED3(CheckPort, candidates_[5].address(), kMinPort, kMaxPort); + EXPECT_TRUE(candidate_allocation_done_); +} + +// Test that we don't crash or malfunction if we have no network adapters. +TEST_F(PortAllocatorTest, TestGetAllPortsNoAdapters) { + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + rtc::Thread::Current()->ProcessMessages(100); + // Without network adapter, we should not get any candidate. + EXPECT_EQ(0U, candidates_.size()); + EXPECT_TRUE(candidate_allocation_done_); +} + +// Test that we can get OnCandidatesAllocationDone callback when all the ports +// are disabled. +TEST_F(PortAllocatorTest, TestDisableAllPorts) { + AddInterface(kClientAddr); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->set_flags(cricket::PORTALLOCATOR_DISABLE_UDP | + cricket::PORTALLOCATOR_DISABLE_STUN | + cricket::PORTALLOCATOR_DISABLE_RELAY | + cricket::PORTALLOCATOR_DISABLE_TCP); + session_->StartGettingPorts(); + rtc::Thread::Current()->ProcessMessages(100); + EXPECT_EQ(0U, candidates_.size()); + EXPECT_TRUE(candidate_allocation_done_); +} + +// Test that we don't crash or malfunction if we can't create UDP sockets. +TEST_F(PortAllocatorTest, TestGetAllPortsNoUdpSockets) { + AddInterface(kClientAddr); + fss_->set_udp_sockets_enabled(false); + EXPECT_TRUE(CreateSession(1)); + session_->StartGettingPorts(); + ASSERT_EQ_WAIT(5U, candidates_.size(), kDefaultAllocationTimeout); + EXPECT_EQ(2U, ports_.size()); + EXPECT_PRED5(CheckCandidate, candidates_[0], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", kRelayUdpIntAddr); + EXPECT_PRED5(CheckCandidate, candidates_[1], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", kRelayUdpExtAddr); + EXPECT_PRED5(CheckCandidate, candidates_[2], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "tcp", kRelayTcpIntAddr); + EXPECT_PRED5(CheckCandidate, candidates_[3], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "tcp", kClientAddr); + EXPECT_PRED5(CheckCandidate, candidates_[4], + cricket::ICE_CANDIDATE_COMPONENT_RTP, + "relay", "ssltcp", kRelaySslTcpIntAddr); + EXPECT_TRUE(candidate_allocation_done_); +} + +// Test that we don't crash or malfunction if we can't create UDP sockets or +// listen on TCP sockets. We still give out a local TCP address, since +// apparently this is needed for the remote side to accept our connection. +TEST_F(PortAllocatorTest, TestGetAllPortsNoUdpSocketsNoTcpListen) { + AddInterface(kClientAddr); + fss_->set_udp_sockets_enabled(false); + fss_->set_tcp_listen_enabled(false); + EXPECT_TRUE(CreateSession(1)); + session_->StartGettingPorts(); + ASSERT_EQ_WAIT(5U, candidates_.size(), kDefaultAllocationTimeout); + EXPECT_EQ(2U, ports_.size()); + EXPECT_PRED5(CheckCandidate, candidates_[0], + 1, "relay", "udp", kRelayUdpIntAddr); + EXPECT_PRED5(CheckCandidate, candidates_[1], + 1, "relay", "udp", kRelayUdpExtAddr); + EXPECT_PRED5(CheckCandidate, candidates_[2], + 1, "relay", "tcp", kRelayTcpIntAddr); + EXPECT_PRED5(CheckCandidate, candidates_[3], + 1, "local", "tcp", kClientAddr); + EXPECT_PRED5(CheckCandidate, candidates_[4], + 1, "relay", "ssltcp", kRelaySslTcpIntAddr); + EXPECT_TRUE(candidate_allocation_done_); +} + +// Test that we don't crash or malfunction if we can't create any sockets. +// TODO: Find a way to exit early here. +TEST_F(PortAllocatorTest, TestGetAllPortsNoSockets) { + AddInterface(kClientAddr); + fss_->set_tcp_sockets_enabled(false); + fss_->set_udp_sockets_enabled(false); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + WAIT(candidates_.size() > 0, 2000); + // TODO - Check candidate_allocation_done signal. + // In case of Relay, ports creation will succeed but sockets will fail. + // There is no error reporting from RelayEntry to handle this failure. +} + +// Testing STUN timeout. +TEST_F(PortAllocatorTest, TestGetAllPortsNoUdpAllowed) { + fss_->AddRule(false, rtc::FP_UDP, rtc::FD_ANY, kClientAddr); + AddInterface(kClientAddr); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + EXPECT_EQ_WAIT(2U, candidates_.size(), kDefaultAllocationTimeout); + EXPECT_EQ(2U, ports_.size()); + EXPECT_PRED5(CheckCandidate, candidates_[0], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", kClientAddr); + EXPECT_PRED5(CheckCandidate, candidates_[1], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "tcp", kClientAddr); + // RelayPort connection timeout is 3sec. TCP connection with RelayServer + // will be tried after 3 seconds. + EXPECT_EQ_WAIT(6U, candidates_.size(), 4000); + EXPECT_EQ(3U, ports_.size()); + EXPECT_PRED5(CheckCandidate, candidates_[2], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", kRelayUdpIntAddr); + EXPECT_PRED5(CheckCandidate, candidates_[3], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "tcp", kRelayTcpIntAddr); + EXPECT_PRED5(CheckCandidate, candidates_[4], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "ssltcp", + kRelaySslTcpIntAddr); + EXPECT_PRED5(CheckCandidate, candidates_[5], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", kRelayUdpExtAddr); + // Stun Timeout is 9sec. + EXPECT_TRUE_WAIT(candidate_allocation_done_, 9000); +} + +TEST_F(PortAllocatorTest, TestCandidatePriorityOfMultipleInterfaces) { + AddInterface(kClientAddr); + AddInterface(kClientAddr2); + // Allocating only host UDP ports. This is done purely for testing + // convenience. + allocator().set_flags(cricket::PORTALLOCATOR_DISABLE_TCP | + cricket::PORTALLOCATOR_DISABLE_STUN | + cricket::PORTALLOCATOR_DISABLE_RELAY); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout); + ASSERT_EQ(2U, candidates_.size()); + EXPECT_EQ(2U, ports_.size()); + // Candidates priorities should be different. + EXPECT_NE(candidates_[0].priority(), candidates_[1].priority()); +} + +// Test to verify ICE restart process. +TEST_F(PortAllocatorTest, TestGetAllPortsRestarts) { + AddInterface(kClientAddr); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + EXPECT_EQ_WAIT(7U, candidates_.size(), kDefaultAllocationTimeout); + EXPECT_EQ(4U, ports_.size()); + EXPECT_TRUE(candidate_allocation_done_); + // TODO - Extend this to verify ICE restart. +} + +// Test ICE candidate filter mechanism with options Relay/Host/Reflexive. +// This test also verifies that when the allocator is only allowed to use +// relay (i.e. IceTransportsType is relay), the raddr is an empty +// address with the correct family. This is to prevent any local +// reflective address leakage in the sdp line. +TEST_F(PortAllocatorTest, TestCandidateFilterWithRelayOnly) { + AddInterface(kClientAddr); + // GTURN is not configured here. + ResetWithTurnServers(kTurnUdpIntAddr, rtc::SocketAddress()); + allocator().set_candidate_filter(cricket::CF_RELAY); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout); + EXPECT_PRED5(CheckCandidate, + candidates_[0], + cricket::ICE_CANDIDATE_COMPONENT_RTP, + "relay", + "udp", + rtc::SocketAddress(kTurnUdpExtAddr.ipaddr(), 0)); + + EXPECT_EQ(1U, candidates_.size()); + EXPECT_EQ(1U, ports_.size()); // Only Relay port will be in ready state. + for (size_t i = 0; i < candidates_.size(); ++i) { + EXPECT_EQ(std::string(cricket::RELAY_PORT_TYPE), candidates_[i].type()); + EXPECT_EQ( + candidates_[0].related_address(), + rtc::EmptySocketAddressWithFamily(candidates_[0].address().family())); + } +} + +TEST_F(PortAllocatorTest, TestCandidateFilterWithHostOnly) { + AddInterface(kClientAddr); + allocator().set_flags(cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET); + allocator().set_candidate_filter(cricket::CF_HOST); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout); + EXPECT_EQ(2U, candidates_.size()); // Host UDP/TCP candidates only. + EXPECT_EQ(2U, ports_.size()); // UDP/TCP ports only. + for (size_t i = 0; i < candidates_.size(); ++i) { + EXPECT_EQ(std::string(cricket::LOCAL_PORT_TYPE), candidates_[i].type()); + } +} + +// Host is behind the NAT. +TEST_F(PortAllocatorTest, TestCandidateFilterWithReflexiveOnly) { + AddInterface(kPrivateAddr); + ResetWithNatServer(kStunAddr); + + allocator().set_flags(cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET); + allocator().set_candidate_filter(cricket::CF_REFLEXIVE); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout); + // Host is behind NAT, no private address will be exposed. Hence only UDP + // port with STUN candidate will be sent outside. + EXPECT_EQ(1U, candidates_.size()); // Only STUN candidate. + EXPECT_EQ(1U, ports_.size()); // Only UDP port will be in ready state. + for (size_t i = 0; i < candidates_.size(); ++i) { + EXPECT_EQ(std::string(cricket::STUN_PORT_TYPE), candidates_[i].type()); + EXPECT_EQ( + candidates_[0].related_address(), + rtc::EmptySocketAddressWithFamily(candidates_[0].address().family())); + } +} + +// Host is not behind the NAT. +TEST_F(PortAllocatorTest, TestCandidateFilterWithReflexiveOnlyAndNoNAT) { + AddInterface(kClientAddr); + allocator().set_flags(cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET); + allocator().set_candidate_filter(cricket::CF_REFLEXIVE); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout); + // Host has a public address, both UDP and TCP candidates will be exposed. + EXPECT_EQ(2U, candidates_.size()); // Local UDP + TCP candidate. + EXPECT_EQ(2U, ports_.size()); // UDP and TCP ports will be in ready state. + for (size_t i = 0; i < candidates_.size(); ++i) { + EXPECT_EQ(std::string(cricket::LOCAL_PORT_TYPE), candidates_[i].type()); + } +} + +TEST_F(PortAllocatorTest, TestBasicMuxFeatures) { + AddInterface(kClientAddr); + allocator().set_flags(cricket::PORTALLOCATOR_ENABLE_BUNDLE); + // Session ID - session1. + rtc::scoped_ptr session1( + CreateSession("session1", cricket::ICE_CANDIDATE_COMPONENT_RTP)); + rtc::scoped_ptr session2( + CreateSession("session1", cricket::ICE_CANDIDATE_COMPONENT_RTCP)); + session1->StartGettingPorts(); + session2->StartGettingPorts(); + // Each session should receive two proxy ports of local and stun. + ASSERT_EQ_WAIT(14U, candidates_.size(), kDefaultAllocationTimeout); + EXPECT_EQ(8U, ports_.size()); + + rtc::scoped_ptr session3( + CreateSession("session1", cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session3->StartGettingPorts(); + // Already allocated candidates and ports will be sent to the newly + // allocated proxy session. + ASSERT_EQ_WAIT(21U, candidates_.size(), kDefaultAllocationTimeout); + EXPECT_EQ(12U, ports_.size()); +} + +// This test verifies by changing ice_ufrag and/or ice_pwd +// will result in different set of candidates when BUNDLE is enabled. +// If BUNDLE is disabled, CreateSession will always allocate new +// set of candidates. +TEST_F(PortAllocatorTest, TestBundleIceRestart) { + AddInterface(kClientAddr); + allocator().set_flags(cricket::PORTALLOCATOR_ENABLE_BUNDLE); + // Session ID - session1. + rtc::scoped_ptr session1( + CreateSession("session1", kContentName, + cricket::ICE_CANDIDATE_COMPONENT_RTP, + kIceUfrag0, kIcePwd0)); + session1->StartGettingPorts(); + ASSERT_EQ_WAIT(7U, candidates_.size(), kDefaultAllocationTimeout); + EXPECT_EQ(4U, ports_.size()); + + // Allocate a different session with sid |session1| and different ice_ufrag. + rtc::scoped_ptr session2( + CreateSession("session1", kContentName, + cricket::ICE_CANDIDATE_COMPONENT_RTP, + "TestIceUfrag", kIcePwd0)); + session2->StartGettingPorts(); + ASSERT_EQ_WAIT(14U, candidates_.size(), kDefaultAllocationTimeout); + EXPECT_EQ(8U, ports_.size()); + // Verifying the candidate address different from previously allocated + // address. + // Skipping verification of component id and candidate type. + EXPECT_NE(candidates_[0].address(), candidates_[7].address()); + EXPECT_NE(candidates_[1].address(), candidates_[8].address()); + + // Allocating a different session with sid |session1| and + // different ice_pwd. + rtc::scoped_ptr session3( + CreateSession("session1", kContentName, + cricket::ICE_CANDIDATE_COMPONENT_RTP, + kIceUfrag0, "TestIcePwd")); + session3->StartGettingPorts(); + ASSERT_EQ_WAIT(21U, candidates_.size(), kDefaultAllocationTimeout); + EXPECT_EQ(12U, ports_.size()); + // Verifying the candidate address different from previously + // allocated address. + EXPECT_NE(candidates_[7].address(), candidates_[14].address()); + EXPECT_NE(candidates_[8].address(), candidates_[15].address()); + + // Allocating a session with by changing both ice_ufrag and ice_pwd. + rtc::scoped_ptr session4( + CreateSession("session1", kContentName, + cricket::ICE_CANDIDATE_COMPONENT_RTP, + "TestIceUfrag", "TestIcePwd")); + session4->StartGettingPorts(); + ASSERT_EQ_WAIT(28U, candidates_.size(), kDefaultAllocationTimeout); + EXPECT_EQ(16U, ports_.size()); + // Verifying the candidate address different from previously + // allocated address. + EXPECT_NE(candidates_[14].address(), candidates_[21].address()); + EXPECT_NE(candidates_[15].address(), candidates_[22].address()); +} + +// Test that when the PORTALLOCATOR_ENABLE_SHARED_UFRAG is enabled we got same +// ufrag and pwd for the collected candidates. +TEST_F(PortAllocatorTest, TestEnableSharedUfrag) { + allocator().set_flags(allocator().flags() | + cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG); + AddInterface(kClientAddr); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + ASSERT_EQ_WAIT(7U, candidates_.size(), kDefaultAllocationTimeout); + EXPECT_PRED5(CheckCandidate, candidates_[0], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", kClientAddr); + EXPECT_PRED5(CheckCandidate, candidates_[1], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "stun", "udp", kClientAddr); + EXPECT_PRED5(CheckCandidate, candidates_[5], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "tcp", kClientAddr); + EXPECT_EQ(4U, ports_.size()); + EXPECT_EQ(kIceUfrag0, candidates_[0].username()); + EXPECT_EQ(kIceUfrag0, candidates_[1].username()); + EXPECT_EQ(kIceUfrag0, candidates_[2].username()); + EXPECT_EQ(kIcePwd0, candidates_[0].password()); + EXPECT_EQ(kIcePwd0, candidates_[1].password()); + EXPECT_TRUE(candidate_allocation_done_); +} + +// Test that when the PORTALLOCATOR_ENABLE_SHARED_UFRAG isn't enabled we got +// different ufrag and pwd for the collected candidates. +TEST_F(PortAllocatorTest, TestDisableSharedUfrag) { + allocator().set_flags(allocator().flags() & + ~cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG); + AddInterface(kClientAddr); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + ASSERT_EQ_WAIT(7U, candidates_.size(), kDefaultAllocationTimeout); + EXPECT_PRED5(CheckCandidate, candidates_[0], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", kClientAddr); + EXPECT_PRED5(CheckCandidate, candidates_[1], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "stun", "udp", kClientAddr); + EXPECT_EQ(4U, ports_.size()); + // Port should generate random ufrag and pwd. + EXPECT_NE(kIceUfrag0, candidates_[0].username()); + EXPECT_NE(kIceUfrag0, candidates_[1].username()); + EXPECT_NE(candidates_[0].username(), candidates_[1].username()); + EXPECT_NE(kIcePwd0, candidates_[0].password()); + EXPECT_NE(kIcePwd0, candidates_[1].password()); + EXPECT_NE(candidates_[0].password(), candidates_[1].password()); + EXPECT_TRUE(candidate_allocation_done_); +} + +// Test that when PORTALLOCATOR_ENABLE_SHARED_SOCKET is enabled only one port +// is allocated for udp and stun. Also verify there is only one candidate +// (local) if stun candidate is same as local candidate, which will be the case +// in a public network like the below test. +TEST_F(PortAllocatorTest, TestSharedSocketWithoutNat) { + AddInterface(kClientAddr); + allocator_->set_flags(allocator().flags() | + cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + ASSERT_EQ_WAIT(6U, candidates_.size(), kDefaultAllocationTimeout); + EXPECT_EQ(3U, ports_.size()); + EXPECT_PRED5(CheckCandidate, candidates_[0], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", kClientAddr); + EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout); +} + +// Test that when PORTALLOCATOR_ENABLE_SHARED_SOCKET is enabled only one port +// is allocated for udp and stun. In this test we should expect both stun and +// local candidates as client behind a nat. +TEST_F(PortAllocatorTest, TestSharedSocketWithNat) { + AddInterface(kClientAddr); + ResetWithNatServer(kStunAddr); + + allocator_->set_flags(allocator().flags() | + cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + ASSERT_EQ_WAIT(3U, candidates_.size(), kDefaultAllocationTimeout); + ASSERT_EQ(2U, ports_.size()); + EXPECT_PRED5(CheckCandidate, candidates_[0], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", kClientAddr); + EXPECT_PRED5(CheckCandidate, candidates_[1], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "stun", "udp", + rtc::SocketAddress(kNatAddr.ipaddr(), 0)); + EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout); + EXPECT_EQ(3U, candidates_.size()); +} + +// Test TURN port in shared socket mode with UDP and TCP TURN server adderesses. +TEST_F(PortAllocatorTest, TestSharedSocketWithoutNatUsingTurn) { + turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP); + AddInterface(kClientAddr); + allocator_.reset(new cricket::BasicPortAllocator(&network_manager_)); + + AddTurnServers(kTurnUdpIntAddr, kTurnTcpIntAddr); + + allocator_->set_step_delay(cricket::kMinimumStepDelay); + allocator_->set_flags(allocator().flags() | + cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET | + cricket::PORTALLOCATOR_DISABLE_TCP); + + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + + ASSERT_EQ_WAIT(3U, candidates_.size(), kDefaultAllocationTimeout); + ASSERT_EQ(3U, ports_.size()); + EXPECT_PRED5(CheckCandidate, candidates_[0], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", kClientAddr); + EXPECT_PRED5(CheckCandidate, candidates_[1], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", + rtc::SocketAddress(kTurnUdpExtAddr.ipaddr(), 0)); + EXPECT_PRED5(CheckCandidate, candidates_[2], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", + rtc::SocketAddress(kTurnUdpExtAddr.ipaddr(), 0)); + EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout); + EXPECT_EQ(3U, candidates_.size()); +} + +// Testing DNS resolve for the TURN server, this will test AllocationSequence +// handling the unresolved address signal from TurnPort. +TEST_F(PortAllocatorTest, TestSharedSocketWithServerAddressResolve) { + turn_server_.AddInternalSocket(rtc::SocketAddress("127.0.0.1", 3478), + cricket::PROTO_UDP); + AddInterface(kClientAddr); + allocator_.reset(new cricket::BasicPortAllocator(&network_manager_)); + cricket::RelayServerConfig relay_server(cricket::RELAY_TURN); + cricket::RelayCredentials credentials(kTurnUsername, kTurnPassword); + relay_server.credentials = credentials; + relay_server.ports.push_back(cricket::ProtocolAddress( + rtc::SocketAddress("localhost", 3478), + cricket::PROTO_UDP, false)); + allocator_->AddRelay(relay_server); + + allocator_->set_step_delay(cricket::kMinimumStepDelay); + allocator_->set_flags(allocator().flags() | + cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET | + cricket::PORTALLOCATOR_DISABLE_TCP); + + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + + EXPECT_EQ_WAIT(2U, ports_.size(), kDefaultAllocationTimeout); +} + +// Test that when PORTALLOCATOR_ENABLE_SHARED_SOCKET is enabled only one port +// is allocated for udp/stun/turn. In this test we should expect all local, +// stun and turn candidates. +TEST_F(PortAllocatorTest, TestSharedSocketWithNatUsingTurn) { + AddInterface(kClientAddr); + ResetWithNatServer(kStunAddr); + + AddTurnServers(kTurnUdpIntAddr, rtc::SocketAddress()); + + allocator_->set_flags(allocator().flags() | + cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET | + cricket::PORTALLOCATOR_DISABLE_TCP); + + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + + ASSERT_EQ_WAIT(3U, candidates_.size(), kDefaultAllocationTimeout); + ASSERT_EQ(2U, ports_.size()); + EXPECT_PRED5(CheckCandidate, candidates_[0], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", kClientAddr); + EXPECT_PRED5(CheckCandidate, candidates_[1], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "stun", "udp", + rtc::SocketAddress(kNatAddr.ipaddr(), 0)); + EXPECT_PRED5(CheckCandidate, candidates_[2], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", + rtc::SocketAddress(kTurnUdpExtAddr.ipaddr(), 0)); + EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout); + EXPECT_EQ(3U, candidates_.size()); + // Local port will be created first and then TURN port. + EXPECT_EQ(2U, ports_[0]->Candidates().size()); + EXPECT_EQ(1U, ports_[1]->Candidates().size()); +} + +// Test that when PORTALLOCATOR_ENABLE_SHARED_SOCKET is enabled and the TURN +// server is also used as the STUN server, we should get 'local', 'stun', and +// 'relay' candidates. +TEST_F(PortAllocatorTest, TestSharedSocketWithNatUsingTurnAsStun) { + AddInterface(kClientAddr); + ResetWithNatServer(kTurnUdpIntAddr); + AddTurnServers(kTurnUdpIntAddr, rtc::SocketAddress()); + + // Must set the step delay to 0 to make sure the relay allocation phase is + // started before the STUN candidates are obtained, so that the STUN binding + // response is processed when both StunPort and TurnPort exist to reproduce + // webrtc issue 3537. + allocator_->set_step_delay(0); + allocator_->set_flags(allocator().flags() | + cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET | + cricket::PORTALLOCATOR_DISABLE_TCP); + + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + + ASSERT_EQ_WAIT(3U, candidates_.size(), kDefaultAllocationTimeout); + EXPECT_PRED5(CheckCandidate, candidates_[0], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", kClientAddr); + EXPECT_PRED5(CheckCandidate, candidates_[1], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "stun", "udp", + rtc::SocketAddress(kNatAddr.ipaddr(), 0)); + EXPECT_PRED5(CheckCandidate, candidates_[2], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", + rtc::SocketAddress(kTurnUdpExtAddr.ipaddr(), 0)); + EXPECT_EQ(candidates_[2].related_address(), candidates_[1].address()); + + EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout); + EXPECT_EQ(3U, candidates_.size()); + // Local port will be created first and then TURN port. + EXPECT_EQ(2U, ports_[0]->Candidates().size()); + EXPECT_EQ(1U, ports_[1]->Candidates().size()); +} + +// This test verifies when PORTALLOCATOR_ENABLE_SHARED_SOCKET flag is enabled +// and fail to generate STUN candidate, local UDP candidate is generated +// properly. +TEST_F(PortAllocatorTest, TestSharedSocketNoUdpAllowed) { + allocator().set_flags(allocator().flags() | + cricket::PORTALLOCATOR_DISABLE_RELAY | + cricket::PORTALLOCATOR_DISABLE_TCP | + cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET); + fss_->AddRule(false, rtc::FP_UDP, rtc::FD_ANY, kClientAddr); + AddInterface(kClientAddr); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + ASSERT_EQ_WAIT(1U, ports_.size(), kDefaultAllocationTimeout); + EXPECT_EQ(1U, candidates_.size()); + EXPECT_PRED5(CheckCandidate, candidates_[0], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", kClientAddr); + // STUN timeout is 9sec. We need to wait to get candidate done signal. + EXPECT_TRUE_WAIT(candidate_allocation_done_, 10000); + EXPECT_EQ(1U, candidates_.size()); +} + +// This test verifies allocator can use IPv6 addresses along with IPv4. +TEST_F(PortAllocatorTest, TestEnableIPv6Addresses) { + allocator().set_flags(allocator().flags() | + cricket::PORTALLOCATOR_DISABLE_RELAY | + cricket::PORTALLOCATOR_ENABLE_IPV6 | + cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET); + AddInterface(kClientIPv6Addr); + AddInterface(kClientAddr); + allocator_->set_step_delay(cricket::kMinimumStepDelay); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + ASSERT_EQ_WAIT(4U, ports_.size(), kDefaultAllocationTimeout); + EXPECT_EQ(4U, candidates_.size()); + EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout); + EXPECT_PRED5(CheckCandidate, candidates_[0], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", + kClientIPv6Addr); + EXPECT_PRED5(CheckCandidate, candidates_[1], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", + kClientAddr); + EXPECT_PRED5(CheckCandidate, candidates_[2], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "tcp", + kClientIPv6Addr); + EXPECT_PRED5(CheckCandidate, candidates_[3], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "tcp", + kClientAddr); + EXPECT_EQ(4U, candidates_.size()); +} + +// Test that the httpportallocator correctly maintains its lists of stun and +// relay servers, by never allowing an empty list. +TEST(HttpPortAllocatorTest, TestHttpPortAllocatorHostLists) { + rtc::FakeNetworkManager network_manager; + cricket::HttpPortAllocator alloc(&network_manager, "unit test agent"); + EXPECT_EQ(1U, alloc.relay_hosts().size()); + EXPECT_EQ(1U, alloc.stun_hosts().size()); + + std::vector relay_servers; + std::vector stun_servers; + + alloc.SetRelayHosts(relay_servers); + alloc.SetStunHosts(stun_servers); + EXPECT_EQ(1U, alloc.relay_hosts().size()); + EXPECT_EQ(1U, alloc.stun_hosts().size()); + + relay_servers.push_back("1.unittest.corp.google.com"); + relay_servers.push_back("2.unittest.corp.google.com"); + stun_servers.push_back( + rtc::SocketAddress("1.unittest.corp.google.com", 0)); + stun_servers.push_back( + rtc::SocketAddress("2.unittest.corp.google.com", 0)); + + alloc.SetRelayHosts(relay_servers); + alloc.SetStunHosts(stun_servers); + EXPECT_EQ(2U, alloc.relay_hosts().size()); + EXPECT_EQ(2U, alloc.stun_hosts().size()); +} + +// Test that the HttpPortAllocator uses correct URL to create sessions. +TEST(HttpPortAllocatorTest, TestSessionRequestUrl) { + rtc::FakeNetworkManager network_manager; + cricket::HttpPortAllocator alloc(&network_manager, "unit test agent"); + + // Disable PORTALLOCATOR_ENABLE_SHARED_UFRAG. + alloc.set_flags(alloc.flags() & ~cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG); + rtc::scoped_ptr session( + static_cast( + alloc.CreateSessionInternal( + "test content", 0, kIceUfrag0, kIcePwd0))); + std::string url = session->GetSessionRequestUrl(); + LOG(LS_INFO) << "url: " << url; + EXPECT_EQ(std::string(cricket::HttpPortAllocator::kCreateSessionURL), url); + + // Enable PORTALLOCATOR_ENABLE_SHARED_UFRAG. + alloc.set_flags(alloc.flags() | cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG); + session.reset(static_cast( + alloc.CreateSessionInternal("test content", 0, kIceUfrag0, kIcePwd0))); + url = session->GetSessionRequestUrl(); + LOG(LS_INFO) << "url: " << url; + std::vector parts; + rtc::split(url, '?', &parts); + ASSERT_EQ(2U, parts.size()); + + std::vector args_parts; + rtc::split(parts[1], '&', &args_parts); + + std::map args; + for (std::vector::iterator it = args_parts.begin(); + it != args_parts.end(); ++it) { + std::vector parts; + rtc::split(*it, '=', &parts); + ASSERT_EQ(2U, parts.size()); + args[rtc::s_url_decode(parts[0])] = rtc::s_url_decode(parts[1]); + } + + EXPECT_EQ(kIceUfrag0, args["username"]); + EXPECT_EQ(kIcePwd0, args["password"]); +} diff --git a/webrtc/p2p/client/sessionmanagertask.h b/webrtc/p2p/client/sessionmanagertask.h new file mode 100644 index 000000000..04d79d48c --- /dev/null +++ b/webrtc/p2p/client/sessionmanagertask.h @@ -0,0 +1,76 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_CLIENT_SESSIONMANAGERTASK_H_ +#define WEBRTC_P2P_CLIENT_SESSIONMANAGERTASK_H_ + +#include "webrtc/p2p/base/sessionmanager.h" +#include "webrtc/p2p/client/sessionsendtask.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" + +namespace cricket { + +// This class handles sending and receiving XMPP messages on behalf of the +// SessionManager. The sending part is handed over to SessionSendTask. + +class SessionManagerTask : public buzz::XmppTask { + public: + SessionManagerTask(buzz::XmppTaskParentInterface* parent, + SessionManager* session_manager) + : buzz::XmppTask(parent, buzz::XmppEngine::HL_SINGLE), + session_manager_(session_manager) { + } + + ~SessionManagerTask() { + } + + // Turns on simple support for sending messages, using SessionSendTask. + void EnableOutgoingMessages() { + session_manager_->SignalOutgoingMessage.connect( + this, &SessionManagerTask::OnOutgoingMessage); + session_manager_->SignalRequestSignaling.connect( + session_manager_, &SessionManager::OnSignalingReady); + } + + virtual int ProcessStart() { + const buzz::XmlElement *stanza = NextStanza(); + if (stanza == NULL) + return STATE_BLOCKED; + session_manager_->OnIncomingMessage(stanza); + return STATE_START; + } + + protected: + virtual bool HandleStanza(const buzz::XmlElement *stanza) { + if (!session_manager_->IsSessionMessage(stanza)) + return false; + // Responses are handled by the SessionSendTask that sent the request. + //if (stanza->Attr(buzz::QN_TYPE) != buzz::STR_SET) + // return false; + QueueStanza(stanza); + return true; + } + + private: + void OnOutgoingMessage(SessionManager* manager, + const buzz::XmlElement* stanza) { + cricket::SessionSendTask* sender = + new cricket::SessionSendTask(parent_, session_manager_); + sender->Send(stanza); + sender->Start(); + } + + SessionManager* session_manager_; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_CLIENT_SESSIONMANAGERTASK_H_ diff --git a/webrtc/p2p/client/sessionsendtask.h b/webrtc/p2p/client/sessionsendtask.h new file mode 100644 index 000000000..818aa1adc --- /dev/null +++ b/webrtc/p2p/client/sessionsendtask.h @@ -0,0 +1,128 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_CLIENT_SESSIONSENDTASK_H_ +#define WEBRTC_P2P_CLIENT_SESSIONSENDTASK_H_ + +#include "webrtc/p2p/base/sessionmanager.h" +#include "webrtc/libjingle/xmpp/constants.h" +#include "webrtc/libjingle/xmpp/xmppclient.h" +#include "webrtc/libjingle/xmpp/xmppengine.h" +#include "webrtc/libjingle/xmpp/xmpptask.h" +#include "webrtc/base/common.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 SessionManager. If an error +// happens it will also signal failure. If, however, the send succeeds this +// task will quietly go away. + +class SessionSendTask : public buzz::XmppTask { + public: + SessionSendTask(buzz::XmppTaskParentInterface* parent, + SessionManager* session_manager) + : buzz::XmppTask(parent, buzz::XmppEngine::HL_SINGLE), + session_manager_(session_manager) { + set_timeout_seconds(15); + session_manager_->SignalDestroyed.connect( + this, &SessionSendTask::OnSessionManagerDestroyed); + } + + virtual ~SessionSendTask() { + SignalDone(this); + } + + void Send(const buzz::XmlElement* stanza) { + ASSERT(stanza_.get() == NULL); + + // This should be an IQ of type set, result, or error. In the first case, + // we supply an ID. In the others, it should be present. + ASSERT(stanza->Name() == buzz::QN_IQ); + ASSERT(stanza->HasAttr(buzz::QN_TYPE)); + if (stanza->Attr(buzz::QN_TYPE) == "set") { + ASSERT(!stanza->HasAttr(buzz::QN_ID)); + } else { + ASSERT((stanza->Attr(buzz::QN_TYPE) == "result") || + (stanza->Attr(buzz::QN_TYPE) == "error")); + ASSERT(stanza->HasAttr(buzz::QN_ID)); + } + + stanza_.reset(new buzz::XmlElement(*stanza)); + if (stanza_->HasAttr(buzz::QN_ID)) { + set_task_id(stanza_->Attr(buzz::QN_ID)); + } else { + stanza_->SetAttr(buzz::QN_ID, task_id()); + } + } + + void OnSessionManagerDestroyed() { + // If the session manager doesn't exist anymore, we should still try to + // send the message, but avoid calling back into the SessionManager. + session_manager_ = NULL; + } + + sigslot::signal1 SignalDone; + + protected: + virtual int OnTimeout() { + if (session_manager_ != NULL) { + session_manager_->OnFailedSend(stanza_.get(), NULL); + } + + return XmppTask::OnTimeout(); + } + + virtual int ProcessStart() { + SendStanza(stanza_.get()); + if (stanza_->Attr(buzz::QN_TYPE) == buzz::STR_SET) { + return STATE_RESPONSE; + } else { + return STATE_DONE; + } + } + + virtual int ProcessResponse() { + const buzz::XmlElement* next = NextStanza(); + if (next == NULL) + return STATE_BLOCKED; + + if (session_manager_ != NULL) { + if (next->Attr(buzz::QN_TYPE) == buzz::STR_RESULT) { + session_manager_->OnIncomingResponse(stanza_.get(), next); + } else { + session_manager_->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) == buzz::STR_RESULT || + stanza->Attr(buzz::QN_TYPE) == buzz::STR_ERROR) { + QueueStanza(stanza); + return true; + } + return false; + } + + private: + SessionManager *session_manager_; + rtc::scoped_ptr stanza_; +}; + +} + +#endif // WEBRTC_P2P_CLIENT_SESSIONSENDTASK_H_ diff --git a/webrtc/p2p/client/socketmonitor.cc b/webrtc/p2p/client/socketmonitor.cc new file mode 100644 index 000000000..5245535bc --- /dev/null +++ b/webrtc/p2p/client/socketmonitor.cc @@ -0,0 +1,97 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/client/socketmonitor.h" + +#include "webrtc/base/common.h" + +namespace cricket { + +enum { + MSG_MONITOR_POLL, + MSG_MONITOR_START, + MSG_MONITOR_STOP, + MSG_MONITOR_SIGNAL +}; + +SocketMonitor::SocketMonitor(TransportChannel* channel, + rtc::Thread* worker_thread, + rtc::Thread* monitor_thread) { + channel_ = channel; + channel_thread_ = worker_thread; + monitoring_thread_ = monitor_thread; + monitoring_ = false; +} + +SocketMonitor::~SocketMonitor() { + channel_thread_->Clear(this); + monitoring_thread_->Clear(this); +} + +void SocketMonitor::Start(int milliseconds) { + rate_ = milliseconds; + if (rate_ < 250) + rate_ = 250; + channel_thread_->Post(this, MSG_MONITOR_START); +} + +void SocketMonitor::Stop() { + channel_thread_->Post(this, MSG_MONITOR_STOP); +} + +void SocketMonitor::OnMessage(rtc::Message *message) { + rtc::CritScope cs(&crit_); + switch (message->message_id) { + case MSG_MONITOR_START: + ASSERT(rtc::Thread::Current() == channel_thread_); + if (!monitoring_) { + monitoring_ = true; + PollSocket(true); + } + break; + + case MSG_MONITOR_STOP: + ASSERT(rtc::Thread::Current() == channel_thread_); + if (monitoring_) { + monitoring_ = false; + channel_thread_->Clear(this); + } + break; + + case MSG_MONITOR_POLL: + ASSERT(rtc::Thread::Current() == channel_thread_); + PollSocket(true); + break; + + case MSG_MONITOR_SIGNAL: { + ASSERT(rtc::Thread::Current() == monitoring_thread_); + std::vector infos = connection_infos_; + crit_.Leave(); + SignalUpdate(this, infos); + crit_.Enter(); + break; + } + } +} + +void SocketMonitor::PollSocket(bool poll) { + ASSERT(rtc::Thread::Current() == channel_thread_); + rtc::CritScope cs(&crit_); + + // Gather connection infos + channel_->GetStats(&connection_infos_); + + // Signal the monitoring thread, start another poll timer + monitoring_thread_->Post(this, MSG_MONITOR_SIGNAL); + if (poll) + channel_thread_->PostDelayed(rate_, this, MSG_MONITOR_POLL); +} + +} // namespace cricket diff --git a/webrtc/p2p/client/socketmonitor.h b/webrtc/p2p/client/socketmonitor.h new file mode 100644 index 000000000..5c10a4ec0 --- /dev/null +++ b/webrtc/p2p/client/socketmonitor.h @@ -0,0 +1,54 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_CLIENT_SOCKETMONITOR_H_ +#define WEBRTC_P2P_CLIENT_SOCKETMONITOR_H_ + +#include + +#include "webrtc/p2p/base/transportchannel.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/sigslot.h" +#include "webrtc/base/thread.h" + +namespace cricket { + +class SocketMonitor : public rtc::MessageHandler, + public sigslot::has_slots<> { + public: + SocketMonitor(TransportChannel* channel, + rtc::Thread* worker_thread, + rtc::Thread* monitor_thread); + ~SocketMonitor(); + + void Start(int cms); + void Stop(); + + rtc::Thread* monitor_thread() { return monitoring_thread_; } + + sigslot::signal2&> SignalUpdate; + + protected: + void OnMessage(rtc::Message* message); + void PollSocket(bool poll); + + std::vector connection_infos_; + TransportChannel* channel_; + rtc::Thread* channel_thread_; + rtc::Thread* monitoring_thread_; + rtc::CriticalSection crit_; + uint32 rate_; + bool monitoring_; +}; + +} // namespace cricket + +#endif // WEBRTC_P2P_CLIENT_SOCKETMONITOR_H_ diff --git a/webrtc/p2p/p2p.gyp b/webrtc/p2p/p2p.gyp new file mode 100644 index 000000000..102e75b58 --- /dev/null +++ b/webrtc/p2p/p2p.gyp @@ -0,0 +1,130 @@ +# Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. + +{ + 'includes': [ '../build/common.gypi', ], + 'targets': [ + { + 'target_name': 'rtc_p2p', + 'type': 'static_library', + 'dependencies': [ + '<(webrtc_root)/base/base.gyp:webrtc_base', + '<(webrtc_root)/libjingle/xmpp/xmpp.gyp:rtc_xmpp', + '<(DEPTH)/third_party/expat/expat.gyp:expat', + ], + 'cflags_cc!': [ + '-Wnon-virtual-dtor', + ], + 'export_dependent_settings': [ + '<(DEPTH)/third_party/expat/expat.gyp:expat', + ], + 'sources': [ + 'base/asyncstuntcpsocket.cc', + 'base/asyncstuntcpsocket.h', + 'base/basicpacketsocketfactory.cc', + 'base/basicpacketsocketfactory.h', + 'base/candidate.h', + 'base/common.h', + 'base/constants.cc', + 'base/constants.h', + 'base/dtlstransportchannel.cc', + 'base/dtlstransportchannel.h', + 'base/p2ptransport.cc', + 'base/p2ptransport.h', + 'base/p2ptransportchannel.cc', + 'base/p2ptransportchannel.h', + 'base/packetsocketfactory.h', + 'base/parsing.cc', + 'base/parsing.h', + 'base/port.cc', + 'base/port.h', + 'base/portallocator.cc', + 'base/portallocator.h', + 'base/portallocatorsessionproxy.cc', + 'base/portallocatorsessionproxy.h', + 'base/portinterface.h', + 'base/portproxy.cc', + 'base/portproxy.h', + 'base/pseudotcp.cc', + 'base/pseudotcp.h', + 'base/rawtransport.cc', + 'base/rawtransport.h', + 'base/rawtransportchannel.cc', + 'base/rawtransportchannel.h', + 'base/relayport.cc', + 'base/relayport.h', + 'base/relayserver.cc', + 'base/relayserver.h', + 'base/session.cc', + 'base/session.h', + 'base/sessionclient.h', + 'base/sessiondescription.cc', + 'base/sessiondescription.h', + 'base/sessionid.h', + 'base/sessionmanager.cc', + 'base/sessionmanager.h', + 'base/sessionmessages.cc', + 'base/sessionmessages.h', + 'base/stun.cc', + 'base/stun.h', + 'base/stunport.cc', + 'base/stunport.h', + 'base/stunrequest.cc', + 'base/stunrequest.h', + 'base/stunserver.cc', + 'base/stunserver.h', + 'base/tcpport.cc', + 'base/tcpport.h', + 'base/transport.cc', + 'base/transport.h', + 'base/transportchannel.cc', + 'base/transportchannel.h', + 'base/transportchannelimpl.h', + 'base/transportchannelproxy.cc', + 'base/transportchannelproxy.h', + 'base/transportdescription.cc', + 'base/transportdescription.h', + 'base/transportdescriptionfactory.cc', + 'base/transportdescriptionfactory.h', + 'base/transportinfo.h', + 'base/turnport.cc', + 'base/turnport.h', + 'base/turnserver.cc', + 'base/turnserver.h', + 'base/udpport.h', + 'client/autoportallocator.h', + 'client/basicportallocator.cc', + 'client/basicportallocator.h', + 'client/connectivitychecker.cc', + 'client/connectivitychecker.h', + 'client/httpportallocator.cc', + 'client/httpportallocator.h', + 'client/sessionmanagertask.h', + 'client/sessionsendtask.h', + 'client/socketmonitor.cc', + 'client/socketmonitor.h', + ], + 'direct_dependent_settings': { + 'cflags_cc!': [ + '-Wnon-virtual-dtor', + ], + 'defines': [ + 'FEATURE_ENABLE_VOICEMAIL', + ], + }, + 'conditions': [ + ['build_with_chromium==0', { + 'defines': [ + 'FEATURE_ENABLE_VOICEMAIL', + 'FEATURE_ENABLE_PSTN', + ], + }], + ], + }], +} + diff --git a/webrtc/p2p/p2p_tests.gypi b/webrtc/p2p/p2p_tests.gypi new file mode 100644 index 000000000..f9e695943 --- /dev/null +++ b/webrtc/p2p/p2p_tests.gypi @@ -0,0 +1,44 @@ +# Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. + +{ + 'includes': [ '../build/common.gypi', ], + 'targets': [ + { + 'target_name': 'rtc_p2p_unittest', + 'type': 'none', + 'direct_dependent_settings': { + 'sources': [ + 'base/dtlstransportchannel_unittest.cc', + 'base/fakesession.h', + 'base/p2ptransportchannel_unittest.cc', + 'base/port_unittest.cc', + 'base/portallocatorsessionproxy_unittest.cc', + 'base/pseudotcp_unittest.cc', + 'base/relayport_unittest.cc', + 'base/relayserver_unittest.cc', + 'base/session_unittest.cc', + 'base/stun_unittest.cc', + 'base/stunport_unittest.cc', + 'base/stunrequest_unittest.cc', + 'base/stunserver_unittest.cc', + 'base/testrelayserver.h', + 'base/teststunserver.h', + 'base/testturnserver.h', + 'base/transport_unittest.cc', + 'base/transportdescriptionfactory_unittest.cc', + 'base/turnport_unittest.cc', + 'client/connectivitychecker_unittest.cc', + 'client/fakeportallocator.h', + 'client/portallocator_unittest.cc', + ], + }, + }, + ], +} + diff --git a/webrtc/webrtc.gyp b/webrtc/webrtc.gyp index bfcec0409..2b24deb3b 100644 --- a/webrtc/webrtc.gyp +++ b/webrtc/webrtc.gyp @@ -10,6 +10,8 @@ ['include_tests==1', { 'includes': [ 'libjingle/xmllite/xmllite_tests.gypi', + 'libjingle/xmpp/xmpp_tests.gypi', + 'p2p/p2p_tests.gypi', 'sound/sound_tests.gypi', 'webrtc_tests.gypi', ], @@ -27,7 +29,9 @@ 'common_audio/common_audio.gyp:*', 'common_video/common_video.gyp:*', 'libjingle/xmllite/xmllite.gyp:*', + 'libjingle/xmpp/xmpp.gyp:*', 'modules/modules.gyp:*', + 'p2p/p2p.gyp:*', 'system_wrappers/source/system_wrappers.gyp:*', 'video_engine/video_engine.gyp:*', 'voice_engine/voice_engine.gyp:*', diff --git a/webrtc/webrtc_tests.gypi b/webrtc/webrtc_tests.gypi index 847feffd0..33e50e706 100644 --- a/webrtc/webrtc_tests.gypi +++ b/webrtc/webrtc_tests.gypi @@ -15,8 +15,12 @@ 'base/base_tests.gyp:rtc_base_tests_utils', 'base/base_tests.gyp:rtc_base_tests', 'libjingle/xmllite/xmllite.gyp:rtc_xmllite', + 'libjingle/xmpp/xmpp.gyp:rtc_xmpp', + 'p2p/p2p.gyp:rtc_p2p', + 'rtc_p2p_unittest', 'rtc_sound_tests', 'rtc_xmllite_unittest', + 'rtc_xmpp_unittest', 'sound/sound.gyp:rtc_sound', '<(DEPTH)/testing/gtest.gyp:gtest', ],