1700 lines
50 KiB
C++
1700 lines
50 KiB
C++
#include "B2BSession.hxx"
|
|
#include "Server.hxx"
|
|
#include "IChatIPPortData.hxx"
|
|
#include "AppSubsystem.hxx"
|
|
|
|
#include <resip/stack/PlainContents.hxx>
|
|
#include <resip/stack/SdpContents.hxx>
|
|
#include <resip/dum/ServerInviteSession.hxx>
|
|
#include <resip/dum/ClientInviteSession.hxx>
|
|
#include <resip/dum/ClientSubscription.hxx>
|
|
#include <rutil/DnsUtil.hxx>
|
|
#include <rutil/Log.hxx>
|
|
#include <rutil/Logger.hxx>
|
|
#include <rutil/WinLeakCheck.hxx>
|
|
|
|
using namespace gateway;
|
|
using namespace resip;
|
|
using namespace std;
|
|
|
|
#define RESIPROCATE_SUBSYSTEM AppSubsystem::GATEWAY
|
|
|
|
#ifdef BRIEF_MSG_LOGGING
|
|
#define LOG_MSG msg.brief()
|
|
#define LOG_MSG_WITH_SDP msg.brief() << ", sdp=" << sdp
|
|
#define LOG_MSGP msg->brief()
|
|
#else
|
|
#define LOG_MSG endl << msg
|
|
#define LOG_MSG_WITH_SDP endl << msg
|
|
#define LOG_MSGP endl << *msg
|
|
#endif
|
|
#define B2BLOG_PREFIX << "B2BSession[" << mHandle << "] "
|
|
|
|
namespace gateway
|
|
{
|
|
|
|
B2BSession::B2BSession(Server& server, bool hasDialogSet) :
|
|
AppDialogSet(server.getDialogUsageManager()),
|
|
mServer(server),
|
|
mHasDialogSet(hasDialogSet),
|
|
mDum(server.getDialogUsageManager()),
|
|
mPeer(0),
|
|
mUACConnectedDialogId(Data::Empty, Data::Empty, Data::Empty),
|
|
mWaitingOfferFromPeer(false),
|
|
mWaitingAnswerFromPeer(false),
|
|
mWaitingNitAnswerFromPeer(false),
|
|
mAnchorMedia(false),
|
|
mMediaRelayPort(0),
|
|
mIChatEndpoint(false),
|
|
mIChatWaitingToAccept(false),
|
|
mIChatWaitingToProceed(false),
|
|
mIChatWaitingToContinue(false),
|
|
mIChatSdp(0)
|
|
{
|
|
mHandle = mServer.registerB2BSession(this);
|
|
}
|
|
|
|
B2BSession::~B2BSession()
|
|
{
|
|
endPeer();
|
|
if(mMediaRelayPort != 0)
|
|
{
|
|
mServer.mMediaRelay->destroyRelay(mMediaRelayPort);
|
|
}
|
|
if(mIChatSdp) delete mIChatSdp;
|
|
mServer.unregisterB2BSession(mHandle);
|
|
}
|
|
|
|
void
|
|
B2BSession::end()
|
|
{
|
|
if(mIChatWaitingToAccept)
|
|
{
|
|
rejectIChatCall();
|
|
}
|
|
|
|
if (mHasDialogSet)
|
|
{
|
|
AppDialogSet::end();
|
|
}
|
|
else
|
|
{
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::setPeer(B2BSession* peer)
|
|
{
|
|
mPeer = peer;
|
|
}
|
|
|
|
B2BSession*
|
|
B2BSession::getPeer()
|
|
{
|
|
return mPeer;
|
|
}
|
|
|
|
void
|
|
B2BSession::stealPeer(B2BSession* victimSession)
|
|
{
|
|
// Assume Peer mapping of victim - and copy some settings
|
|
setPeer(victimSession->getPeer());
|
|
if(mPeer)
|
|
{
|
|
mPeer->setPeer(this);
|
|
}
|
|
mMediaRelayPort = victimSession->mMediaRelayPort;
|
|
victimSession->mMediaRelayPort = 0; // clear out so that session will not dealloc media port
|
|
mAnchorMedia = victimSession->mAnchorMedia;
|
|
|
|
// Clear peer mapping in victim session
|
|
victimSession->setPeer(0);
|
|
}
|
|
|
|
class IChatCallTimeout : public resip::DumCommand
|
|
{
|
|
public:
|
|
IChatCallTimeout(const Server& server, const B2BSessionHandle& handle) :
|
|
mServer(server), mB2BSessionHandle(handle) {}
|
|
IChatCallTimeout(const IChatCallTimeout& rhs) :
|
|
mServer(rhs.mServer), mB2BSessionHandle(rhs.mB2BSessionHandle) {}
|
|
~IChatCallTimeout() {}
|
|
|
|
void executeCommand()
|
|
{
|
|
B2BSession* session = mServer.getB2BSession(mB2BSessionHandle);
|
|
if(session)
|
|
{
|
|
session->timeoutIChatCall();
|
|
}
|
|
}
|
|
|
|
resip::Message* clone() const { return new IChatCallTimeout(*this); }
|
|
EncodeStream& encode(EncodeStream& strm) const { strm << "IChatCallTimeout: handle=" << mB2BSessionHandle; return strm; }
|
|
EncodeStream& encodeBrief(EncodeStream& strm) const { return encode(strm); }
|
|
|
|
private:
|
|
const Server& mServer;
|
|
B2BSessionHandle mB2BSessionHandle;
|
|
};
|
|
|
|
void
|
|
B2BSession::initiateIChatCallRequest(const std::string& to, const std::string& from)
|
|
{
|
|
// Notify jabber connector we are proceeding and what our session handle is
|
|
mIChatCallToJID = to;
|
|
mIChatCallFromJID = from;
|
|
mIChatEndpoint = true;
|
|
mIChatWaitingToAccept = true;
|
|
proceedingIChatCall();
|
|
|
|
bool result=false;
|
|
try
|
|
{
|
|
Uri toUri(Data("xmpp:") + to.c_str());
|
|
try
|
|
{
|
|
NameAddr fromNameAddr(Data("sip:") + from.c_str());
|
|
fromNameAddr.displayName() = fromNameAddr.uri().user();
|
|
result = createNewPeer(toUri, fromNameAddr, 0);
|
|
}
|
|
catch(resip::BaseException& e)
|
|
{
|
|
ErrLog(B2BLOG_PREFIX << "Error creating NameAddr from Jabber from header=" << from << ", error=" << e);
|
|
}
|
|
}
|
|
catch(resip::BaseException& e)
|
|
{
|
|
ErrLog(B2BLOG_PREFIX << "Error extracting Uri from Jabber To header=" << to << ", error=" << e);
|
|
}
|
|
|
|
if(!result)
|
|
{
|
|
rejectIChatCall();
|
|
end();
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::startIChatCall(const Uri& destinationUri, const NameAddr& from, const SdpContents *sdp)
|
|
{
|
|
resip_assert(destinationUri.scheme() == "xmpp");
|
|
|
|
mIChatEndpoint = true;
|
|
mIChatDestination = destinationUri;
|
|
mIChatFrom = from;
|
|
if(sdp)
|
|
{
|
|
mIChatSdp = new SdpContents(*sdp);
|
|
}
|
|
|
|
mIChatCallToJID = Data::from(destinationUri).substr(5).c_str(); // Get all of address, except xmpp: part
|
|
Uri fromUri;
|
|
// Note: for some reason iChat will send a 603 Decline if the username is more than 10 characters - so we truncate
|
|
fromUri.user() = from.uri().user().substr(0, from.uri().user().size() > 10 ? 10 : from.uri().user().size());
|
|
fromUri.host() = mServer.mJabberComponentName;
|
|
mIChatCallFromJID = fromUri.getAor().c_str();
|
|
|
|
initiateIChatCall();
|
|
|
|
// Start a timer
|
|
IChatCallTimeout t(mServer, mHandle);
|
|
mServer.post(t, mServer.mIChatProceedingTimeout);
|
|
mIChatWaitingToProceed = true;
|
|
}
|
|
|
|
void
|
|
B2BSession::startSIPCall(const Uri& destinationUri, const NameAddr& from, const SdpContents *sdp)
|
|
{
|
|
// Create a UserProfile for new call
|
|
SharedPtr<UserProfile> userProfile(new UserProfile(mServer.getMasterProfile()));
|
|
|
|
// Set the From address
|
|
userProfile->setDefaultFrom(from);
|
|
userProfile->getDefaultFrom().remove(p_tag); // Remove tag (if it exists)
|
|
|
|
// Create the invite message
|
|
SharedPtr<SipMessage> invitemsg = mDum.makeInviteSession(
|
|
NameAddr(destinationUri),
|
|
userProfile,
|
|
sdp,
|
|
this);
|
|
|
|
// Send the invite message
|
|
mDum.send(invitemsg);
|
|
}
|
|
|
|
bool
|
|
B2BSession::checkIChatCallMatch(const resip::SipMessage& msg)
|
|
{
|
|
if(!mHasDialogSet && !mIChatWaitingToAccept)
|
|
{
|
|
// Check if URI matches
|
|
// If we are an iChat endpoint then real To uri is actually in the display name of the To header
|
|
InfoLog(B2BLOG_PREFIX << "B2BSession::checkIChatCallMatch: checking to: " << msg.header(h_To).displayName() << " = " << mIChatCallToJID);
|
|
InfoLog(B2BLOG_PREFIX << "B2BSession::checkIChatCallMatch: checking from: " << msg.header(h_From).displayName() << " = " << mIChatCallFromJID);
|
|
if(msg.header(h_To).displayName() == Data(mIChatCallToJID.c_str()) &&
|
|
msg.header(h_From).displayName() == Data(mIChatCallFromJID.c_str()))
|
|
{
|
|
mHasDialogSet = true; // If we match, we are going to get a dialog set associated with us
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
B2BSession::createNewPeer(const Uri& destinationUri, const NameAddr& from, const SdpContents *sdp)
|
|
{
|
|
const SdpContents *pSdp = sdp;
|
|
|
|
resip_assert(!mPeer);
|
|
|
|
mPeer = new B2BSession(mServer);
|
|
|
|
Data destinationData;
|
|
if(!mServer.translateAddress(Data::from(destinationUri), destinationData, true /* failIfNoRule */))
|
|
{
|
|
ErrLog(B2BLOG_PREFIX << "B2BSession::createNewPeer: No translation rule for " << destinationUri);
|
|
delete mPeer;
|
|
mPeer = 0;
|
|
return false;
|
|
}
|
|
Uri destination;
|
|
try
|
|
{
|
|
destination = Uri(destinationData);
|
|
}
|
|
catch(resip::BaseException& e)
|
|
{
|
|
ErrLog(B2BLOG_PREFIX << "B2BSession::createNewPeer: Translation of " << destinationUri << " resulted in invalid uri format: " << e);
|
|
delete mPeer;
|
|
mPeer = 0;
|
|
return false;
|
|
}
|
|
|
|
// Check if media needs to be anchored
|
|
SdpContents localOffer;
|
|
if((mServer.mAlwaysRelayIChatMedia && mIChatEndpoint) || destination.scheme() == "xmpp" || !sdp)
|
|
{
|
|
mAnchorMedia = true;
|
|
mPeer->mAnchorMedia = true;
|
|
|
|
// Check if sdp used to create new peer is locally generated - if so, don't allocate a new media relay, use the existing one
|
|
if(sdp && sdp->session().origin().user() == SDP_ICHATGW_ORIGIN_USER)
|
|
{
|
|
if(sdp->session().media().size() >= 1 && sdp->session().media().front().port() != 0)
|
|
{
|
|
mMediaRelayPort = sdp->session().media().front().port();
|
|
InfoLog(B2BLOG_PREFIX << "B2BSession::createNewPeer: Detected looped back session - sharing media relay (port=" << mMediaRelayPort << ") with originator.");
|
|
}
|
|
}
|
|
|
|
// Replace passed in remote SDP with local SDP
|
|
if(!buildLocalOffer(localOffer))
|
|
{
|
|
ErrLog(B2BLOG_PREFIX << "B2BSession::createNewPeer: Unable to build local offer.");
|
|
delete mPeer;
|
|
mPeer = 0;
|
|
return false;
|
|
}
|
|
pSdp = &localOffer;
|
|
mPeer->mMediaRelayPort = mMediaRelayPort;
|
|
}
|
|
|
|
// Create B2BCall
|
|
InfoLog(B2BLOG_PREFIX << "B2BSession::createNewPeer: Routing new call to " << destination);
|
|
|
|
// Map other leg to this one
|
|
mPeer->setPeer(this);
|
|
|
|
if(destination.scheme() == "xmpp")
|
|
{
|
|
mPeer->startIChatCall(destination, from, pSdp);
|
|
}
|
|
else
|
|
{
|
|
mPeer->startSIPCall(destination, from, pSdp);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
B2BSession::notifyIChatCallCancelled()
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "notifyIChatCallCancelled");
|
|
mIChatWaitingToAccept = false;
|
|
end();
|
|
}
|
|
|
|
void
|
|
B2BSession::notifyIChatCallProceeding(const std::string& to)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "notifyIChatCallProceeding: to=" << to);
|
|
mIChatWaitingToProceed = false;
|
|
mIChatWaitingToContinue = true;
|
|
if(mPeer)
|
|
{
|
|
if(mPeer->mInviteSessionHandle.isValid())
|
|
{
|
|
// Sending a provisional back to the other end should cause ringback to be played
|
|
ServerInviteSession* sis = dynamic_cast<ServerInviteSession*>(mPeer->mInviteSessionHandle.get());
|
|
if(sis && !sis->isAccepted())
|
|
{
|
|
sis->provisional(180);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::notifyIChatCallFailed(unsigned int statusCode)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "notifyIChatCallFailed: Failed IChat call, statusCode=" << statusCode << " - tearing down other leg.");
|
|
endPeer();
|
|
delete this;
|
|
}
|
|
|
|
void
|
|
B2BSession::continueIChatCall(const std::string& remoteIPPortListBlob)
|
|
{
|
|
resip_assert(!mIChatWaitingToProceed);
|
|
resip_assert(mIChatWaitingToContinue);
|
|
|
|
mIChatWaitingToContinue = false;
|
|
|
|
// Create a UserProfile for new call
|
|
SharedPtr<UserProfile> userProfile(new UserProfile(mServer.getMasterProfile()));
|
|
|
|
// Doesn't really matter what's in the SIP message from header that goes to iChat - it's never displayed to the client
|
|
userProfile->setDefaultFrom(mServer.getMasterProfile()->getDefaultFrom());
|
|
|
|
Tuple destinationIPPort;
|
|
IChatIPPortData remoteIPPortList(remoteIPPortListBlob);
|
|
|
|
// Take first ipv4 or ipv6 entry - for ipv4 it appears to be a STUN mapped address
|
|
bool skipFirst = mServer.mSkipFirstIChatAddress; // Note (for testing): skipping the first IPV4 NAT mapped address (on systems with one interface) causes the local address to be used
|
|
bool findTilda = false; // Note (for testing): Not too sure what the ~ addresses are - they could be media relay servers - you cannot just send RTP to them, so there must be some additional protocol to allocate a relay
|
|
bool v4found = false;
|
|
IChatIPPortData::IPPortDataList::const_iterator it = remoteIPPortList.getIPPortDataList().begin();
|
|
for(; it != remoteIPPortList.getIPPortDataList().end(); it++)
|
|
{
|
|
if(!v4found && it->second.ipVersion() == V4)
|
|
{
|
|
if(skipFirst)
|
|
{
|
|
skipFirst = false;
|
|
}
|
|
else
|
|
{
|
|
if(!findTilda || it->first.find("~") != Data::npos)
|
|
{
|
|
// return first matching address
|
|
destinationIPPort = it->second;
|
|
if(!mServer.mPreferIPv6) break;
|
|
v4found = true;
|
|
}
|
|
}
|
|
}
|
|
if(mServer.mPreferIPv6 && it->second.ipVersion() == V6)
|
|
{
|
|
if(skipFirst)
|
|
{
|
|
skipFirst = false;
|
|
}
|
|
else
|
|
{
|
|
if(!findTilda || it->first.find("~") != Data::npos)
|
|
{
|
|
// return first matching address
|
|
destinationIPPort = it->second;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(destinationIPPort.getPort() == 0)
|
|
{
|
|
// We didn't find an applicable address
|
|
InfoLog(B2BLOG_PREFIX << "B2BSession::continueIChatCall: no appropriate address found in remoteIPPortList.");
|
|
notifyIChatCallFailed(603); // Decline
|
|
return;
|
|
}
|
|
|
|
Data uriData;
|
|
if(destinationIPPort.ipVersion() == V6)
|
|
{
|
|
uriData = "sip:user@[" + destinationIPPort.presentationFormat() + "]:" + Data(destinationIPPort.getPort());
|
|
}
|
|
else
|
|
{
|
|
uriData = "sip:user@" + destinationIPPort.presentationFormat() + ":" + Data(destinationIPPort.getPort());
|
|
}
|
|
|
|
try
|
|
{
|
|
NameAddr destination(uriData);
|
|
userProfile->setOutboundProxy(destination.uri());
|
|
userProfile->setForceOutboundProxyOnAllRequestsEnabled(true);
|
|
|
|
InfoLog(B2BLOG_PREFIX << "B2BSession::continueIChatCall: to=" << destination << " tuple=" << destinationIPPort);
|
|
|
|
// Create the invite message
|
|
SharedPtr<SipMessage> invitemsg = mDum.makeInviteSession(
|
|
destination,
|
|
userProfile,
|
|
mIChatSdp,
|
|
this);
|
|
|
|
// Send the invite message
|
|
mDum.send(invitemsg);
|
|
|
|
// Prime the RTP engine of iChat if using IPv6
|
|
// Note: under IPv6 iChat will not send an initial RTP packet until it receives one
|
|
if(destinationIPPort.ipVersion() == V6 && mMediaRelayPort > 0)
|
|
{
|
|
mServer.mMediaRelay->primeNextEndpoint(mMediaRelayPort, destinationIPPort);
|
|
}
|
|
}
|
|
catch(resip::BaseException& e)
|
|
{
|
|
ErrLog(B2BLOG_PREFIX << "continueIChatCall: Invalid NameAddr format=" << uriData << ": " << e);
|
|
notifyIChatCallFailed(500);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::timeoutIChatCall()
|
|
{
|
|
if(mIChatWaitingToProceed)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "timeoutIChatCall: could not find an appropriate iChat endpoint.");
|
|
cancelIChatCall();
|
|
|
|
notifyIChatCallFailed(408);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::initiateIChatCall()
|
|
{
|
|
IPCMsg msg;
|
|
msg.addArg("initiateIChatCall");
|
|
msg.addArg(mIChatCallToJID.c_str());
|
|
msg.addArg(mIChatCallFromJID.c_str());
|
|
msg.addArg(mHandle);
|
|
mServer.mIPCThread->sendIPCMsg(msg);
|
|
}
|
|
|
|
void
|
|
B2BSession::cancelIChatCall()
|
|
{
|
|
IPCMsg msg;
|
|
msg.addArg("cancelIChatCall");
|
|
msg.addArg(mIChatCallToJID.c_str());
|
|
msg.addArg(mIChatCallFromJID.c_str());
|
|
mServer.mIPCThread->sendIPCMsg(msg);
|
|
}
|
|
|
|
void
|
|
B2BSession::proceedingIChatCall()
|
|
{
|
|
IPCMsg msg;
|
|
msg.addArg("proceedingIChatCall");
|
|
msg.addArg(mIChatCallToJID.c_str());
|
|
msg.addArg(mIChatCallFromJID.c_str());
|
|
msg.addArg(mHandle);
|
|
mServer.mIPCThread->sendIPCMsg(msg);
|
|
}
|
|
|
|
void
|
|
B2BSession::acceptIChatCall()
|
|
{
|
|
IPCMsg msg;
|
|
msg.addArg("acceptIChatCall");
|
|
msg.addArg(mIChatCallToJID.c_str());
|
|
msg.addArg(mIChatCallFromJID.c_str());
|
|
mServer.mIPCThread->sendIPCMsg(msg);
|
|
|
|
mIChatWaitingToAccept = false;
|
|
}
|
|
|
|
void
|
|
B2BSession::rejectIChatCall()
|
|
{
|
|
IPCMsg msg;
|
|
msg.addArg("rejectIChatCall");
|
|
msg.addArg(mIChatCallToJID.c_str());
|
|
msg.addArg(mIChatCallFromJID.c_str());
|
|
mServer.mIPCThread->sendIPCMsg(msg);
|
|
|
|
mIChatWaitingToAccept = false;
|
|
}
|
|
|
|
bool
|
|
B2BSession::buildLocalOffer(SdpContents& offer)
|
|
{
|
|
// Build s=, o=, t=, and c= lines
|
|
UInt64 currentTime = Timer::getTimeMicroSec();
|
|
|
|
unsigned int port=0;
|
|
if(mAnchorMedia)
|
|
{
|
|
if(mMediaRelayPort == 0)
|
|
{
|
|
// If we don't have an allocated port yet, then create one
|
|
if(!mServer.mMediaRelay->createRelay(mMediaRelayPort))
|
|
{
|
|
ErrLog(B2BLOG_PREFIX << "Failed to allocate relay port!");
|
|
return false;
|
|
}
|
|
}
|
|
port = mMediaRelayPort;
|
|
}
|
|
|
|
// Note: The outbound decorator will take care of filling in the correct IP address before the message is sent
|
|
// to the wire.
|
|
SdpContents::Session::Origin origin(SDP_ICHATGW_ORIGIN_USER, currentTime /* sessionId */, currentTime /* version */, SdpContents::IP4, "0.0.0.0"); // o=
|
|
SdpContents::Session session(0, origin, "-" /* s= */);
|
|
session.connection() = SdpContents::Session::Connection(SdpContents::IP4, "0.0.0.0"); // c=
|
|
session.addTime(SdpContents::Session::Time(0, 0));
|
|
|
|
// Build Codecs and media offering
|
|
SdpContents::Session::Medium medium("audio", port, 1, "RTP/AVP");
|
|
SdpContents::Session::Codec g711ucodec("PCMU", 8000);
|
|
g711ucodec.payloadType() = 0; /* RFC3551 */ ;
|
|
medium.addCodec(g711ucodec);
|
|
|
|
if(mAnchorMedia)
|
|
{
|
|
medium.addAttribute("sendrecv");
|
|
}
|
|
else
|
|
{
|
|
medium.addAttribute("inactive");
|
|
}
|
|
session.addMedium(medium);
|
|
|
|
offer.session() = session;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
B2BSession::buildLocalAnswer(SdpContents& answer)
|
|
{
|
|
bool valid = false;
|
|
|
|
if(mInviteSessionHandle.isValid() && mInviteSessionHandle->hasProposedRemoteSdp())
|
|
{
|
|
const SdpContents& offer = mInviteSessionHandle->getProposedRemoteSdp();
|
|
|
|
try
|
|
{
|
|
// use our local offer as a starting place - this also allocates a relay port (if needed)
|
|
if(!buildLocalOffer(answer))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Set sessionid and version for this answer
|
|
UInt64 currentTime = Timer::getTimeMicroSec();
|
|
answer.session().origin().getSessionId() = currentTime;
|
|
answer.session().origin().getVersion() = currentTime;
|
|
|
|
// Copy t= field from sdp (RFC3264)
|
|
resip_assert(answer.session().getTimes().size() > 0);
|
|
if(offer.session().getTimes().size() >= 1)
|
|
{
|
|
answer.session().getTimes().clear();
|
|
answer.session().addTime(offer.session().getTimes().front());
|
|
}
|
|
|
|
// Clear out m= lines in answer then populate below
|
|
answer.session().media().clear();
|
|
|
|
// Loop through each offered m= line and provide a response
|
|
std::list<SdpContents::Session::Medium>::const_iterator itMediaLine = offer.session().media().begin();
|
|
for(; itMediaLine != offer.session().media().end(); itMediaLine++)
|
|
{
|
|
const SdpContents::Session::Medium& offerMediaLine = *itMediaLine;
|
|
|
|
// We only process one media stream - so if we already have a valid - just reject the rest
|
|
if(valid)
|
|
{
|
|
SdpContents::Session::Medium rejmedium(offerMediaLine.name(), 0, 1, // Reject medium by specifying port 0 (RFC3264)
|
|
offerMediaLine.protocol());
|
|
answer.session().addMedium(rejmedium);
|
|
continue;
|
|
}
|
|
|
|
// Answer Media Line
|
|
// If this is a valid audio medium then process it
|
|
|
|
if(isEqualNoCase(offerMediaLine.name(), "audio") &&
|
|
isEqualNoCase(offerMediaLine.protocol(), "RTP/AVP") &&
|
|
offerMediaLine.port() != 0)
|
|
{
|
|
SdpContents::Session::Medium medium("audio", mMediaRelayPort, 1, "RTP/AVP");
|
|
|
|
// Iterate through codecs and look for supported codecs (only G711 for now) - tag found ones by storing their payload id
|
|
std::list<Codec>::const_iterator itCodec = offerMediaLine.codecs().begin();
|
|
for(; itCodec != offerMediaLine.codecs().end(); itCodec++)
|
|
{
|
|
const Codec& codec = *itCodec;
|
|
|
|
if(isEqualNoCase(codec.getName(), "pcmu") && codec.getRate() == 8000)
|
|
{
|
|
SdpContents::Session::Codec answerCodec(codec);
|
|
answerCodec.payloadType() = codec.payloadType(); // honour offered payload id - just to be nice :)
|
|
medium.addCodec(answerCodec);
|
|
// Consider offer valid if we see any matching codec
|
|
valid = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(valid)
|
|
{
|
|
//medium.addAttribute("ptime", 20);
|
|
|
|
// Check requested direction
|
|
if(offerMediaLine.exists("inactive"))
|
|
{
|
|
medium.addAttribute("inactive");
|
|
}
|
|
else if(offerMediaLine.exists("sendonly"))
|
|
{
|
|
medium.addAttribute("recvonly");
|
|
}
|
|
else if(offerMediaLine.exists("recvonly"))
|
|
{
|
|
medium.addAttribute("sendonly");
|
|
}
|
|
else
|
|
{
|
|
// Note: sendrecv is the default in SDP
|
|
medium.addAttribute("sendrecv");
|
|
}
|
|
answer.session().addMedium(medium);
|
|
}
|
|
else
|
|
{
|
|
SdpContents::Session::Medium rejmedium(offerMediaLine.name(), 0, 1, // Reject medium by specifying port 0 (RFC3264)
|
|
offerMediaLine.protocol());
|
|
answer.session().addMedium(rejmedium);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SdpContents::Session::Medium rejmedium(offerMediaLine.name(), 0, 1, // Reject medium by specifying port 0 (RFC3264)
|
|
offerMediaLine.protocol());
|
|
answer.session().addMedium(rejmedium);
|
|
}
|
|
} // end loop through m= offers
|
|
if(!valid)
|
|
{
|
|
WarningLog( B2BLOG_PREFIX << "B2BSession::buildLocalAnswer - no matching codecs found in offer");
|
|
}
|
|
}
|
|
catch(BaseException &e)
|
|
{
|
|
WarningLog( B2BLOG_PREFIX << "B2BSession::buildLocalAnswer - exception parsing SDP offer: " << e.getMessage());
|
|
valid = false;
|
|
}
|
|
catch(...)
|
|
{
|
|
WarningLog(B2BLOG_PREFIX << "B2BSession::buildLocalAnswer - unknown exception parsing SDP offer");
|
|
valid = false;
|
|
}
|
|
|
|
//InfoLog( << "B2BSession::buildLocalAnswer - SDPOffer: " << offer);
|
|
//InfoLog( << "B2BSession::buildLocalAnswer - SDPAnswer: " << answer);
|
|
}
|
|
else
|
|
{
|
|
ErrLog(B2BLOG_PREFIX << "B2BSession::buildLocalAnswer - unable to build local answer, ishValid=" << mInviteSessionHandle.isValid() << ", hasProposedRemoteSdp=" << mInviteSessionHandle->hasProposedRemoteSdp());
|
|
}
|
|
return valid;
|
|
}
|
|
|
|
bool
|
|
B2BSession::provideLocalAnswer()
|
|
{
|
|
SdpContents answer;
|
|
resip_assert(mInviteSessionHandle.isValid());
|
|
bool answerOk = buildLocalAnswer(answer);
|
|
|
|
if(answerOk)
|
|
{
|
|
mInviteSessionHandle->provideAnswer(answer);
|
|
}
|
|
else
|
|
{
|
|
mInviteSessionHandle->reject(488);
|
|
}
|
|
|
|
return answerOk;
|
|
}
|
|
|
|
void
|
|
B2BSession::endPeer()
|
|
{
|
|
if(mPeer)
|
|
{
|
|
mPeer->setPeer(0);
|
|
if(mPeer->mIChatWaitingToContinue || mPeer->mIChatWaitingToProceed)
|
|
{
|
|
mPeer->cancelIChatCall();
|
|
delete mPeer;
|
|
}
|
|
else
|
|
{
|
|
mPeer->end(); // send cancel or bye appropriately
|
|
}
|
|
mPeer = 0;
|
|
}
|
|
}
|
|
|
|
bool
|
|
B2BSession::isUACConnected()
|
|
{
|
|
return !mUACConnectedDialogId.getCallId().empty();
|
|
}
|
|
|
|
bool
|
|
B2BSession::isStaleFork(const DialogId& dialogId)
|
|
{
|
|
return (!mUACConnectedDialogId.getCallId().empty() && dialogId != mUACConnectedDialogId);
|
|
}
|
|
|
|
void
|
|
B2BSession::fixupSdp(const SdpContents& origSdp, SdpContents& fixedSdp)
|
|
{
|
|
fixedSdp = origSdp;
|
|
|
|
if(fixedSdp.session().media().size() >= 1)
|
|
{
|
|
fixedSdp.session().media().front().clearCodecs();
|
|
const std::list<Codec>& origcodecs = origSdp.session().media().front().codecs();
|
|
std::list<Codec>::const_iterator it = origcodecs.begin();
|
|
for(; it != origcodecs.end(); it++)
|
|
{
|
|
if(mServer.mCodecIdFilterList.find(it->payloadType()) == mServer.mCodecIdFilterList.end())
|
|
{
|
|
fixedSdp.session().media().front().addCodec(*it);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SharedPtr<UserProfile>
|
|
B2BSession::selectUASUserProfile(const SipMessage& msg)
|
|
{
|
|
// If this is an iChat endpoint to force all requests in the session to go to the IP address and port
|
|
// that the initiatial invite was from
|
|
if(msg.exists(h_UserAgent) && msg.header(h_UserAgent).value().prefix("Viceroy"))
|
|
{
|
|
// Create a UserProfile for new call
|
|
SharedPtr<UserProfile> userProfile(new UserProfile(mServer.getMasterProfile()));
|
|
|
|
// Doesn't really matter what's in the SIP message from header that goes to iChat - it's never displayed to the client
|
|
userProfile->setDefaultFrom(mServer.getMasterProfile()->getDefaultFrom());
|
|
|
|
// Force endpoint routing
|
|
Data destinationData;
|
|
if(msg.getSource().ipVersion() == V6)
|
|
{
|
|
destinationData = "sip:[" + msg.getSource().presentationFormat() + "]:" + Data(msg.getSource().getPort());
|
|
}
|
|
else
|
|
{
|
|
destinationData = "sip:" + msg.getSource().presentationFormat() + ":" + Data(msg.getSource().getPort());
|
|
}
|
|
Uri destination(destinationData);
|
|
userProfile->setOutboundProxy(destination);
|
|
userProfile->setForceOutboundProxyOnAllRequestsEnabled(true);
|
|
return userProfile;
|
|
}
|
|
else
|
|
{
|
|
return mServer.getMasterProfile();
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// InviteSessionHandler ///////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void
|
|
B2BSession::onNewSession(ClientInviteSessionHandle h, InviteSession::OfferAnswerType oat, const SipMessage& msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onNewSession(ClientInviteSessionHandle): msg=" << LOG_MSG);
|
|
mInviteSessionHandle = h->getSessionHandle();
|
|
if(msg.exists(h_UserAgent))
|
|
{
|
|
if(msg.header(h_UserAgent).value().prefix("Viceroy"))
|
|
{
|
|
mIChatEndpoint = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::onNewSession(ServerInviteSessionHandle h, InviteSession::OfferAnswerType oat, const SipMessage& msg)
|
|
{
|
|
mInviteSessionHandle = h->getSessionHandle();
|
|
|
|
if(msg.exists(h_UserAgent))
|
|
{
|
|
if(msg.header(h_UserAgent).value().prefix("Viceroy"))
|
|
{
|
|
mIChatEndpoint = true;
|
|
}
|
|
}
|
|
|
|
// First check if this INVITE is to replace an existing session
|
|
if(msg.exists(h_Replaces))
|
|
{
|
|
pair<InviteSessionHandle, int> presult;
|
|
presult = mDum.findInviteSession(msg.header(h_Replaces));
|
|
if(!(presult.first == InviteSessionHandle::NotValid()))
|
|
{
|
|
B2BSession* sessionToReplace = dynamic_cast<B2BSession *>(presult.first->getAppDialogSet().get());
|
|
InfoLog(B2BLOG_PREFIX << "onNewSession(ServerInviteSessionHandle): to replace handle=" << sessionToReplace->getB2BSessionHandle() << ", msg=" << LOG_MSG);
|
|
|
|
// Assume Peer mapping of old call - and copy some settings
|
|
stealPeer(sessionToReplace);
|
|
|
|
// Session to replace was found - end old session
|
|
sessionToReplace->end();
|
|
return;
|
|
}
|
|
}
|
|
|
|
InfoLog(B2BLOG_PREFIX << "onNewSession(ServerInviteSessionHandle): msg=" << LOG_MSG);
|
|
|
|
// Note: remaining inbound call handling is done in onOffer and onOfferRequired
|
|
}
|
|
|
|
void
|
|
B2BSession::onFailure(ClientInviteSessionHandle h, const SipMessage& msg)
|
|
{
|
|
// Note: Teardown of peer is handled in destructor
|
|
InfoLog(B2BLOG_PREFIX << "onFailure: msg=" << LOG_MSG);
|
|
}
|
|
|
|
void
|
|
B2BSession::onEarlyMedia(ClientInviteSessionHandle h, const SipMessage& msg, const SdpContents& sdp)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onEarlyMedia: msg=" << LOG_MSG_WITH_SDP);
|
|
}
|
|
|
|
void
|
|
B2BSession::onProvisional(ClientInviteSessionHandle h, const SipMessage& msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onProvisional: msg=" << LOG_MSG);
|
|
|
|
if(isStaleFork(h->getDialogId())) return;
|
|
|
|
if(mPeer)
|
|
{
|
|
if(mPeer->mInviteSessionHandle.isValid())
|
|
{
|
|
ServerInviteSession* sis = dynamic_cast<ServerInviteSession*>(mPeer->mInviteSessionHandle.get());
|
|
if(sis && !sis->isAccepted())
|
|
{
|
|
sis->provisional(msg.header(h_StatusLine).responseCode());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::onConnected(ClientInviteSessionHandle h, const SipMessage& msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onConnected: msg=" << LOG_MSG);
|
|
if(!isUACConnected())
|
|
{
|
|
// It is possible in forking scenarios to get multiple 200 responses, if this is
|
|
// our first 200 response, then this is the leg we accept, store the connected DialogId
|
|
mUACConnectedDialogId = h->getDialogId();
|
|
// Note: each forked leg will update mInviteSessionHandle (in onNewSession call) - need to set mInviteSessionHandle for final answering leg on 200
|
|
mInviteSessionHandle = h->getSessionHandle();
|
|
}
|
|
else
|
|
{
|
|
// We already have a connected leg - end this one with a BYE
|
|
h->end();
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::onConnected(InviteSessionHandle h, const SipMessage& msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onConnected: msg=" << LOG_MSG);
|
|
}
|
|
|
|
void
|
|
B2BSession::onStaleCallTimeout(ClientInviteSessionHandle h)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onStaleCallTimeout:");
|
|
endPeer();
|
|
}
|
|
|
|
unsigned int
|
|
B2BSession::mapRejectionCodeForIChat(unsigned int statusCode)
|
|
{
|
|
// iChat appears to display the following error codes nicely
|
|
// 486 - Busy
|
|
// 487 - Cancelled
|
|
// 603 - Decline
|
|
// 606 - Security Error
|
|
switch(statusCode)
|
|
{
|
|
case 486:
|
|
case 600:
|
|
return 486;
|
|
case 487:
|
|
return 487;
|
|
default:
|
|
return 603;
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::onTerminated(InviteSessionHandle h, InviteSessionHandler::TerminatedReason reason, const SipMessage* msg)
|
|
{
|
|
Data reasonData;
|
|
switch(reason)
|
|
{
|
|
case InviteSessionHandler::RemoteBye:
|
|
reasonData = "received a BYE from peer";
|
|
break;
|
|
case InviteSessionHandler::RemoteCancel:
|
|
reasonData = "received a CANCEL from peer";
|
|
break;
|
|
case InviteSessionHandler::Rejected:
|
|
reasonData = "received a rejection from peer";
|
|
break;
|
|
case InviteSessionHandler::LocalBye:
|
|
reasonData = "ended locally via BYE";
|
|
break;
|
|
case InviteSessionHandler::LocalCancel:
|
|
reasonData = "ended locally via CANCEL";
|
|
break;
|
|
case InviteSessionHandler::Replaced:
|
|
reasonData = "ended due to being replaced";
|
|
break;
|
|
case InviteSessionHandler::Referred:
|
|
reasonData = "ended due to being reffered";
|
|
break;
|
|
case InviteSessionHandler::Error:
|
|
reasonData = "ended due to an error";
|
|
break;
|
|
case InviteSessionHandler::Timeout:
|
|
reasonData = "ended due to a timeout";
|
|
break;
|
|
default:
|
|
resip_assert(false);
|
|
break;
|
|
}
|
|
|
|
if(msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onTerminated: reason=" << reasonData << ", msg=" << LOG_MSGP);
|
|
}
|
|
else
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onTerminated: reason=" << reasonData);
|
|
}
|
|
|
|
unsigned int statusCode = 603;
|
|
if(msg)
|
|
{
|
|
if(msg->isResponse())
|
|
{
|
|
statusCode = msg->header(h_StatusLine).responseCode();
|
|
}
|
|
}
|
|
|
|
// If this is a referred call and the refer is still around - then switch back to referrer (ie. failed transfer recovery)
|
|
if(mReferringAppDialogSet.isValid() && mPeer)
|
|
{
|
|
B2BSession* session = (B2BSession*)mReferringAppDialogSet.get();
|
|
session->stealPeer(this);
|
|
}
|
|
|
|
if(isStaleFork(h->getDialogId())) return;
|
|
|
|
// If we have a peer that hasn't been accepted yet - then pass back terminated code, by calling reject now
|
|
if(mPeer && mPeer->mInviteSessionHandle.isValid())
|
|
{
|
|
ServerInviteSession* sis = dynamic_cast<ServerInviteSession*>(mPeer->mInviteSessionHandle.get());
|
|
if(sis && !sis->isAccepted())
|
|
{
|
|
if(mPeer->mIChatEndpoint)
|
|
{
|
|
statusCode = mapRejectionCodeForIChat(statusCode);
|
|
}
|
|
sis->reject(statusCode);
|
|
}
|
|
}
|
|
|
|
endPeer();
|
|
}
|
|
|
|
void
|
|
B2BSession::onRedirected(ClientInviteSessionHandle h, const SipMessage& msg)
|
|
{
|
|
// We will recurse on redirect requests, so nothing to do here
|
|
InfoLog(B2BLOG_PREFIX << "onRedirected: msg=" << LOG_MSG);
|
|
}
|
|
|
|
void
|
|
B2BSession::onAnswer(InviteSessionHandle h, const SipMessage& msg, const SdpContents& sdp)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onAnswer: msg=" << LOG_MSG_WITH_SDP);
|
|
|
|
if(isStaleFork(h->getDialogId())) return;
|
|
|
|
if(mPeer)
|
|
{
|
|
if(mPeer->mInviteSessionHandle.isValid())
|
|
{
|
|
if(mPeer->mWaitingAnswerFromPeer)
|
|
{
|
|
mPeer->mWaitingAnswerFromPeer = false;
|
|
if(mAnchorMedia)
|
|
{
|
|
// We need to answer with local sdp
|
|
mPeer->provideLocalAnswer();
|
|
}
|
|
else
|
|
{
|
|
mPeer->mInviteSessionHandle->provideAnswer(sdp);
|
|
}
|
|
}
|
|
else if(mPeer->mWaitingOfferFromPeer)
|
|
{
|
|
if(mAnchorMedia)
|
|
{
|
|
// We need to send a local sdp offer
|
|
SdpContents localOffer;
|
|
if(!buildLocalOffer(localOffer))
|
|
{
|
|
ErrLog(<< "B2BSession::onAnswer: unable to build local offer.");
|
|
endPeer();
|
|
end();
|
|
return;
|
|
}
|
|
mPeer->mInviteSessionHandle->provideOffer(localOffer);
|
|
}
|
|
else
|
|
{
|
|
mPeer->mInviteSessionHandle->provideOffer(sdp);
|
|
}
|
|
mPeer->mWaitingOfferFromPeer = false;
|
|
}
|
|
|
|
ServerInviteSession* sis = dynamic_cast<ServerInviteSession*>(mPeer->mInviteSessionHandle.get());
|
|
if(sis && !sis->isAccepted())
|
|
{
|
|
sis->accept();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(!mPeer->mHasDialogSet && mPeer->mIChatWaitingToAccept)
|
|
{
|
|
// We are in a call setup phase for iChat -> SIP - peer is just an empty dialogset
|
|
mPeer->acceptIChatCall();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::onOffer(InviteSessionHandle h, const SipMessage& msg, const SdpContents& sdp)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onOffer: msg=" << LOG_MSG_WITH_SDP);
|
|
|
|
if(isStaleFork(h->getDialogId())) return;
|
|
|
|
if(mPeer)
|
|
{
|
|
if(mAnchorMedia)
|
|
{
|
|
// We need to answer with local sdp
|
|
if(provideLocalAnswer())
|
|
{
|
|
ServerInviteSession* sis = dynamic_cast<ServerInviteSession*>(mInviteSessionHandle.get());
|
|
if(sis && !sis->isAccepted())
|
|
{
|
|
sis->accept();
|
|
}
|
|
}
|
|
}
|
|
else if(mPeer->mInviteSessionHandle.isValid())
|
|
{
|
|
if(mPeer->mWaitingOfferFromPeer)
|
|
{
|
|
SdpContents fixedUpOffer;
|
|
fixupSdp(sdp, fixedUpOffer);
|
|
mPeer->mInviteSessionHandle->provideOffer(fixedUpOffer);
|
|
mPeer->mWaitingOfferFromPeer = false;
|
|
mWaitingAnswerFromPeer = true;
|
|
}
|
|
else
|
|
{
|
|
h->provideAnswer(h->getLocalSdp());
|
|
/* Experimental - appears to server no purpose
|
|
if(sdp.session().media().front().exists("sendonly") ||
|
|
sdp.session().media().front().exists("inactive"))
|
|
{
|
|
PlainContents msgBody("VCRemoteMuted:ON");
|
|
mPeer->mInviteSessionHandle->message(msgBody);
|
|
}
|
|
else
|
|
{
|
|
PlainContents msgBody("VCRemoteMuted:OFF");
|
|
mPeer->mInviteSessionHandle->message(msgBody);
|
|
}*/
|
|
}
|
|
}
|
|
else
|
|
{
|
|
resip_assert(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we don't have a peer yet - we need to place a new call
|
|
SdpContents fixedUpOffer;
|
|
fixupSdp(sdp, fixedUpOffer);
|
|
|
|
bool result;
|
|
if(mIChatEndpoint)
|
|
{
|
|
try
|
|
{
|
|
// If we are an iChat endpoint then real To/From uri is actually in the display name of the To/From
|
|
// header.
|
|
Uri to(Data("xmpp:") + msg.header(h_To).displayName());
|
|
if(!msg.header(h_From).displayName().empty())
|
|
{
|
|
try
|
|
{
|
|
NameAddr from(Data("sip:" + msg.header(h_From).displayName()));
|
|
if(!from.uri().user().empty())
|
|
{
|
|
from.displayName() = from.uri().user();
|
|
}
|
|
result = createNewPeer(to, from, &fixedUpOffer);
|
|
}
|
|
catch(resip::BaseException&)
|
|
{
|
|
result = createNewPeer(to, msg.header(h_From), &fixedUpOffer);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result = createNewPeer(to, msg.header(h_From), &fixedUpOffer);
|
|
}
|
|
}
|
|
catch(resip::BaseException& e)
|
|
{
|
|
ErrLog(B2BLOG_PREFIX << "Error extracting real URI from iChat To header display name=" << msg.header(h_To) << ", error=" << e);
|
|
result = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result = createNewPeer(msg.header(h_To).uri(), msg.header(h_From),&fixedUpOffer);
|
|
}
|
|
if(!result)
|
|
{
|
|
ErrLog(B2BLOG_PREFIX << "Failed to create new peer, rejecting call with 404.");
|
|
h->reject(404);
|
|
}
|
|
else
|
|
{
|
|
mWaitingAnswerFromPeer = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::onOfferRequired(InviteSessionHandle h, const SipMessage& msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onOfferRequired: msg=" << LOG_MSG);
|
|
|
|
if(isStaleFork(h->getDialogId())) return;
|
|
|
|
if(mPeer)
|
|
{
|
|
if(mAnchorMedia)
|
|
{
|
|
// We need to offer with local sdp
|
|
SdpContents sdp;
|
|
if(!buildLocalOffer(sdp))
|
|
{
|
|
ErrLog(<< "B2BSession::onOfferRequired: unable to build local offer.");
|
|
endPeer();
|
|
end();
|
|
return;
|
|
}
|
|
h->provideOffer(sdp);
|
|
}
|
|
else if(mPeer->mInviteSessionHandle.isValid())
|
|
{
|
|
mPeer->mInviteSessionHandle->requestOffer();
|
|
mWaitingOfferFromPeer = true;
|
|
}
|
|
else
|
|
{
|
|
resip_assert(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we don't have a peer yet - we need to place a new call
|
|
if(!createNewPeer(msg.header(h_To).uri(), msg.header(h_From),0))
|
|
{
|
|
ErrLog(B2BLOG_PREFIX << "Failed to create new peer, rejecting call with 404.");
|
|
h->reject(404);
|
|
}
|
|
else
|
|
{
|
|
mWaitingOfferFromPeer = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::onOfferRejected(InviteSessionHandle h, const SipMessage* msg)
|
|
{
|
|
int statusCode = 488;
|
|
WarningCategory* warning=0;
|
|
|
|
if(isStaleFork(h->getDialogId())) return;
|
|
|
|
if(msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onOfferRejected: msg=" << LOG_MSGP);
|
|
if(msg->exists(h_Warnings))
|
|
{
|
|
warning = (WarningCategory*)&msg->header(h_Warnings).back();
|
|
}
|
|
if(msg->isResponse())
|
|
{
|
|
statusCode = msg->header(h_StatusLine).responseCode();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onOfferRejected:");
|
|
}
|
|
|
|
if(mPeer)
|
|
{
|
|
if(mPeer->mInviteSessionHandle.isValid() && mPeer->mWaitingAnswerFromPeer)
|
|
{
|
|
mPeer->mInviteSessionHandle->reject(statusCode, warning);
|
|
mPeer->mWaitingAnswerFromPeer = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::onOfferRequestRejected(InviteSessionHandle h, const SipMessage& msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onOfferRequestRejected: msg=" << LOG_MSG);
|
|
// This is called when we are waiting to resend a INVITE with no sdp after a glare condition, and we
|
|
// instead receive an inbound INVITE or UPDATE
|
|
if(mPeer)
|
|
{
|
|
if(mPeer->mInviteSessionHandle.isValid() && mPeer->mWaitingOfferFromPeer)
|
|
{
|
|
// Return glare to peer
|
|
mPeer->mInviteSessionHandle->reject(491);
|
|
mPeer->mWaitingOfferFromPeer = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::onRemoteSdpChanged(InviteSessionHandle h, const SipMessage& msg, const SdpContents& sdp)
|
|
{
|
|
/// called when a modified SDP is received in a 2xx response to a
|
|
/// session-timer reINVITE. Under normal circumstances where the response
|
|
/// SDP is unchanged from current remote SDP no handler is called
|
|
/// There is not much we can do about this. If session timers are used then they are managed seperately per leg
|
|
/// and we have no real mechanism to notify the other peer of new SDP without starting a new offer/answer negotiation
|
|
InfoLog(B2BLOG_PREFIX << "onRemoteSdpChanged: msg=" << LOG_MSG_WITH_SDP);
|
|
}
|
|
|
|
void
|
|
B2BSession::onInfo(InviteSessionHandle h, const SipMessage& msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onInfo: msg=" << LOG_MSG);
|
|
if(mPeer)
|
|
{
|
|
if(mPeer->mInviteSessionHandle.isValid() && msg.getContents())
|
|
{
|
|
mPeer->mInviteSessionHandle->info(*msg.getContents());
|
|
mWaitingNitAnswerFromPeer = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
h->acceptNIT();
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::onInfoSuccess(InviteSessionHandle h, const SipMessage& msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onInfoSuccess: msg=" << LOG_MSG);
|
|
if(mPeer)
|
|
{
|
|
if(mPeer->mInviteSessionHandle.isValid() && mPeer->mWaitingNitAnswerFromPeer)
|
|
{
|
|
mPeer->mInviteSessionHandle->acceptNIT(msg.header(h_StatusLine).responseCode(), msg.getContents());
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::onInfoFailure(InviteSessionHandle h, const SipMessage& msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onInfoFailure: msg=" << LOG_MSG);
|
|
if(mPeer)
|
|
{
|
|
if(mPeer->mInviteSessionHandle.isValid() && mPeer->mWaitingNitAnswerFromPeer)
|
|
{
|
|
mPeer->mInviteSessionHandle->rejectNIT(msg.header(h_StatusLine).responseCode());
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::onRefer(InviteSessionHandle h, ServerSubscriptionHandle ss, const SipMessage& msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onRefer: msg=" << LOG_MSG);
|
|
|
|
// If no peer (for some reason), then reject request
|
|
if(!mPeer)
|
|
{
|
|
ss->send(ss->reject(403));
|
|
return;
|
|
}
|
|
|
|
// Recurse on the REFER - do not pass to other leg
|
|
try
|
|
{
|
|
// Accept the Refer
|
|
ss->send(ss->accept(202 /* Refer Accepted */));
|
|
|
|
B2BSession* newPeer = new B2BSession(mServer);
|
|
|
|
SdpContents *pOffer = 0;
|
|
SdpContents offer;
|
|
if(mAnchorMedia)
|
|
{
|
|
buildLocalOffer(offer);
|
|
pOffer = &offer;
|
|
}
|
|
else
|
|
{
|
|
mPeer->mWaitingOfferFromPeer = true;
|
|
}
|
|
|
|
// Map other leg to this new one
|
|
newPeer->stealPeer(this);
|
|
newPeer->mReferringAppDialogSet = getHandle();
|
|
|
|
SharedPtr<SipMessage> invitemsg = mDum.makeInviteSessionFromRefer(msg, ss->getHandle(), pOffer, newPeer);
|
|
mDum.send(invitemsg);
|
|
}
|
|
catch(BaseException &e)
|
|
{
|
|
WarningLog(B2BLOG_PREFIX << "onRefer exception: " << e);
|
|
}
|
|
catch(...)
|
|
{
|
|
WarningLog(B2BLOG_PREFIX << "onRefer unknown exception");
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::onReferAccepted(InviteSessionHandle h, ClientSubscriptionHandle csh, const SipMessage& msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onReferAccepted: msg=" << LOG_MSG);
|
|
end(); // Click-to-call refer request was accepted - end our call with the initiator
|
|
}
|
|
|
|
void
|
|
B2BSession::onReferRejected(InviteSessionHandle h, const SipMessage& msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onReferRejected: msg=" << LOG_MSG);
|
|
|
|
// Endpoint doesn't support REFER - Fallback to anchoring
|
|
/*
|
|
mPeer = new B2BSession(mServer);
|
|
mPeer->setPeer(this);
|
|
mPeer->startClickToCallAnchorLeg(msg.header(h_From), mClickToCallDestination, mMediaRelayPort);
|
|
if(mMediaRelayPort == 0)
|
|
{
|
|
mWaitingOfferFromPeer = true;
|
|
}*/
|
|
}
|
|
|
|
bool
|
|
B2BSession::doReferNoSub(const SipMessage& msg)
|
|
{
|
|
// If no peer (for some reason), then return false
|
|
if(!mPeer)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
B2BSession* newPeer = new B2BSession(mServer);
|
|
|
|
|
|
SdpContents *pOffer = 0;
|
|
SdpContents offer;
|
|
if(mAnchorMedia)
|
|
{
|
|
buildLocalOffer(offer);
|
|
pOffer = &offer;
|
|
}
|
|
else
|
|
{
|
|
mPeer->mWaitingOfferFromPeer = true;
|
|
}
|
|
|
|
// Map other leg to this new one
|
|
newPeer->stealPeer(this);
|
|
newPeer->mReferringAppDialogSet = getHandle();
|
|
|
|
// Build the Invite
|
|
SharedPtr<SipMessage> invitemsg = mDum.makeInviteSessionFromRefer(msg, getUserProfile(), pOffer, newPeer);
|
|
mDum.send(invitemsg);
|
|
return true;
|
|
}
|
|
|
|
void
|
|
B2BSession::onReferNoSub(InviteSessionHandle h, const SipMessage& msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onReferNoSub: msg=" << LOG_MSG);
|
|
|
|
// If no peer (for some reason), then reject request
|
|
if(!mPeer)
|
|
{
|
|
h->rejectReferNoSub(403);
|
|
return;
|
|
}
|
|
|
|
// Accept the Refer
|
|
h->acceptReferNoSub(202 /* Refer Accepted */);
|
|
|
|
doReferNoSub(msg);
|
|
}
|
|
|
|
void
|
|
B2BSession::onMessage(InviteSessionHandle h, const SipMessage& msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onMessage: msg=" << LOG_MSG);
|
|
if(mPeer && !mIChatEndpoint) // Note: If iChat endpoint, then just respond to message, since iChat sends PING MESSAGES periodically and most phones don't respond
|
|
{
|
|
if(mPeer->mInviteSessionHandle.isValid() && msg.getContents())
|
|
{
|
|
mPeer->mInviteSessionHandle->message(*msg.getContents());
|
|
mWaitingNitAnswerFromPeer = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
h->acceptNIT();
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::onMessageSuccess(InviteSessionHandle h, const SipMessage& msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onMessageSuccess: msg=" << LOG_MSG);
|
|
if(mPeer)
|
|
{
|
|
if(mPeer->mInviteSessionHandle.isValid() && mPeer->mWaitingNitAnswerFromPeer)
|
|
{
|
|
mPeer->mInviteSessionHandle->acceptNIT(msg.header(h_StatusLine).responseCode(), msg.getContents());
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::onMessageFailure(InviteSessionHandle h, const SipMessage& msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onMessageFailure: msg=" << LOG_MSG);
|
|
if(mPeer)
|
|
{
|
|
if(mPeer->mInviteSessionHandle.isValid() && mPeer->mWaitingNitAnswerFromPeer)
|
|
{
|
|
mPeer->mInviteSessionHandle->rejectNIT(msg.header(h_StatusLine).responseCode());
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::onForkDestroyed(ClientInviteSessionHandle h)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onForkDestroyed:");
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// DialogSetHandler ///////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void
|
|
B2BSession::onTrying(AppDialogSetHandle h, const SipMessage& msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onTrying: msg=" << LOG_MSG);
|
|
}
|
|
|
|
void
|
|
B2BSession::onNonDialogCreatingProvisional(AppDialogSetHandle h, const SipMessage& msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onNonDialogCreatingProvisional: msg=" << LOG_MSG);
|
|
|
|
if(isUACConnected()) return;
|
|
|
|
if(mPeer)
|
|
{
|
|
if(mPeer->mInviteSessionHandle.isValid())
|
|
{
|
|
ServerInviteSession* sis = dynamic_cast<ServerInviteSession*>(mPeer->mInviteSessionHandle.get());
|
|
if(sis && !sis->isAccepted())
|
|
{
|
|
sis->provisional(msg.header(h_StatusLine).responseCode());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// ClientSubscriptionHandler ///////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void
|
|
B2BSession::onUpdatePending(ClientSubscriptionHandle h, const SipMessage& msg, bool outOfOrder)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onUpdatePending(ClientSubscriptionHandle): " << LOG_MSG);
|
|
if (msg.exists(h_Event) && msg.header(h_Event).value() == "refer")
|
|
{
|
|
h->acceptUpdate();
|
|
}
|
|
else
|
|
{
|
|
h->rejectUpdate(400, Data("Only notifies for refers are allowed."));
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::onUpdateActive(ClientSubscriptionHandle h, const SipMessage& msg, bool outOfOrder)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onUpdateActive(ClientSubscriptionHandle): " << LOG_MSG);
|
|
if (msg.exists(h_Event) && msg.header(h_Event).value() == "refer")
|
|
{
|
|
h->acceptUpdate();
|
|
}
|
|
else
|
|
{
|
|
h->rejectUpdate(400, Data("Only notifies for refers are allowed."));
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::onUpdateExtension(ClientSubscriptionHandle h, const SipMessage& msg, bool outOfOrder)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onUpdateExtension(ClientSubscriptionHandle): " << LOG_MSG);
|
|
if (msg.exists(h_Event) && msg.header(h_Event).value() == "refer")
|
|
{
|
|
h->acceptUpdate();
|
|
}
|
|
else
|
|
{
|
|
h->rejectUpdate(400, Data("Only notifies for refers are allowed."));
|
|
}
|
|
}
|
|
|
|
void
|
|
B2BSession::onTerminated(ClientSubscriptionHandle h, const SipMessage* msg)
|
|
{
|
|
if(msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onTerminated(ClientSubscriptionHandle): " << LOG_MSGP);
|
|
}
|
|
else
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onTerminated(ClientSubscriptionHandle)");
|
|
}
|
|
// Note: Final notify is sometimes only passed in the onTerminated callback
|
|
//if (notify.isRequest() && notify.exists(h_Event) && notify.header(h_Event).value() == "refer")
|
|
//{
|
|
//}
|
|
}
|
|
|
|
void
|
|
B2BSession::onNewSubscription(ClientSubscriptionHandle h, const SipMessage& msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onNewSubscription(ClientSubscriptionHandle): " << LOG_MSG);
|
|
}
|
|
|
|
int
|
|
B2BSession::onRequestRetry(ClientSubscriptionHandle h, int retryMinimum, const SipMessage& msg)
|
|
{
|
|
InfoLog(B2BLOG_PREFIX << "onRequestRetry(ClientSubscriptionHandle): " << LOG_MSG);
|
|
return -1;
|
|
}
|
|
|
|
}
|
|
|
|
/* ====================================================================
|
|
|
|
Copyright (c) 2009, SIP Spectrum, Inc.
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are
|
|
met:
|
|
|
|
1. Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
|
|
2. Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in the
|
|
documentation and/or other materials provided with the distribution.
|
|
|
|
3. Neither the name of SIP Spectrum nor the names of its contributors
|
|
may be used to endorse or promote products derived from this
|
|
software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
==================================================================== */
|
|
|