diff --git a/apps/sbc/CallLeg.cpp b/apps/sbc/CallLeg.cpp index 2a700af0..7479fc6d 100644 --- a/apps/sbc/CallLeg.cpp +++ b/apps/sbc/CallLeg.cpp @@ -240,7 +240,8 @@ CallLeg::CallLeg(const CallLeg* caller, AmSipDialog* p_dlg, AmSipSubscription* p : AmB2BSession(caller->getLocalTag(),p_dlg,p_subs), call_status(Disconnected), on_hold(false), - hold(PreserveHoldStatus) + hold(PreserveHoldStatus), + hold_method_requested(NonHold) { a_leg = !caller->a_leg; // we have to be the complement @@ -300,7 +301,8 @@ CallLeg::CallLeg(AmSipDialog* p_dlg, AmSipSubscription* p_subs) : AmB2BSession("",p_dlg,p_subs), call_status(Disconnected), on_hold(false), - hold(PreserveHoldStatus) + hold(PreserveHoldStatus), + hold_method_requested(NonHold) { a_leg = true; @@ -1126,7 +1128,22 @@ void CallLeg::onSipRequest(const AmSipRequest& req) * to avoid other confusions... */ dlg->reply(req, 200, "OK"); - } else { + } + else + { + /** only for requests which put the call on hold. + * Remember that we have to answer to the one, who puts on hold, + * with the 'inactive' back (as soon as the on hold is accepted with the 200OK + * by the other side of the call) in case the on hold was requested using 'inactive' + */ + AmSdp sdp; + hm hold_method; + if (req.method == SIP_METH_INVITE && retrieveAmSdp(req.body, sdp)) { + /* case when remote side puts us on hold */ + if (isOnHoldRequested(sdp, hold_method)) updateHoldMethod(hold_method); + /* it's likely sendrecv - then make sure 'hold_method_requested' is kept updated */ + else hold_method_requested = NonHold; + } AmB2BSession::onSipRequest(req); } } @@ -1397,6 +1414,41 @@ void CallLeg::updateCallStatus(CallStatus new_status, const StatusChangeCause &c onCallStatusChange(cause); } +void CallLeg::updateHoldMethod(const hm &hm) +{ + switch (hm) + { + case SendonlyStream: hold_method_requested = SendonlyHold; break; + case InactiveStream: hold_method_requested = InactiveHold; break; + case ZeroedConnection: hold_method_requested = ZeroedHold; break; + default: hold_method_requested = NonHold; + } + DBG("hold_method_requested is set to: <%d> for LT <%s>\n", + hold_method_requested, getLocalTag().c_str()); +} +bool CallLeg::isOnHoldRequested(const AmSdp &sdp, hm &method) +{ + if (isHoldRequest(sdp, (HoldMethod&)method)) { + DBG("This request puts the call on hold\n"); + return true; + } + return false; +} +bool CallLeg::retrieveAmSdp(const AmMimeBody &mSdp, AmSdp &sdp) +{ + AmMimeBody t_sdp_body = mSdp; + AmMimeBody * sdp_body = t_sdp_body.hasContentType(SIP_APPLICATION_SDP); + if (!sdp_body) { + DBG("Failed to parse SDP body while retrieving into AmSdp!\n"); + return false; + } + if (sdp.parse((const char *)sdp_body->getPayload())) { + DBG("Failed to parse SDP body while retrieving into AmSdp!\n"); + return false; + } + return true; /* parsed successfully */ +} + void CallLeg::addExistingCallee(const string &session_tag, ReconnectLegEvent *ev) { // add existing session as our B leg @@ -1794,11 +1846,40 @@ void CallLeg::updateLocalSdp(AmSdp &sdp) adjustOffer(sdp); } - if (hold == PreserveHoldStatus && !on_hold) { - // store non-hold SDP to be able to resumeHeld - non_hold_sdp = sdp; + /** make sure to answer with the 'sendonly' / 'inactive' to the one who previously + * requested the on-hold, in order to meet RFC requirements: + * - 'sendonly' must be faced with the 'recvonly' sent back as an answer + * - 'inactive' must be faced with the 'inactive' sent back as an answer + * This block is only needed for cases, when MoH is emulated on the SEMS directly. + */ + else if (hold == PreserveHoldStatus && hold_method_requested != NonHold) + { + for (std::vector::iterator m = sdp.media.begin(); + m != sdp.media.end(); ++m) + { + if (m->isAudio()) { + switch(hold_method_requested) + { + case SendonlyHold: + m->send = false; /* make sure to answer with the recvonly, if the on hold */ + m->recv = true; /* was perviously requested using a=sendonly */ + DBG("On hold has been previously requested and must be held further\n"); + DBG("This SDP is prepared to be sent with the a=recvonly\n"); + break; + case InactiveHold: + m->send = false; /* make sure to answer with the inactive, if the on hold */ + m->recv = false; /* was perviously requested using a=inactive */ + DBG("On hold has been previously requested and must be held further\n"); + DBG("This SDP is prepared to be sent with the a=inactive\n"); + break; + } + } + } } + /* store non-hold SDP to be able to resumeHeld */ + if (hold == PreserveHoldStatus && !on_hold) non_hold_sdp = sdp; + AmB2BSession::updateLocalSdp(sdp); } diff --git a/apps/sbc/CallLeg.h b/apps/sbc/CallLeg.h index 5711b86b..aa101d17 100644 --- a/apps/sbc/CallLeg.h +++ b/apps/sbc/CallLeg.h @@ -142,6 +142,7 @@ class CallLeg: public AmB2BSession bool on_hold; // remote is on hold AmSdp non_hold_sdp; enum { HoldRequested, ResumeRequested, PreserveHoldStatus } hold; + enum { NonHold, InactiveHold, SendonlyHold, ZeroedHold } hold_method_requested; // queue of session update operations, first element is possibly the one // being in progress @@ -189,6 +190,24 @@ class CallLeg: public AmB2BSession void updateCallStatus(CallStatus new_status, const StatusChangeCause &cause = StatusChangeCause()); + /** TODO: bring 'enum HoldMethod' from the .cpp file inside the CallLeg class definition. + * Currently this is a rude implementation which creates obstacles, + * and forces us to have a duplicate (hm) here in the class, when we want to have it as param. + * Additionally we have to constantly cast HoldMethod -> hm while working with them. + */ + enum hm { SendonlyStream, InactiveStream, ZeroedConnection }; + /** keep the method, which was used to put the call + * on hold, in the SDP offer received (most likely in re-INVITE) + */ + void updateHoldMethod(const hm &hm); + /* check if this request assumes call to be put on hold, + * this in internal class'es implementation. + * hm - hold method (SendonlyStream, InactiveStream, ZeroedConnection) + */ + bool isOnHoldRequested(const AmSdp &sdp, hm &hm); + /* set AmSdp based on AmMimeBody */ + bool retrieveAmSdp(const AmMimeBody &mSdp, AmSdp &sdp); + ////////////////////////////////////////////////////////////////////// // callbacks (intended to be redefined in successors but should not be // called by them directly)