From 3b94b6d14b1e490e7291129e7635d002f831adf7 Mon Sep 17 00:00:00 2001 From: Donat Zenichev Date: Fri, 25 Jul 2025 13:02:34 +0200 Subject: [PATCH] MT#59962 Aling the AmOfferAnswer model Make the AmOfferAnswer model actual and introduce according class API updates. Accordingly update CallLeg, SBCCallLeg and AmSipDialog. Change-Id: Iab26f4a7577af56df68ffe693859eaac08913b7f --- apps/sbc/CallLeg.cpp | 6 +- apps/sbc/ExtendedCCInterface.h | 4 + apps/sbc/SBCCallLeg.cpp | 44 ++++++++ apps/sbc/SBCCallLeg.h | 4 + core/Am100rel.cpp | 10 ++ core/Am100rel.h | 2 + core/AmOfferAnswer.cpp | 200 ++++++++++++++++++++------------- core/AmOfferAnswer.h | 15 ++- core/AmSession.h | 3 + core/AmSipDialog.cpp | 12 ++ core/AmSipDialog.h | 12 ++ 11 files changed, 230 insertions(+), 82 deletions(-) diff --git a/apps/sbc/CallLeg.cpp b/apps/sbc/CallLeg.cpp index 5bdcb6e7..de7ab7b0 100644 --- a/apps/sbc/CallLeg.cpp +++ b/apps/sbc/CallLeg.cpp @@ -529,7 +529,9 @@ bool CallLeg::setOther(const string &id, bool forward) setOtherId(id); clearRtpReceiverRelay(); // release old media session if set setMediaSession(i->media_session); - if (forward && dlg->getOAState() == AmOfferAnswer::OA_Completed) { + if (forward + && ((dlg->getOAState() == AmOfferAnswer::OA_Completed) + || ((dlg->getOAState() == AmOfferAnswer::OA_PreviewCompleted)))) { // reset OA state to offer_recived if already completed to accept new // B leg's SDP dlg->setOAState(AmOfferAnswer::OA_OfferRecved); @@ -1652,6 +1654,7 @@ void CallLeg::changeRtpMode(RTPRelayMode new_mode) switch (dlg->getOAState()) { case AmOfferAnswer::OA_Completed: + case AmOfferAnswer::OA_PreviewCompleted: case AmOfferAnswer::OA_None: // must be followed by OA exchange because we can't updateLocalSdp // (reINVITE would be needed) @@ -1708,6 +1711,7 @@ void CallLeg::changeRtpMode(RTPRelayMode new_mode, AmB2BMedia *new_media) switch (dlg->getOAState()) { case AmOfferAnswer::OA_Completed: + case AmOfferAnswer::OA_PreviewCompleted: case AmOfferAnswer::OA_None: // must be followed by OA exchange because we can't updateLocalSdp // (reINVITE would be needed) diff --git a/apps/sbc/ExtendedCCInterface.h b/apps/sbc/ExtendedCCInterface.h index 6893cd3f..e995b320 100644 --- a/apps/sbc/ExtendedCCInterface.h +++ b/apps/sbc/ExtendedCCInterface.h @@ -61,6 +61,10 @@ class ExtendedCCInterface virtual CCChainProcessing onInDialogReply(SBCCallLeg *call, const AmSipReply &reply) { return ContinueProcessing; } + /** called for O/A negotiaions */ + virtual CCChainProcessing onSdpCompleted(SBCCallLeg *call, const AmSdp &local, const AmSdp &remote) { return ContinueProcessing; } + virtual CCChainProcessing onSdpReceived(SBCCallLeg *call, const AmSdp &sdp, bool is_offer) { return ContinueProcessing; } + /** called before any other processing for the event is done */ virtual CCChainProcessing onEvent(SBCCallLeg *call, AmEvent *e) { return ContinueProcessing; } diff --git a/apps/sbc/SBCCallLeg.cpp b/apps/sbc/SBCCallLeg.cpp index 6e372fd8..324aebfd 100644 --- a/apps/sbc/SBCCallLeg.cpp +++ b/apps/sbc/SBCCallLeg.cpp @@ -1040,6 +1040,41 @@ void SBCCallLeg::onCallConnected(const AmSipReply& reply) { } } +int SBCCallLeg::onSdpCompleted(const AmSdp& local, const AmSdp& remote) { + + for (vector::iterator i = cc_ext.begin(); + i != cc_ext.end(); ++i) { + + try { + if ((*i)->onSdpCompleted(this, local, remote) == StopProcessing) return -1; + } catch (const std::exception &e) { + ILOG_DLG(L_ERR, "exception \"%s\" executing extended call conltrol 'onSdpCompleted' ", + e.what()); + stopCall(StatusChangeCause::InternalError); + return -1; + } + } + + return CallLeg::onSdpCompleted(local, remote); +} + +void SBCCallLeg::onSdpReceived(const AmSdp& sdp, bool is_offer) { + + for (vector::iterator i = cc_ext.begin(); + i != cc_ext.end(); ++i) { + + try { + if ((*i)->onSdpReceived(this, sdp, is_offer) == StopProcessing) + return; + } catch (const std::exception &e) { + ILOG_DLG(L_ERR, "exception \"%s\" executing extend call conltrol 'onSdpReceived' ", + e.what()); + stopCall(StatusChangeCause::InternalError); + return; + } + } +} + void SBCCallLeg::onStop() { if (call_profile.cc_interfaces.size()) { gettimeofday(&call_end_ts, NULL); @@ -1492,6 +1527,15 @@ int SBCCallLeg::filterSdp(AmMimeBody &body, const string &method) } } + // SDP answer + if((dlg->getOAState() == AmOfferAnswer::OA_Completed) + || (dlg->getOAState() == AmOfferAnswer::OA_PreviewCompleted)) { + if (call_profile.transcoder.isActive()) { + appendTranscoderCodecs(sdp); + changed = true; + } + } + // It doesn't make sense to filter out codecs allowed for transcoding and thus // if the filter filters them out it can be considered as configuration // problem, right? diff --git a/apps/sbc/SBCCallLeg.h b/apps/sbc/SBCCallLeg.h index a90e0c18..cb4b1c6d 100644 --- a/apps/sbc/SBCCallLeg.h +++ b/apps/sbc/SBCCallLeg.h @@ -217,6 +217,10 @@ class SBCCallLeg : public CallLeg, public CredentialHolder void setOtherId(const AmSipReply& reply); void setOtherId(const string& n_other_id) { CallLeg::setOtherId(n_other_id); } + /** OA callback **/ + virtual int onSdpCompleted(const AmSdp& local, const AmSdp& remote); + virtual void onSdpReceived(const AmSdp& sdp, bool is_offer); + void onSipReply(const AmSipRequest& req, const AmSipReply& reply, AmSipDialog::Status old_dlg_status); void onSendRequest(AmSipRequest& req, int &flags); diff --git a/core/Am100rel.cpp b/core/Am100rel.cpp index cbc1a206..a194605f 100644 --- a/core/Am100rel.cpp +++ b/core/Am100rel.cpp @@ -227,4 +227,14 @@ void Am100rel::onTimeout(const AmSipRequest& req, const AmSipReply& rpl) } } +bool Am100rel::willBeReliable(const AmSipReply& reply) +{ + if ((reply.cseq_method == SIP_METH_INVITE) + && (100 < reply.code && reply.code < 200) + && (key_in_list(getHeader(reply.hdrs, SIP_HDR_REQUIRE), SIP_EXT_100REL) + || (reliable_1xx == REL100_REQUIRE))) { + return true; + } + return false; +} diff --git a/core/Am100rel.h b/core/Am100rel.h index 7e9e6f3d..889a3f17 100644 --- a/core/Am100rel.h +++ b/core/Am100rel.h @@ -42,6 +42,8 @@ public: void onReplyOut(AmSipReply& reply); void onTimeout(const AmSipRequest& req, const AmSipReply& rpl); + + bool willBeReliable(const AmSipReply& reply); }; #endif diff --git a/core/AmOfferAnswer.cpp b/core/AmOfferAnswer.cpp index 8f5f68ac..28797b29 100644 --- a/core/AmOfferAnswer.cpp +++ b/core/AmOfferAnswer.cpp @@ -63,7 +63,7 @@ AmOfferAnswer::AmOfferAnswer(AmSipDialog* dlg) } -AmOfferAnswer::OAState AmOfferAnswer::getState() +AmOfferAnswer::OAState AmOfferAnswer::getState() const { return state; } @@ -75,12 +75,12 @@ void AmOfferAnswer::setState(AmOfferAnswer::OAState n_st) state = n_st; } -const AmSdp& AmOfferAnswer::getLocalSdp() +const AmSdp& AmOfferAnswer::getLocalSdp() const { return sdp_local; } -const AmSdp& AmOfferAnswer::getRemoteSdp() +const AmSdp& AmOfferAnswer::getRemoteSdp() const { return sdp_remote; } @@ -95,8 +95,15 @@ int AmOfferAnswer::checkStateChange() { int ret = 0; - if((saved_state != state) && - (state == OA_Completed)) { + // onSdpComplete is called when: + // - state transitions to Complete or PreviewCompleted + // - but not from PreviewCompleted to Complete, which should be ignored + // + if((saved_state != state) + && ((state == OA_Completed) || + (state == OA_PreviewCompleted)) + && !((saved_state == OA_PreviewCompleted) + && (state == OA_Completed))) { ret = dlg->onSdpCompleted(); } @@ -108,6 +115,7 @@ void AmOfferAnswer::clear() { setState(OA_None); cseq = 0; + remote_tag.clear(); sdp_remote.clear(); sdp_local.clear(); } @@ -136,8 +144,9 @@ int AmOfferAnswer::onRequestIn(const AmSipRequest& req) const AmMimeBody * csta_body = req.body.hasContentType(SIP_APPLICATION_CSTA_XML); /* if application/sdp present */ - if(sdp_body) { - err_code = onRxSdp(req.cseq,*sdp_body,&err_txt); + if (sdp_body) { + err_code = onRxSdp(req.cseq,req.from_tag,true,*sdp_body,&err_txt); + /* if both application/sdp and application/csta+xml are not present */ } else if (!csta_body) { err_code = 400; @@ -187,32 +196,44 @@ int AmOfferAnswer::onReplyIn(const AmSipReply& reply) if (sdp_body || csta_body) { - if (((state == OA_Completed) || - (state == OA_OfferRecved)) && - (reply.cseq == cseq)) + if ((reply.cseq_method == SIP_METH_INVITE) && + ((state == OA_Completed) + || (state == OA_PreviewCompleted) + || (state == OA_OfferRecved)) && + (reply.cseq == cseq) && + (reply.to_tag != remote_tag)) { - ILOG_DLG(L_DBG, "ignoring subsequent SDP reply within the same transaction\n"); - ILOG_DLG(L_DBG, "this usually happens when 183 and 200 have SDP\n"); + /* hack to handle 2xx with different tag than was in 183: accept the + * new SDP though we should accept the first one we received (without + * fork) */ + ILOG_DLG(L_DBG, "overwriting SDP remembered within the same transaction\n"); + + if (sdp_remote.parse((const char*)sdp_body->getPayload())){ + err_code = 400; + err_txt = "session description parsing failed"; + } else if(sdp_remote.media.empty()){ + err_code = 400; + err_txt = "no media line found in SDP message"; + } - /* Make sure that session is started when 200 OK is received */ - if (reply.code == 200) dlg->onSdpCompleted(); + if (err_code != 0) { + sdp_remote.clear(); + } else if(state != OA_OfferRecved) { + dlg->onSdpReceived(false); + dlg->onSdpCompleted(); + } else { + dlg->onSdpReceived(true); + } } else { + bool is_reliable = reply.code >= 200 || key_in_list(getHeader(reply.hdrs, SIP_HDR_REQUIRE), SIP_EXT_100REL); saveState(); - err_code = onRxSdp(reply.cseq,reply.body,&err_txt); + err_code = onRxSdp(reply.cseq,reply.to_tag,is_reliable, *sdp_body,&err_txt); checkStateChange(); } } } - if ((reply.code >= 300) && - (reply.cseq == cseq)) - { - /* final error reply -> cleanup OA state */ - ILOG_DLG(L_DBG, "after %u reply to %s: resetting OA state\n", reply.code, reply.cseq_method.c_str()); - clearTransitionalState(); - } - if (err_code) { /* TODO: only if initial INVITE (if re-INV, app should decide) */ ILOG_DLG(L_DBG, "error %i (%s) with SDP received in %i reply: sending ACK+BYE\n", err_code, err_txt ? err_txt : "none", reply.code); @@ -222,7 +243,9 @@ int AmOfferAnswer::onReplyIn(const AmSipReply& reply) return 0; } -int AmOfferAnswer::onRxSdp(unsigned int m_cseq, const AmMimeBody& body, const char** err_txt) +int AmOfferAnswer::onRxSdp(unsigned int m_cseq, const string& m_remote_tag, + bool is_reliable, const AmMimeBody& body, + const char** err_txt) { ILOG_DLG(L_DBG, "entering onRxSdp(), oa_state=%s\n", getOAStateStr(state)); OAState old_oa_state = state; @@ -234,16 +257,15 @@ int AmOfferAnswer::onRxSdp(unsigned int m_cseq, const AmMimeBody& body, const ch const AmMimeBody *sdp_body = body.hasContentType(SIP_APPLICATION_SDP); const AmMimeBody *csta_body = body.hasContentType(SIP_APPLICATION_CSTA_XML); - if (sdp_body == NULL) { + if (sdp_body == NULL && csta_body == NULL) { err_code = 400; *err_txt = "sdp body part not found"; - } - - if (sdp_remote.parse((const char*)body.getPayload())) { + + } else if (sdp_remote.parse((const char*)body.getPayload())){ err_code = 400; *err_txt = "session description parsing failed"; - } - else if(sdp_remote.media.empty()){ + + } else if (sdp_remote.media.empty()){ err_code = 400; *err_txt = "no media line found in SDP message"; } @@ -255,17 +277,35 @@ int AmOfferAnswer::onRxSdp(unsigned int m_cseq, const AmMimeBody& body, const ch if(err_code == 0) { switch(state) { - case OA_None: case OA_Completed: + if(m_cseq == cseq && remote_tag == m_remote_tag){ + // same INVITE transaction: ignore new SDP coming + break; + } + // else continue with normal processing below... + + case OA_None: setState(OA_OfferRecved); + dlg->onSdpReceived(true); cseq = m_cseq; + remote_tag = m_remote_tag; break; - + + // TODO: add support of OA_PreviewCompleted + case OA_OfferSent: - setState(OA_Completed); + if(is_reliable) + setState(OA_Completed); + + dlg->onSdpReceived(false); + remote_tag = m_remote_tag; break; case OA_OfferRecved: + if(m_cseq == cseq && remote_tag == m_remote_tag){ + /* same INVITE transaction: ignore new SDP coming */ + break; + } err_code = 400;// TODO: check correct error code *err_txt = "pending SDP offer"; break; @@ -281,12 +321,14 @@ int AmOfferAnswer::onRxSdp(unsigned int m_cseq, const AmMimeBody& body, const ch return err_code; } -int AmOfferAnswer::onTxSdp(unsigned int m_cseq, const AmMimeBody& body, bool force_no_sdp_update) +int AmOfferAnswer::onTxSdp(unsigned int m_cseq, bool is_reliable, + const AmMimeBody& body, bool force_no_sdp_update) { ILOG_DLG(L_DBG, "AmOfferAnswer::onTxSdp()\n"); /* assume that the payload is ok if it is not empty. * (do not parse again self-generated SDP) */ + if (body.empty()) { ILOG_DLG(L_DBG, "Body is empty, cannot do anything here.\n"); return -1; @@ -302,8 +344,12 @@ int AmOfferAnswer::onTxSdp(unsigned int m_cseq, const AmMimeBody& body, bool for break; case OA_OfferRecved: - if (!force_no_sdp_update) - setState(OA_Completed); + if (is_reliable) { + if (!force_no_sdp_update) + setState(OA_Completed); + } + + // TODO: add support of OA_PreviewCompleted break; case OA_OfferSent: @@ -347,6 +393,7 @@ int AmOfferAnswer::onRequestOut(AmSipRequest& req) else if(force_sdp) { return -1; } + } else if (sdp_body && has_sdp) { // update local SDP copy if (sdp_local.parse((const char*)sdp_body->getPayload())) { @@ -355,7 +402,7 @@ int AmOfferAnswer::onRequestOut(AmSipRequest& req) } if ((has_sdp || (has_csta && req.method != SIP_METH_INFO)) && - (onTxSdp(req.cseq,req.body) != 0)) + (onTxSdp(req.cseq, true, req.body) != 0)) { ILOG_DLG(L_WARN, "onTxSdp() failed\n"); return -1; @@ -402,7 +449,6 @@ int AmOfferAnswer::onReplyOut(AmSipReply& reply, int &flags, AmMimeBody &ret_bod already had the local SDP and saved that. Re-use it, and do not beget the 183 without SDP body */ if (reply.code == 183 && !sdp_local.media.empty()) { - ILOG_DLG(L_DBG, "The 183 with no SDP, but system already has local SDP for this session, re-using it..\n"); string existing_sdp; @@ -427,6 +473,7 @@ int AmOfferAnswer::onReplyOut(AmSipReply& reply, int &flags, AmMimeBody &ret_bod generate_sdp = !no_sdp_generation && ((state == OA_OfferRecved) || (state == OA_None) + // TODO; add support of OA_PreviewCompleted || (state == OA_Completed)); ILOG_DLG(L_DBG, "Now generate_sdp has been reset to <%c>.\n", generate_sdp ? 't' : 'f'); } @@ -459,13 +506,7 @@ int AmOfferAnswer::onReplyOut(AmSipReply& reply, int &flags, AmMimeBody &ret_bod ILOG_DLG(L_DBG, "Generating of the new SDP body is required.\n"); if (getSdpBody(sdp_buf)) { - if (reply.code == 183 && reply.cseq_method == SIP_METH_INVITE) { - /* just ignore if no SDP is generated (required for B2B) */ - } else if (reply.code == 200 && reply.cseq_method == SIP_METH_INVITE && state == OA_Completed) { - /* just ignore if no SDP is generated (required for B2B) */ - } else if(force_sdp) - return -1; - + if (force_sdp) return -1; } else { if (!sdp_body) { if ((sdp_body = reply.body.addPart(SIP_APPLICATION_SDP)) == NULL ) { @@ -496,11 +537,13 @@ int AmOfferAnswer::onReplyOut(AmSipReply& reply, int &flags, AmMimeBody &ret_bod } } - if ((has_sdp || (has_csta && reply.cseq_method != SIP_METH_INFO)) && - (onTxSdp(reply.cseq, reply.body, force_no_sdp_update) != 0)) - { - ILOG_DLG(L_WARN, "onTxSdp() failed\n"); - return -1; + if (has_sdp || (has_csta && reply.cseq_method != SIP_METH_INFO)) { + bool is_reliable = dlg->willBeReliable(reply); + + if ((onTxSdp(reply.cseq, is_reliable, reply.body, force_no_sdp_update) != 0)) { + ILOG_DLG(L_WARN, "onTxSdp() failed\n"); + return -1; + } } if ((reply.code >= 300) && (reply.cseq == cseq)) { @@ -549,33 +592,38 @@ int AmOfferAnswer::onReplySent(const AmSipReply& reply) int AmOfferAnswer::getSdpBody(string& sdp_body) { - switch(state){ - case OA_None: - case OA_Completed: - if(dlg->getSdpOffer(sdp_local)){ - sdp_local.print(sdp_body); - } - else { - ILOG_DLG(L_DBG, "No SDP Offer.\n"); - return -1; - } - break; - case OA_OfferRecved: - if(dlg->getSdpAnswer(sdp_remote,sdp_local)){ - sdp_local.print(sdp_body); - } - else { - ILOG_DLG(L_DBG, "No SDP Answer.\n"); - return -1; - } - break; - - case OA_OfferSent: - ILOG_DLG(L_DBG, "Still waiting for a reply\n"); - return -1; + ILOG_DLG(L_DBG, "AmOfferAnswer::getSdpBody(), current OA state is <%d>\n", state); - default: - break; + switch (state) { + + case OA_None: + case OA_Completed: + ILOG_DLG(L_DBG, "OA: Getting SDP offer..\n"); + if (dlg->getSdpOffer(sdp_local)) { + sdp_local.print(sdp_body); + } else { + ILOG_DLG(L_DBG, "No SDP Offer.\n"); + return -1; + } + break; + + // TODO: add support of OA_PreviewCompleted + case OA_OfferRecved: + ILOG_DLG(L_DBG, "OA: Getting SDP offer..\n"); + if (dlg->getSdpAnswer(sdp_remote,sdp_local)) { + sdp_local.print(sdp_body); + } else { + ILOG_DLG(L_DBG, "No SDP Answer.\n"); + return -1; + } + break; + + case OA_OfferSent: + ILOG_DLG(L_DBG, "Still waiting for a reply!\n"); + return -1; + + default: + break; } return 0; diff --git a/core/AmOfferAnswer.h b/core/AmOfferAnswer.h index e646fda4..f0c19254 100644 --- a/core/AmOfferAnswer.h +++ b/core/AmOfferAnswer.h @@ -40,6 +40,7 @@ public: OA_None=0, OA_OfferRecved, OA_OfferSent, + OA_PreviewCompleted, // see RFC6337, section 3.1.1 OA_Completed, __max_OA }; @@ -48,6 +49,7 @@ private: OAState state; OAState saved_state; unsigned int cseq; + string remote_tag; AmSdp sdp_remote; AmSdp sdp_local; @@ -61,8 +63,10 @@ private: int checkStateChange(); /** SDP handling */ - int onRxSdp(unsigned int m_cseq, const AmMimeBody& body, const char** err_txt); - int onTxSdp(unsigned int m_cseq, const AmMimeBody& body, bool force_no_sdp_update = false); + int onRxSdp(unsigned int m_cseq, const string& m_remote_tag, + bool is_reliable, const AmMimeBody& body, + const char** err_txt); + int onTxSdp(unsigned int m_cseq, bool is_reliable, const AmMimeBody& body, bool force_no_sdp_update = false); int getSdpBody(string& sdp_body); public: @@ -70,10 +74,11 @@ public: AmOfferAnswer(AmSipDialog* dlg); /** Accessors */ - OAState getState(); + OAState getState() const; + OAState getSavedState() { return saved_state; } void setState(OAState n_st); - const AmSdp& getLocalSdp(); - const AmSdp& getRemoteSdp(); + const AmSdp& getLocalSdp() const; + const AmSdp& getRemoteSdp() const; void clear(); void clearTransitionalState(); diff --git a/core/AmSession.h b/core/AmSession.h index b47f2353..621d25ad 100644 --- a/core/AmSession.h +++ b/core/AmSession.h @@ -591,6 +591,9 @@ public: /** Hook called when an SDP OA transaction has been completed */ virtual int onSdpCompleted(const AmSdp& offer, const AmSdp& answer); + /** Hook called when an SDP Offer or Answer is received */ + virtual void onSdpReceived(const AmSdp& sdp, bool is_offer) {}; + /** Hook called when an early session starts (SDP OA completed + dialog in early state) */ virtual void onEarlySessionStart(); diff --git a/core/AmSipDialog.cpp b/core/AmSipDialog.cpp index b3d42dbc..f084c205 100644 --- a/core/AmSipDialog.cpp +++ b/core/AmSipDialog.cpp @@ -205,6 +205,13 @@ int AmSipDialog::onSdpCompleted() return ret; } +void AmSipDialog::onSdpReceived(bool is_offer) +{ + if (!hdl) return; + + ((AmSipDialogEventHandler*)hdl)->onSdpReceived(oa.getRemoteSdp(), is_offer); +} + bool AmSipDialog::getSdpOffer(AmSdp& offer) { if(!hdl) return false; @@ -236,6 +243,11 @@ void AmSipDialog::setRel100State(Am100rel::State rel100_state) { rel100.setState(rel100_state); } +bool AmSipDialog::willBeReliable(const AmSipReply& reply) +{ + return rel100.willBeReliable(reply); +} + void AmSipDialog::setOAEnabled(bool oa_enabled) { ILOG_DLG(L_DBG, "%sabling offer_answer on SIP dialog '%s'\n", oa_enabled?"en":"dis", local_tag.c_str()); diff --git a/core/AmSipDialog.h b/core/AmSipDialog.h index 559f29ac..16d7c1c9 100644 --- a/core/AmSipDialog.h +++ b/core/AmSipDialog.h @@ -102,6 +102,11 @@ protected: /** @return a pending UAS INVITE transaction or NULL */ AmSipRequest* getUASPendingInv(); + /** + * Calls onSdpReceived on the session event handler + */ + void onSdpReceived(bool is_offer); + /** * Calls onSdpCompleted on the session event handler * and executes onSessionStart/onEarlySessionStart when required. @@ -130,7 +135,11 @@ protected: const AmSdp& getLocalSdp() { return oa.getLocalSdp(); } const AmSdp& getRemoteSdp() { return oa.getRemoteSdp(); } + /** + * Reliable provisional replies + */ void setRel100State(Am100rel::State rel100_state); + bool willBeReliable(const AmSipReply& reply); void uasTimeout(AmSipTimeoutEvent* to_ev); @@ -209,6 +218,9 @@ public: /** Hook called when an SDP OA transaction has been completed */ virtual int onSdpCompleted(const AmSdp& local, const AmSdp& remote)=0; + /** Hook called when an SDP Offer or Answer is received */ + virtual void onSdpReceived(const AmSdp& sdp, bool is_offer)=0; + /** Hook called when an early session starts * (SDP OA completed + dialog in early state) */ virtual void onEarlySessionStart()=0;