mirror of https://github.com/sipwise/sems.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
405 lines
16 KiB
405 lines
16 KiB
/*
|
|
* Copyright (C) 2010-2011 Stefan Sayer
|
|
* Copyright (C) 2012-2013 FRAFOS GmbH
|
|
*
|
|
* This file is part of SEMS, a free SIP media server.
|
|
*
|
|
* SEMS is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* For a license to use the SEMS software under conditions
|
|
* other than those described here, or to purchase support for this
|
|
* software, please contact iptel.org by e-mail at the following addresses:
|
|
* info@iptel.org
|
|
*
|
|
* SEMS is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
#ifndef __AMB2BCALL_H
|
|
#define __AMB2BCALL_H
|
|
|
|
#include "AmB2BSession.h"
|
|
#include "AmSessionContainer.h"
|
|
#include "CallLegEvents.h"
|
|
#include "SessionUpdate.h"
|
|
|
|
#include <queue>
|
|
|
|
/** composed AmB2BCalleeSession & AmB2BCallerSession
|
|
* represents indepenedently A or B leg of a call,
|
|
* old clases left for compatibility
|
|
*
|
|
* Notes:
|
|
*
|
|
* - we use the relayEvent implementation from AmB2BSession - it can happen
|
|
* that we have no peer (i.e. we are a standalone call leg, for example parked
|
|
* one) => do not create other call leg automatically
|
|
*
|
|
* - we use onSystemEvent implementation from AmB2BSession - the other leg
|
|
* receives and handles the same shutdown event, right?
|
|
*
|
|
* - the role (A/B leg) can be changed during the CallLeg life and is
|
|
* understood just this way
|
|
*
|
|
* "A leg is the call leg created when handling initial INVITE request"
|
|
*
|
|
* It is used
|
|
* - as identification what part of media session is affected by operation
|
|
* - when CANCEL is being processed only the CANCEL of initial INVITE is
|
|
* important so it is explicitly verified that it is handled in A leg)
|
|
*
|
|
* In other words - the B leg can create new 'b-like-legs' the same way the
|
|
* A leg does.
|
|
* */
|
|
class CallLeg: public AmB2BSession
|
|
{
|
|
public:
|
|
/** B2B call status.
|
|
*
|
|
* This status need not to be related directly to SIP dialog status in
|
|
* appropriate call legs - for example the B2B call status can be
|
|
* "Connected" though the legs have received BYE replies. */
|
|
enum CallStatus {
|
|
Disconnected, //< there is no other call leg we are connected to
|
|
NoReply, //< there is at least one call leg we are connected to but without any response
|
|
Ringing, //< this leg or one of legs we are connected to rings
|
|
Connected, //< there is exactly one call leg we are connected to, in this case AmB2BSession::other_id holds the other leg id
|
|
Disconnecting //< we were connected and now going to be disconnected (waiting for reINVITE reply for example)
|
|
};
|
|
|
|
/** reason reported in onCallFailed method */
|
|
enum CallFailureReason {
|
|
CallRefused, //< non-ok reply received and no more B-legs exit
|
|
CallCanceled //< call canceled
|
|
};
|
|
|
|
/** reason for changing call status */
|
|
struct StatusChangeCause
|
|
{
|
|
enum Reason {
|
|
SipReply,
|
|
SipRequest,
|
|
Canceled,
|
|
NoAck,
|
|
NoPrack,
|
|
RtpTimeout,
|
|
SessionTimeout,
|
|
InternalError,
|
|
Other
|
|
} reason;
|
|
|
|
union {
|
|
const AmSipReply *reply;
|
|
const AmSipRequest *request;
|
|
const char *desc;
|
|
} param;
|
|
StatusChangeCause(const AmSipReply *r): reason(SipReply) { param.reply = r; }
|
|
StatusChangeCause(const AmSipRequest *r): reason(SipRequest) { param.request = r; }
|
|
StatusChangeCause(): reason(Other) { param.desc = NULL; }
|
|
StatusChangeCause(const char *desc): reason(Other) { param.desc = desc; }
|
|
StatusChangeCause(const Reason r): reason(r) { param.reply = NULL; }
|
|
};
|
|
|
|
private:
|
|
|
|
CallStatus call_status; //< status of the call (replaces callee's status)
|
|
|
|
/** information needed in A leg for a B leg */
|
|
struct OtherLegInfo {
|
|
/** local tag of the B leg */
|
|
string id;
|
|
|
|
/** once the B leg gets connected to the A leg A leg starts to use its
|
|
* corresponding media_session created when the B leg is added to the list
|
|
* of B legs */
|
|
AmB2BMedia *media_session;
|
|
|
|
void releaseMediaSession() {
|
|
if (media_session) {
|
|
media_session->releaseReference();
|
|
media_session = NULL;
|
|
}
|
|
}
|
|
};
|
|
|
|
/** List of legs which can be connected to this leg, it is valid for A leg until first
|
|
* 2xx response which moves the A leg to Connected state and terminates all
|
|
* other B legs.
|
|
*
|
|
* Please note that the A/B role may change during the call leg life. For
|
|
* example when a B leg is parked and then 'rings back on timer' it becomes
|
|
* A leg, i.e. it creates new B leg(s) for itself. */
|
|
std::vector<OtherLegInfo> other_legs;
|
|
|
|
bool on_hold; // remote is on hold
|
|
AmSdp non_hold_sdp;
|
|
enum { HoldRequested, ResumeRequested, PreserveHoldStatus } hold;
|
|
|
|
// queue of session update operations, first element is possibly the one
|
|
// being in progress
|
|
std::list<SessionUpdate *> pending_updates;
|
|
class SessionUpdateTimer pending_updates_timer;
|
|
|
|
// generate re-INVITE with given parameters (establishing means that the
|
|
// INVITE is establishing a connection between two legs)
|
|
// returns the request CSeq or -1 upon error
|
|
int reinvite(const string &hdrs, const AmMimeBody &body, bool relayed, unsigned r_cseq, bool establishing);
|
|
|
|
// put on hold directly, returns CSeq of hold request or -1 if no request
|
|
// was sent out (either error or on hold already)
|
|
int putOnHoldImpl();
|
|
|
|
// resume held call directly; returns CSeq of unhold request or -1 if no request
|
|
// was sent out (either error or not on hold)
|
|
int resumeHeldImpl();
|
|
|
|
// generate 200 reply on a pending INVITE (uses fake body)
|
|
void acceptPendingInvite(AmSipRequest *invite);
|
|
|
|
// methods just for make this stuff more readable, not intended to be
|
|
// overriden, override onB2BEvent instead!
|
|
void onB2BReply(B2BSipReplyEvent *e);
|
|
void onB2BConnect(ConnectLegEvent *e);
|
|
void onB2BReconnect(ReconnectLegEvent *e);
|
|
void onB2BReplace(ReplaceLegEvent *e);
|
|
void onB2BReplaceInProgress(ReplaceInProgressEvent *e);
|
|
void b2bInitial1xx(AmSipReply& reply, bool forward);
|
|
void b2bInitial2xx(AmSipReply& reply, bool forward);
|
|
void b2bInitialErr(AmSipReply& reply, bool forward);
|
|
|
|
int relaySipReply(AmSipReply &reply);
|
|
|
|
/** terminate all other B legs than the connected one (should not be used
|
|
* directly by successors, right?) */
|
|
void terminateNotConnectedLegs();
|
|
|
|
/** choose given B leg from the list of other B legs */
|
|
bool setOther(const string &id, bool use_initial_sdp);
|
|
|
|
/** remove given leg from the list of other legs */
|
|
void removeOtherLeg(const string &id);
|
|
|
|
void updateCallStatus(CallStatus new_status, const StatusChangeCause &cause = StatusChangeCause());
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// callbacks (intended to be redefined in successors but should not be
|
|
// called by them directly)
|
|
|
|
/* handler called when call status changes */
|
|
virtual void onCallStatusChange(const StatusChangeCause &cause) { }
|
|
|
|
/** handler called when the second leg is connected (FIXME: this is a hack,
|
|
* use this method in SBCCallLeg only) */
|
|
virtual void onCallConnected(const AmSipReply& reply) { }
|
|
|
|
/** Method called if given B leg couldn't establish the call (refused with
|
|
* failure response)
|
|
*
|
|
* Redefine to implement serial fork or handle redirect. */
|
|
virtual void onBLegRefused(const AmSipReply& reply) { }
|
|
|
|
/** handler called when all B-legs failed or the call has been canceled.
|
|
The reply passed is the last final reply. */
|
|
virtual void onCallFailed(CallFailureReason reason, const AmSipReply *reply) { }
|
|
|
|
/** add newly created callee with prepared ConnectLegEvent */
|
|
void addNewCallee(CallLeg *callee, ConnectLegEvent *e) { addNewCallee(callee, e, rtp_relay_mode); }
|
|
|
|
/** add a newly created calee with prepared ConnectLegEvent and forced RTP
|
|
* relay mode (this is a hack to work around allowed temporary changes of
|
|
* RTP relay mode used for music on hold)
|
|
* FIXME: throw this out once MoH will use another method than temporary RTP
|
|
* Relay mode change */
|
|
void addNewCallee(CallLeg *callee, ConnectLegEvent *e, AmB2BSession::RTPRelayMode mode);
|
|
|
|
void addExistingCallee(const string &session_tag, ReconnectLegEvent *e);
|
|
|
|
/** Clears other leg, eventually removes it from the list of other legs if
|
|
* it is there. It neither updates call state nor sip_relay_only flag! */
|
|
virtual void clear_other();
|
|
|
|
void changeRtpMode(RTPRelayMode new_mode, AmB2BMedia *new_media);
|
|
void changeOtherLegsRtpMode(RTPRelayMode new_mode);
|
|
|
|
// offer-answer handling
|
|
void adjustOffer(AmSdp &sdp);
|
|
|
|
/* offer was rejected (called just for negative replies to an request
|
|
* carying offer (not always correct?), answer with disabled streams
|
|
* doesn't cause calling this */
|
|
void offerRejected();
|
|
|
|
// returns false if there is a pending INVITE so the session can not be
|
|
// changed right now
|
|
bool canUpdateSession() { return !(dlg->getUACInvTransPending() || dlg->getUASPendingInv()); }
|
|
void applyPendingUpdate();
|
|
void updateSession(SessionUpdate *op);
|
|
|
|
virtual void onTransFinished();
|
|
|
|
protected:
|
|
|
|
// functions offered to successors
|
|
|
|
virtual void setCallStatus(CallStatus new_status);
|
|
CallStatus getCallStatus() { return call_status; }
|
|
|
|
// @see AmSession
|
|
virtual void onInvite(const AmSipRequest& req);
|
|
virtual void onInvite2xx(const AmSipReply& reply);
|
|
virtual void onCancel(const AmSipRequest& req);
|
|
virtual void onBye(const AmSipRequest& req);
|
|
virtual void onRemoteDisappeared(const AmSipReply& reply);
|
|
virtual void onNoAck(unsigned int cseq);
|
|
virtual void onNoPrack(const AmSipRequest &req, const AmSipReply &rpl);
|
|
virtual void onRtpTimeout();
|
|
virtual void onSessionTimeout();
|
|
|
|
// @see AmB2BSession
|
|
virtual void onOtherBye(const AmSipRequest& req);
|
|
|
|
virtual void onSipRequest(const AmSipRequest& req);
|
|
virtual void onSipReply(const AmSipRequest& req, const AmSipReply& reply, AmSipDialog::Status old_dlg_status);
|
|
|
|
virtual void onInitialReply(B2BSipReplyEvent *e);
|
|
|
|
/* callback method called when hold/resume request is replied */
|
|
// virtual void handleHoldReply(bool succeeded);
|
|
|
|
/* called to create SDP of locally generated hold request */
|
|
virtual void createHoldRequest(AmSdp &sdp) = 0;
|
|
|
|
/** called to alter B2B hold request (i.e. the request from other peer) */
|
|
virtual void alterHoldRequest(AmSdp &sdp) { }
|
|
|
|
/* called to create SDP of locally generated resume request */
|
|
virtual void createResumeRequest(AmSdp &sdp);
|
|
|
|
/** called to alter B2B hold request (i.e. the request from other peer) */
|
|
virtual void alterResumeRequest(AmSdp &sdp) { }
|
|
|
|
/* hold requested (either from B2B peer leg or locally)
|
|
* to be overridden */
|
|
virtual void holdRequested() { }
|
|
virtual void holdAccepted();
|
|
virtual void holdRejected();
|
|
virtual void resumeRequested() { }
|
|
virtual void resumeAccepted();
|
|
virtual void resumeRejected() { }
|
|
|
|
virtual void terminateOtherLeg();
|
|
virtual void terminateLeg();
|
|
|
|
/** change RTP mode (and AmB2BMedia if needed) but do not send reINVITE
|
|
*
|
|
* Changes the mode for all peer legs as well.
|
|
*
|
|
* WARNING: really unsafe because possibly makes the object state
|
|
* inconsistent with sent SIP messages (sent/received SDPs).
|
|
*
|
|
* WARNING: might require several events exchanged between call legs, the
|
|
* AmB2BMedia will be valid after them!
|
|
*
|
|
* TODO: add parameter to trigger reINVITE */
|
|
void changeRtpMode(RTPRelayMode new_mode);
|
|
|
|
virtual void updateLocalSdp(AmSdp &sdp);
|
|
|
|
// return retry time for 491 reply in seconds
|
|
virtual double get491RetryTime() { return (get_random() % 200) / 100.0; }
|
|
|
|
public:
|
|
virtual void onB2BEvent(B2BEvent* ev);
|
|
|
|
/** does all the job around disconnecting from the other leg (updates call
|
|
* status, disconnects RTP from the other, puts remote on hold (if
|
|
* requested)
|
|
*
|
|
* The other leg is not affected by disconnect - it is neither terminated
|
|
* nor informed about the peer disconnection. */
|
|
virtual void disconnect(bool hold_remote, bool preserve_media_session = false);
|
|
|
|
/** Terminate the whole B2B call (if there is no other leg only this one is
|
|
* stopped). */
|
|
virtual void stopCall(const StatusChangeCause &cause);
|
|
|
|
|
|
/** Put remote party on hold (may change RTP relay mode!). Note that this
|
|
* task is asynchronous so the remote is most probably NOT 'on hold' after
|
|
* calling this method
|
|
*
|
|
* This method calls handleHoldReply(false) directly if an error occurs. */
|
|
virtual void putOnHold();
|
|
|
|
/** resume call if the remote party is on hold */
|
|
virtual void resumeHeld(/*bool send_reinvite*/);
|
|
|
|
virtual bool isOnHold() { return on_hold; }
|
|
|
|
|
|
/** add given call leg as our B leg */
|
|
void addCallee(CallLeg *callee, const AmSipRequest &relayed_invite) { addNewCallee(callee, new ConnectLegEvent(relayed_invite)); }
|
|
|
|
/** add given already existing session as our B leg */
|
|
void addCallee(const string &session_tag, const AmSipRequest &relayed_invite);
|
|
void addCallee(const string &session_tag, const string &hdrs)
|
|
{ addExistingCallee(session_tag, new ReconnectLegEvent(a_leg ? ReconnectLegEvent::B : ReconnectLegEvent::A, getLocalTag(), hdrs, established_body)); }
|
|
|
|
/** add given call leg as our B leg (only stored SDP is used, we don't need
|
|
* to have INVITE request.
|
|
* Can be used in A leg and B leg as well.
|
|
* Additional headers may be specified - these are used in outgoing INVITE
|
|
* to the callee's destination. */
|
|
void addCallee(CallLeg *callee, const string &hdrs);
|
|
// void addCallee(CallLeg *callee, const string &hdrs, AmB2BSession::RTPRelayMode mode) { addNewCallee(callee, new ConnectLegEvent(hdrs, established_body), mode); }
|
|
|
|
|
|
/** Replace given already existing session in a B2B call. We become new
|
|
* A leg there regardless if we are replacing original A or B leg. */
|
|
void replaceExistingLeg(const string &session_tag, const AmSipRequest &relayed_invite);
|
|
void replaceExistingLeg(const string &session_tag, const string &hdrs);
|
|
|
|
/** generate debug information into log with overall call leg status */
|
|
void debug();
|
|
|
|
const char* getCallStatusStr();
|
|
|
|
// AmMediaSession interface from AmMediaProcessor
|
|
int readStreams(unsigned long long ts, unsigned char *buffer);
|
|
int writeStreams(unsigned long long ts, unsigned char *buffer);
|
|
|
|
public:
|
|
/** creates A leg */
|
|
CallLeg(AmSipDialog* p_dlg=NULL, AmSipSubscription* p_subs=NULL);
|
|
|
|
/** creates B leg using given session as A leg */
|
|
CallLeg(const CallLeg* caller, AmSipDialog* p_dlg=NULL,
|
|
AmSipSubscription* p_subs=NULL);
|
|
|
|
virtual ~CallLeg();
|
|
|
|
// OA callbacks
|
|
virtual int onSdpCompleted(const AmSdp& local, const AmSdp& remote);
|
|
virtual bool getSdpOffer(AmSdp& offer) { return false; }
|
|
virtual bool getSdpAnswer(const AmSdp& offer, AmSdp& answer) { return false; }
|
|
virtual void onEarlySessionStart() { }
|
|
virtual void onSessionStart() { }
|
|
|
|
// classes doing session update are allowed to access our internals:
|
|
friend class Reinvite;
|
|
friend class PutOnHold;
|
|
friend class ResumeHeld;
|
|
};
|
|
|
|
|
|
|
|
#endif
|