2013-01-18 15:55:50 +00:00
using System ;
using System.IO ;
using System.Threading ;
using System.Collections ;
2014-01-08 14:16:39 +00:00
using AsterNET.Manager.Action ;
using AsterNET.Manager.Event ;
using AsterNET.Manager.Response ;
2013-01-18 15:55:50 +00:00
using System.Text.RegularExpressions ;
using System.Text ;
using System.Net.Sockets ;
using System.Collections.Generic ;
using System.Reflection ;
2014-01-08 14:16:39 +00:00
using AsterNET.IO ;
using AsterNET.Util ;
2013-01-18 15:55:50 +00:00
2014-01-08 14:16:39 +00:00
namespace AsterNET.Manager
2013-01-18 15:55:50 +00:00
{
#region Event delegate
public delegate void ManagerEventHandler ( object sender , ManagerEvent e ) ;
public delegate void AgentCallbackLoginEventHandler ( object sender , Event . AgentCallbackLoginEvent e ) ;
public delegate void AgentCallbackLogoffEventHandler ( object sender , Event . AgentCallbackLogoffEvent e ) ;
public delegate void AgentCalledEventHandler ( object sender , Event . AgentCalledEvent e ) ;
public delegate void AgentCompleteEventHandler ( object sender , Event . AgentCompleteEvent e ) ;
public delegate void AgentConnectEventHandler ( object sender , Event . AgentConnectEvent e ) ;
public delegate void AgentDumpEventHandler ( object sender , Event . AgentDumpEvent e ) ;
public delegate void AgentLoginEventHandler ( object sender , Event . AgentLoginEvent e ) ;
public delegate void AgentLogoffEventHandler ( object sender , Event . AgentLogoffEvent e ) ;
public delegate void AgentsCompleteEventHandler ( object sender , Event . AgentsCompleteEvent e ) ;
public delegate void AgentsEventHandler ( object sender , Event . AgentsEvent e ) ;
public delegate void AlarmClearEventHandler ( object sender , Event . AlarmClearEvent e ) ;
public delegate void AlarmEventHandler ( object sender , Event . AlarmEvent e ) ;
2014-01-08 14:16:39 +00:00
public delegate void BridgeEventHandler ( object sender , Event . BridgeEvent e ) ;
2013-01-18 15:55:50 +00:00
public delegate void CdrEventHandler ( object sender , Event . CdrEvent e ) ;
public delegate void DBGetResponseEventHandler ( object sender , Event . DBGetResponseEvent e ) ;
public delegate void DialEventHandler ( object sender , Event . DialEvent e ) ;
2014-01-08 14:16:39 +00:00
public delegate void DTMFEventHandler ( object sender , Event . DTMFEvent e ) ;
2013-01-18 15:55:50 +00:00
public delegate void DNDStateEventHandler ( object sender , Event . DNDStateEvent e ) ;
public delegate void ExtensionStatusEventHandler ( object sender , Event . ExtensionStatusEvent e ) ;
public delegate void HangupEventHandler ( object sender , Event . HangupEvent e ) ;
public delegate void HoldedCallEventHandler ( object sender , Event . HoldedCallEvent e ) ;
public delegate void HoldEventHandler ( object sender , Event . HoldEvent e ) ;
public delegate void JoinEventHandler ( object sender , Event . JoinEvent e ) ;
public delegate void LeaveEventHandler ( object sender , Event . LeaveEvent e ) ;
public delegate void LinkEventHandler ( object sender , Event . LinkEvent e ) ;
public delegate void LogChannelEventHandler ( object sender , Event . LogChannelEvent e ) ;
public delegate void MeetMeJoinEventHandler ( object sender , Event . MeetmeJoinEvent e ) ;
public delegate void MeetMeLeaveEventHandler ( object sender , Event . MeetmeLeaveEvent e ) ;
public delegate void MeetMeTalkingEventHandler ( object sender , Event . MeetmeTalkingEvent e ) ;
2017-05-03 15:00:27 +00:00
2013-01-18 15:55:50 +00:00
public delegate void MessageWaitingEventHandler ( object sender , Event . MessageWaitingEvent e ) ;
public delegate void NewCallerIdEventHandler ( object sender , Event . NewCallerIdEvent e ) ;
public delegate void NewChannelEventHandler ( object sender , Event . NewChannelEvent e ) ;
public delegate void NewExtenEventHandler ( object sender , Event . NewExtenEvent e ) ;
public delegate void NewStateEventHandler ( object sender , Event . NewStateEvent e ) ;
public delegate void OriginateResponseEventHandler ( object sender , Event . OriginateResponseEvent e ) ;
public delegate void ParkedCallEventHandler ( object sender , Event . ParkedCallEvent e ) ;
public delegate void ParkedCallGiveUpEventHandler ( object sender , Event . ParkedCallGiveUpEvent e ) ;
public delegate void ParkedCallsCompleteEventHandler ( object sender , Event . ParkedCallsCompleteEvent e ) ;
public delegate void ParkedCallTimeOutEventHandler ( object sender , Event . ParkedCallTimeOutEvent e ) ;
public delegate void PeerEntryEventHandler ( object sender , Event . PeerEntryEvent e ) ;
public delegate void PeerlistCompleteEventHandler ( object sender , Event . PeerlistCompleteEvent e ) ;
public delegate void PeerStatusEventHandler ( object sender , Event . PeerStatusEvent e ) ;
public delegate void QueueEntryEventHandler ( object sender , Event . QueueEntryEvent e ) ;
public delegate void QueueMemberAddedEventHandler ( object sender , Event . QueueMemberAddedEvent e ) ;
public delegate void QueueMemberEventHandler ( object sender , Event . QueueMemberEvent e ) ;
public delegate void QueueMemberPausedEventHandler ( object sender , Event . QueueMemberPausedEvent e ) ;
public delegate void QueueMemberRemovedEventHandler ( object sender , Event . QueueMemberRemovedEvent e ) ;
public delegate void QueueMemberStatusEventHandler ( object sender , Event . QueueMemberStatusEvent e ) ;
public delegate void QueueParamsEventHandler ( object sender , Event . QueueParamsEvent e ) ;
public delegate void QueueStatusCompleteEventHandler ( object sender , Event . QueueStatusCompleteEvent e ) ;
public delegate void RegistryEventHandler ( object sender , Event . RegistryEvent e ) ;
public delegate void RenameEventHandler ( object sender , Event . RenameEvent e ) ;
2014-01-08 14:16:39 +00:00
public delegate void TransferEventHandler ( object sender , Event . TransferEvent e ) ;
2013-01-18 15:55:50 +00:00
public delegate void StatusCompleteEventHandler ( object sender , Event . StatusCompleteEvent e ) ;
public delegate void StatusEventHandler ( object sender , Event . StatusEvent e ) ;
public delegate void UnholdEventHandler ( object sender , Event . UnholdEvent e ) ;
public delegate void UnlinkEventHandler ( object sender , Event . UnlinkEvent e ) ;
public delegate void UnparkedCallEventHandler ( object sender , Event . UnparkedCallEvent e ) ;
public delegate void UserEventHandler ( object sender , Event . UserEvent e ) ;
2014-01-08 14:16:39 +00:00
public delegate void QueueCallerAbandonEventHandler ( object sender , Event . QueueCallerAbandonEvent e ) ;
2013-01-18 15:55:50 +00:00
public delegate void ZapShowChannelsCompleteEventHandler ( object sender , Event . ZapShowChannelsCompleteEvent e ) ;
public delegate void ZapShowChannelsEventHandler ( object sender , Event . ZapShowChannelsEvent e ) ;
public delegate void ConnectionStateEventHandler ( object sender , Event . ConnectionStateEvent e ) ;
2014-01-08 14:16:39 +00:00
public delegate void VarSetEventHandler ( object sender , Event . VarSetEvent e ) ;
public delegate void AGIExecHandler ( object sender , Event . AGIExecEvent e ) ;
public delegate void ConfbridgeStartEventHandler ( object sender , Event . ConfbridgeStartEvent e ) ;
public delegate void ConfbridgeJoinEventHandler ( object sender , Event . ConfbridgeJoinEvent e ) ;
public delegate void ConfbridgeLeaveEventHandler ( object sender , Event . ConfbridgeLeaveEvent e ) ;
public delegate void ConfbridgeEndEventHandler ( object sender , Event . ConfbridgeEndEvent e ) ;
public delegate void ConfbridgeTalkingEventHandler ( object sender , Event . ConfbridgeTalkingEvent e ) ;
2014-04-07 10:35:39 +00:00
public delegate void FailedACLEventHandler ( object sender , Event . FailedACLEvent e ) ;
2015-03-16 20:48:35 +00:00
public delegate void AttendedTransferEventHandler ( object sender , Event . AttendedTransferEvent e ) ;
public delegate void BlindTransferEventHandler ( object sender , Event . BlindTransferEvent e ) ;
public delegate void BridgeCreateEventHandler ( object sender , Event . BridgeCreateEvent e ) ;
public delegate void BridgeDestroyEventHandler ( object sender , Event . BridgeDestroyEvent e ) ;
public delegate void BridgeEnterEventHandler ( object sender , Event . BridgeEnterEvent e ) ;
public delegate void BridgeLeaveEventHandler ( object sender , Event . BridgeLeaveEvent e ) ;
2016-09-20 13:29:46 +00:00
public delegate void DialBeginEventHandler ( object sender , Event . DialBeginEvent e ) ;
2016-09-21 07:04:36 +00:00
public delegate void DialEndEventHandler ( object sender , Event . DialEndEvent e ) ;
public delegate void QueueCallerJoinEventHandler ( object sender , Event . QueueCallerJoinEvent e ) ;
public delegate void QueueCallerLeaveEventHandler ( object sender , Event . QueueCallerLeaveEvent e ) ;
2016-10-18 14:07:25 +00:00
public delegate void QueueMemberPauseEventHandler ( object sender , Event . QueueMemberPauseEvent e ) ;
2017-05-03 15:00:27 +00:00
public delegate void ChallengeResponseFailedEventHandler ( object sender , Event . ChallengeResponseFailedEvent e ) ;
public delegate void InvalidAccountIDEventHandler ( object sender , Event . InvalidAccountIDEvent e ) ;
public delegate void DeviceStateChangedEventHandler ( object sender , Event . DeviceStateChangeEvent e ) ;
public delegate void ChallengeSentEventHandler ( object sender , Event . ChallengeSentEvent e ) ;
public delegate void SuccessfulAuthEventHandler ( object sender , Event . SuccessfulAuthEvent e ) ;
2015-03-16 20:48:35 +00:00
2013-01-18 15:55:50 +00:00
2016-09-20 13:29:46 +00:00
#endregion
2013-01-18 15:55:50 +00:00
2016-09-20 13:29:46 +00:00
/// <summary>
/// Default implemention of the ManagerConnection interface.
/// </summary>
public class ManagerConnection
2013-01-18 15:55:50 +00:00
{
#region Variables
#if LOGGER
private Logger logger = Logger . Instance ( ) ;
#endif
private long actionIdCount = 0 ;
private string hostname ;
private int port ;
private string username ;
private string password ;
private SocketConnection mrSocket ;
2014-06-12 08:08:52 +00:00
private Thread mrReaderThread ;
2013-01-18 15:55:50 +00:00
private ManagerReader mrReader ;
private int defaultResponseTimeout = 2000 ;
private int defaultEventTimeout = 5000 ;
private int sleepTime = 50 ;
private bool keepAlive = true ;
private bool keepAliveAfterAuthenticationFailure = false ;
private string protocolIdentifier ;
private AsteriskVersion asteriskVersion ;
private Dictionary < int , IResponseHandler > responseHandlers ;
private Dictionary < int , IResponseHandler > pingHandlers ;
private Dictionary < int , IResponseHandler > responseEventHandlers ;
private int pingInterval = 10000 ;
private object lockSocket = new object ( ) ;
private object lockHandlers = new object ( ) ;
private bool enableEvents = true ;
private string version = string . Empty ;
private Encoding socketEncoding = Encoding . ASCII ;
private bool reconnected = false ;
private bool reconnectEnable = false ;
private int reconnectCount ;
private Dictionary < int , ConstructorInfo > registeredEventClasses ;
private Dictionary < int , int > registeredEventHandlers ;
private event ManagerEventHandler internalEvent ;
private bool fireAllEvents = false ;
private Thread callerThread ;
/// <summary> Default Fast Reconnect retry counter.</summary>
private int reconnectRetryFast = 5 ;
/// <summary> Default Maximum Reconnect retry counter.</summary>
private int reconnectRetryMax = 10 ;
/// <summary> Default Fast Reconnect interval in milliseconds.</summary>
private int reconnectIntervalFast = 5000 ;
/// <summary> Default Slow Reconnect interval in milliseconds.</summary>
private int reconnectIntervalMax = 10000 ;
2020-01-21 11:33:47 +00:00
public char [ ] VAR_DELIMITER = { '|' } ;
2015-04-02 17:23:08 +00:00
2013-01-18 15:55:50 +00:00
#endregion
2014-01-08 14:16:39 +00:00
/// <summary>
/// Allows you to specifiy how events are fired. If false (default) then
/// events will be fired in order. Otherwise events will be fired as they arrive and
/// control logic in your application will need to handle synchronization.
/// </summary>
public bool UseASyncEvents = false ;
2013-01-18 15:55:50 +00:00
#region Events
/// <summary>
/// An UnhandledEvent is triggered on unknown event.
/// </summary>
public event ManagerEventHandler UnhandledEvent ;
/// <summary>
/// An AgentCallbackLogin is triggered when an agent is successfully logged in.
/// </summary>
public event AgentCallbackLoginEventHandler AgentCallbackLogin ;
/// <summary>
/// An AgentCallbackLogoff is triggered when an agent that previously logged in is logged of.<br/>
/// </summary>
public event AgentCallbackLogoffEventHandler AgentCallbackLogoff ;
/// <summary>
/// An AgentCalled is triggered when an agent is ring.<br/>
2015-01-04 14:26:32 +00:00
/// To enable AgentCalled you have to set eventwhencalled = yes in queues.conf.<br/>
2013-01-18 15:55:50 +00:00
/// </summary>
public event AgentCalledEventHandler AgentCalled ;
/// <summary>
/// An AgentCompleteEvent is triggered when at the end of a call if the caller was connected to an agent.
/// </summary>
public event AgentCompleteEventHandler AgentComplete ;
/// <summary>
/// An AgentConnectEvent is triggered when a caller is connected to an agent.
/// </summary>
public event AgentConnectEventHandler AgentConnect ;
/// <summary>
/// An AgentDumpEvent is triggered when an agent dumps the caller while listening to the queue announcement.
/// </summary>
public event AgentDumpEventHandler AgentDump ;
/// <summary>
/// An AgentLoginEvent is triggered when an agent is successfully logged in using AgentLogin.
/// </summary>
public event AgentLoginEventHandler AgentLogin ;
/// <summary>
/// An AgentCallbackLogoffEvent is triggered when an agent that previously logged in using AgentLogin is logged of.
/// </summary>
public event AgentLogoffEventHandler AgentLogoff ;
/// <summary>
/// An AgentsCompleteEvent is triggered after the state of all agents has been reported in response to an AgentsAction.
/// </summary>
public event AgentsCompleteEventHandler AgentsComplete ;
/// <summary>
/// An AgentsEvent is triggered for each agent in response to an AgentsAction.
/// </summary>
public event AgentsEventHandler Agents ;
/// <summary>
/// An AlarmEvent is triggered when a Zap channel leaves alarm state.
/// </summary>
public event AlarmClearEventHandler AlarmClear ;
2014-01-08 14:16:39 +00:00
/// <summary>
///
/// </summary>
public event BridgeEventHandler Bridge ;
2013-01-18 15:55:50 +00:00
/// <summary>
/// An AlarmEvent is triggered when a Zap channel enters or changes alarm state.
/// </summary>
public event AlarmEventHandler Alarm ;
/// <summary>
/// A CdrEvent is triggered when a call detail record is generated, usually at the end of a call.
/// </summary>
public event CdrEventHandler Cdr ;
public event DBGetResponseEventHandler DBGetResponse ;
/// <summary>
/// A Dial is triggered whenever a phone attempts to dial someone.<br/>
/// </summary>
public event DialEventHandler Dial ;
2014-01-08 14:16:39 +00:00
public event DTMFEventHandler DTMF ;
2013-01-18 15:55:50 +00:00
/// <summary>
/// A DNDStateEvent is triggered by the Zap channel driver when a channel enters or leaves DND (do not disturb) state.
/// </summary>
public event DNDStateEventHandler DNDState ;
/// <summary>
/// An ExtensionStatus is triggered when the state of an extension changes.<br/>
/// </summary>
public event ExtensionStatusEventHandler ExtensionStatus ;
/// <summary>
/// A Hangup is triggered when a channel is hung up.<br/>
/// </summary>
public event HangupEventHandler Hangup ;
/// <summary>
/// A HoldedCall is triggered when a channel is put on hold.<br/>
/// </summary>
public event HoldedCallEventHandler HoldedCall ;
/// <summary>
/// A Hold is triggered by the SIP channel driver when a channel is put on hold.<br/>
/// </summary>
public event HoldEventHandler Hold ;
/// <summary>
/// A Join is triggered when a channel joines a queue.<br/>
/// </summary>
public event JoinEventHandler Join ;
/// <summary>
/// A Leave is triggered when a channel leaves a queue.<br/>
/// </summary>
public event LeaveEventHandler Leave ;
/// <summary>
/// A Link is triggered when two voice channels are linked together and voice data exchange commences.<br/>
/// Several Link events may be seen for a single call. This can occur when Asterisk fails to setup a
/// native bridge for the call.This is when Asterisk must sit between two telephones and perform
/// CODEC conversion on their behalf.
/// </summary>
public event LinkEventHandler Link ;
/// <summary>
/// A LogChannel is triggered when logging is turned on or off.<br/>
/// </summary>
public event LogChannelEventHandler LogChannel ;
/// <summary>
/// A MeetMeJoin is triggered if a channel joins a meet me conference.<br/>
/// </summary>
public event MeetMeJoinEventHandler MeetMeJoin ;
/// <summary>
/// A MeetMeLeave is triggered if a channel leaves a meet me conference.<br/>
/// </summary>
public event MeetMeLeaveEventHandler MeetMeLeave ;
// public event MeetMeStopTalkingEventHandler MeetMeStopTalking;
/// <summary>
/// A MeetMeTalkingEvent is triggered when a user starts talking in a meet me conference.<br/>
/// To enable talker detection you must pass the option 'T' to the MeetMe application.
/// </summary>
public event MeetMeTalkingEventHandler MeetMeTalking ;
/// <summary>
/// A MessageWaiting is triggered when someone leaves voicemail.<br/>
/// </summary>
public event MessageWaitingEventHandler MessageWaiting ;
/// <summary>
/// A NewCallerId is triggered when the caller id of a channel changes.<br/>
/// </summary>
public event NewCallerIdEventHandler NewCallerId ;
/// <summary>
/// A NewChannel is triggered when a new channel is created.<br/>
/// </summary>
public event NewChannelEventHandler NewChannel ;
/// <summary>
/// A NewExten is triggered when a channel is connected to a new extension.<br/>
/// </summary>
public event NewExtenEventHandler NewExten ;
/// <summary>
/// A NewState is triggered when the state of a channel has changed.<br/>
/// </summary>
public event NewStateEventHandler NewState ;
// public event OriginateEventHandler Originate;
/// <summary>
/// An OriginateFailure is triggered when the execution of an OriginateAction failed.
/// </summary>
// public event OriginateFailureEventHandler OriginateFailure;
/// <summary>
/// An OriginateSuccess is triggered when the execution of an OriginateAction succeeded.
/// </summary>
// public event OriginateSuccessEventHandler OriginateSuccess;
/// <summary>
/// An OriginateResponse is triggered when the execution of an Originate.
/// </summary>
public event OriginateResponseEventHandler OriginateResponse ;
/// <summary>
/// A ParkedCall is triggered when a channel is parked (in this case no
/// action id is set) and in response to a ParkedCallsAction.<br/>
/// </summary>
public event ParkedCallEventHandler ParkedCall ;
/// <summary>
/// A ParkedCallGiveUp is triggered when a channel that has been parked is hung up.<br/>
/// </summary>
public event ParkedCallGiveUpEventHandler ParkedCallGiveUp ;
/// <summary>
/// A ParkedCallsComplete is triggered after all parked calls have been reported in response to a ParkedCallsAction.
/// </summary>
public event ParkedCallsCompleteEventHandler ParkedCallsComplete ;
/// <summary>
/// A ParkedCallTimeOut is triggered when call parking times out for a given channel.<br/>
/// </summary>
public event ParkedCallTimeOutEventHandler ParkedCallTimeOut ;
/// <summary>
/// A PeerEntry is triggered in response to a SIPPeersAction or SIPShowPeerAction and contains information about a peer.<br/>
/// </summary>
public event PeerEntryEventHandler PeerEntry ;
/// <summary>
/// A PeerlistComplete is triggered after the details of all peers has been reported in response to an SIPPeersAction or SIPShowPeerAction.<br/>
/// </summary>
public event PeerlistCompleteEventHandler PeerlistComplete ;
/// <summary>
/// A PeerStatus is triggered when a SIP or IAX client attempts to registrer at this asterisk server.<br/>
/// </summary>
public event PeerStatusEventHandler PeerStatus ;
2014-01-08 14:16:39 +00:00
/// <summary>
/// A QueueEntryEvent is triggered in response to a QueueStatusAction and contains information about an entry in a queue.
/// </summary>
public event QueueCallerAbandonEventHandler QueueCallerAbandon ;
2013-01-18 15:55:50 +00:00
/// <summary>
/// A QueueEntryEvent is triggered in response to a QueueStatusAction and contains information about an entry in a queue.
/// </summary>
public event QueueEntryEventHandler QueueEntry ;
/// <summary>
/// A QueueMemberAddedEvent is triggered when a queue member is added to a queue.
/// </summary>
public event QueueMemberAddedEventHandler QueueMemberAdded ;
/// <summary>
/// A QueueMemberEvent is triggered in response to a QueueStatusAction and contains information about a member of a queue.
/// </summary>
public event QueueMemberEventHandler QueueMember ;
2016-10-19 07:20:23 +00:00
/// <summary>
/// A QueueMemberPausedEvent is triggered when a queue member is paused or unpaused.
/// <b>Replaced by : </b> <see cref="QueueMemberPauseEvent"/> since <see href="https://wiki.asterisk.org/wiki/display/AST/Asterisk+12+Documentation" target="_blank" alt="Asterisk 12 wiki docs">Asterisk 12</see>.<br/>
/// <b>Removed since : </b> <see href="https://wiki.asterisk.org/wiki/display/AST/Asterisk+13+Documentation" target="_blank" alt="Asterisk 13 wiki docs">Asterisk 13</see>.<br/>
/// </summary>
public event QueueMemberPausedEventHandler QueueMemberPaused ;
2013-01-18 15:55:50 +00:00
/// <summary>
/// A QueueMemberRemovedEvent is triggered when a queue member is removed from a queue.
/// </summary>
public event QueueMemberRemovedEventHandler QueueMemberRemoved ;
/// <summary>
/// A QueueMemberStatusEvent shows the status of a QueueMemberEvent.
/// </summary>
public event QueueMemberStatusEventHandler QueueMemberStatus ;
/// <summary>
/// A QueueParamsEvent is triggered in response to a QueueStatusAction and contains the parameters of a queue.
/// </summary>
public event QueueParamsEventHandler QueueParams ;
/// <summary>
/// A QueueStatusCompleteEvent is triggered after the state of all queues has been reported in response to a QueueStatusAction.
/// </summary>
public event QueueStatusCompleteEventHandler QueueStatusComplete ;
/// <summary>
/// A Registry is triggered when this asterisk server attempts to register
/// as a client at another SIP or IAX server.<br/>
/// </summary>
public event RegistryEventHandler Registry ;
/// <summary>
/// A RenameEvent is triggered when the name of a channel is changed.
/// </summary>
public event RenameEventHandler Rename ;
/// <summary>
/// A StatusCompleteEvent is triggered after the state of all channels has been reported in response to a StatusAction.
/// </summary>
public event StatusCompleteEventHandler StatusComplete ;
/// <summary>
/// A StatusEvent is triggered for each active channel in response to a StatusAction.
/// </summary>
public event StatusEventHandler Status ;
2014-01-08 14:16:39 +00:00
/// <summary>
///
/// </summary>
public event TransferEventHandler Transfer ;
2013-01-18 15:55:50 +00:00
/// <summary>
/// An UnholdEvent is triggered by the SIP channel driver when a channel is no longer put on hold.
/// </summary>
public event UnholdEventHandler Unhold ;
/// <summary>
/// An UnlinkEvent is triggered when a link between two voice channels is discontinued, for example, just before call completion.
/// </summary>
public event UnlinkEventHandler Unlink ;
/// <summary>
/// A UnparkedCallEvent is triggered when a channel that has been parked is resumed.
/// </summary>
public event UnparkedCallEventHandler UnparkedCall ;
/// <summary>
/// A ZapShowChannelsEvent is triggered on UserEvent in dialplan.
/// </summary>
public event UserEventHandler UserEvents ;
/// <summary>
/// A ZapShowChannelsCompleteEvent is triggered after the state of all zap channels has been reported in response to a ZapShowChannelsAction.
/// </summary>
public event ZapShowChannelsCompleteEventHandler ZapShowChannelsComplete ;
/// <summary>
/// A ZapShowChannelsEvent is triggered in response to a ZapShowChannelsAction and shows the state of a zap channel.
/// </summary>
public event ZapShowChannelsEventHandler ZapShowChannels ;
/// <summary>
/// A ConnectionState is triggered after Connect/Disconnect/Reload/Shutdown events.
/// </summary>
public event ConnectionStateEventHandler ConnectionState ;
2014-01-08 14:16:39 +00:00
/// <summary>
/// When a variable is set
/// </summary>
public event VarSetEventHandler VarSet ;
2013-08-21 10:31:26 +00:00
2014-01-08 14:16:39 +00:00
/// <summary>
/// AgiExec is execute
/// </summary>
public event AGIExecHandler AGIExec ;
2014-01-03 16:39:46 +00:00
2014-01-08 14:16:39 +00:00
/// <summary>
/// This event is sent when the first user requests a conference and it is instantiated
/// </summary>
public event ConfbridgeStartEventHandler ConfbridgeStart ;
2014-01-03 16:39:46 +00:00
2014-01-08 14:16:39 +00:00
/// <summary>
/// This event is sent when a user joins a conference - either one already in progress or as the first user to join a newly instantiated bridge.
/// </summary>
public event ConfbridgeJoinEventHandler ConfbridgeJoin ;
2014-01-03 16:39:46 +00:00
2014-01-08 14:16:39 +00:00
/// <summary>
/// This event is sent when a user leaves a conference.
/// </summary>
public event ConfbridgeLeaveEventHandler ConfbridgeLeave ;
2014-01-03 16:39:46 +00:00
2014-01-08 14:16:39 +00:00
/// <summary>
/// This event is sent when the last user leaves a conference and it is torn down.
/// </summary>
public event ConfbridgeEndEventHandler ConfbridgeEnd ;
2014-01-03 16:39:46 +00:00
2014-01-08 14:16:39 +00:00
/// <summary>
/// This event is sent when the conference detects that a user has either begin or stopped talking.
/// </summary>
public event ConfbridgeTalkingEventHandler ConfbridgeTalking ;
2014-01-03 16:39:46 +00:00
2014-04-07 10:35:39 +00:00
/// <summary>
///
/// </summary>
public event FailedACLEventHandler FailedACL ;
2015-03-16 20:48:35 +00:00
public event AttendedTransferEventHandler AttendedTransfer ;
public event BlindTransferEventHandler BlindTransfer ;
public event BridgeCreateEventHandler BridgeCreate ;
public event BridgeDestroyEventHandler BridgeDestroy ;
public event BridgeEnterEventHandler BridgeEnter ;
public event BridgeLeaveEventHandler BridgeLeave ;
2016-09-20 13:29:46 +00:00
/// <summary>
/// Raised when a dial action has started.<br/>
/// </summary>
public event DialBeginEventHandler DialBegin ;
2016-09-21 07:04:36 +00:00
/// <summary>
/// Raised when a dial action has completed.<br/>
/// </summary>
public event DialEndEventHandler DialEnd ;
/// <summary>
/// Raised when a caller joins a Queue.<br/>
/// </summary>
public event QueueCallerJoinEventHandler QueueCallerJoin ;
/// <summary>
/// Raised when a caller leaves a Queue.<br/>
/// </summary>
public event QueueCallerLeaveEventHandler QueueCallerLeave ;
2016-10-18 14:07:25 +00:00
/// <summary>
/// A QueueMemberPauseEvent is triggered when a queue member is paused or unpaused.<br />
2016-10-19 07:20:23 +00:00
/// <b>Available since : </b> <see href="https://wiki.asterisk.org/wiki/display/AST/Asterisk+12+Documentation" target="_blank" alt="Asterisk 12 wiki docs">Asterisk 12</see>.
2016-10-18 14:07:25 +00:00
/// </summary>
public event QueueMemberPauseEventHandler QueueMemberPause ;
2017-05-03 15:00:27 +00:00
/// <summary>
/// A ChallengeResponseFailed is triggered when a request's attempt to authenticate has been challenged, and the request failed the authentication challenge.
/// </summary>
public event ChallengeResponseFailedEventHandler ChallengeResponseFailed ;
/// <summary>
/// A InvalidAccountID is triggered when a request fails an authentication check due to an invalid account ID.
/// </summary>
public event InvalidAccountIDEventHandler InvalidAccountID ;
/// <summary>
/// A DeviceStateChanged is triggered when a device state changes.
/// </summary>
public event DeviceStateChangedEventHandler DeviceStateChanged ;
/// <summary>
/// A ChallengeSent is triggered when an Asterisk service sends an authentication challenge to a request..
/// </summary>
public event ChallengeSentEventHandler ChallengeSent ;
/// <summary>
/// A SuccessfulAuth is triggered when a request successfully authenticates with a service.
/// </summary>
public event SuccessfulAuthEventHandler SuccessfulAuth ;
2016-10-18 14:07:25 +00:00
2016-09-20 13:29:46 +00:00
#endregion
2013-01-18 15:55:50 +00:00
2016-09-20 13:29:46 +00:00
#region Constructor - ManagerConnection ( )
/// <summary> Creates a new instance.</summary>
public ManagerConnection ( )
2013-01-18 15:55:50 +00:00
{
callerThread = Thread . CurrentThread ;
socketEncoding = Encoding . ASCII ;
responseHandlers = new Dictionary < int , IResponseHandler > ( ) ;
pingHandlers = new Dictionary < int , IResponseHandler > ( ) ;
responseEventHandlers = new Dictionary < int , IResponseHandler > ( ) ;
registeredEventClasses = new Dictionary < int , ConstructorInfo > ( ) ;
Helper . RegisterBuiltinEventClasses ( registeredEventClasses ) ;
registeredEventHandlers = new Dictionary < int , int > ( ) ;
#region Event mapping table
Helper . RegisterEventHandler ( registeredEventHandlers , 0 , typeof ( AgentCallbackLoginEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 1 , typeof ( AgentCallbackLogoffEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 2 , typeof ( AgentCalledEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 3 , typeof ( AgentCompleteEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 4 , typeof ( AgentConnectEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 5 , typeof ( AgentDumpEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 6 , typeof ( AgentLoginEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 7 , typeof ( AgentLogoffEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 8 , typeof ( AgentsCompleteEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 9 , typeof ( AgentsEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 10 , typeof ( AlarmClearEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 11 , typeof ( AlarmEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 12 , typeof ( CdrEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 14 , typeof ( DBGetResponseEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 15 , typeof ( DialEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 17 , typeof ( DNDStateEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 18 , typeof ( ExtensionStatusEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 19 , typeof ( HangupEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 20 , typeof ( HoldedCallEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 21 , typeof ( HoldEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 22 , typeof ( JoinEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 23 , typeof ( LeaveEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 24 , typeof ( LinkEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 25 , typeof ( LogChannelEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 26 , typeof ( MeetmeJoinEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 27 , typeof ( MeetmeLeaveEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 28 , typeof ( MeetmeTalkingEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 29 , typeof ( MessageWaitingEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 30 , typeof ( NewCallerIdEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 31 , typeof ( NewChannelEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 32 , typeof ( NewExtenEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 33 , typeof ( NewStateEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 34 , typeof ( OriginateResponseEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 35 , typeof ( ParkedCallEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 36 , typeof ( ParkedCallGiveUpEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 37 , typeof ( ParkedCallsCompleteEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 38 , typeof ( ParkedCallTimeOutEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 39 , typeof ( PeerEntryEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 40 , typeof ( PeerlistCompleteEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 41 , typeof ( PeerStatusEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 42 , typeof ( QueueEntryEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 43 , typeof ( QueueMemberAddedEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 44 , typeof ( QueueMemberEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 45 , typeof ( QueueMemberPausedEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 46 , typeof ( QueueMemberRemovedEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 47 , typeof ( QueueMemberStatusEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 48 , typeof ( QueueParamsEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 49 , typeof ( QueueStatusCompleteEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 50 , typeof ( RegistryEvent ) ) ;
2014-01-08 14:16:39 +00:00
Helper . RegisterEventHandler ( registeredEventHandlers , 51 , typeof ( QueueCallerAbandonEvent ) ) ;
2013-01-18 15:55:50 +00:00
Helper . RegisterEventHandler ( registeredEventHandlers , 52 , typeof ( RenameEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 54 , typeof ( StatusCompleteEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 55 , typeof ( StatusEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 56 , typeof ( UnholdEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 57 , typeof ( UnlinkEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 58 , typeof ( UnparkedCallEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 59 , typeof ( UserEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 60 , typeof ( ZapShowChannelsCompleteEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 61 , typeof ( ZapShowChannelsEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 62 , typeof ( ConnectEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 62 , typeof ( DisconnectEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 62 , typeof ( ReloadEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 62 , typeof ( ShutdownEvent ) ) ;
2014-01-08 14:16:39 +00:00
Helper . RegisterEventHandler ( registeredEventHandlers , 63 , typeof ( BridgeEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 64 , typeof ( TransferEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 65 , typeof ( DTMFEvent ) ) ;
2013-03-15 19:26:36 +00:00
2014-01-08 14:16:39 +00:00
Helper . RegisterEventHandler ( registeredEventHandlers , 70 , typeof ( VarSetEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 80 , typeof ( AGIExecEvent ) ) ;
2014-01-03 16:39:46 +00:00
2014-04-07 10:35:39 +00:00
Helper . RegisterEventHandler ( registeredEventHandlers , 81 , typeof ( ConfbridgeStartEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 82 , typeof ( ConfbridgeJoinEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 83 , typeof ( ConfbridgeLeaveEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 84 , typeof ( ConfbridgeEndEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 85 , typeof ( ConfbridgeTalkingEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 86 , typeof ( FailedACLEvent ) ) ;
2013-08-21 10:31:26 +00:00
2015-03-16 20:48:35 +00:00
Helper . RegisterEventHandler ( registeredEventHandlers , 87 , typeof ( AttendedTransferEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 88 , typeof ( BridgeCreateEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 89 , typeof ( BridgeDestroyEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 90 , typeof ( BridgeEnterEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 91 , typeof ( BridgeLeaveEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 92 , typeof ( BlindTransferEvent ) ) ;
2016-09-20 13:29:46 +00:00
Helper . RegisterEventHandler ( registeredEventHandlers , 93 , typeof ( DialBeginEvent ) ) ;
2016-09-21 07:04:36 +00:00
Helper . RegisterEventHandler ( registeredEventHandlers , 94 , typeof ( DialEndEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 95 , typeof ( QueueCallerJoinEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 96 , typeof ( QueueCallerLeaveEvent ) ) ;
2016-10-18 14:07:25 +00:00
Helper . RegisterEventHandler ( registeredEventHandlers , 97 , typeof ( QueueMemberPauseEvent ) ) ;
2017-05-03 15:00:27 +00:00
Helper . RegisterEventHandler ( registeredEventHandlers , 98 , typeof ( ChallengeResponseFailedEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 99 , typeof ( InvalidAccountIDEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 100 , typeof ( DeviceStateChangeEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 101 , typeof ( ChallengeSentEvent ) ) ;
Helper . RegisterEventHandler ( registeredEventHandlers , 102 , typeof ( SuccessfulAuthEvent ) ) ;
2013-01-18 15:55:50 +00:00
2016-09-20 13:29:46 +00:00
#endregion
2013-01-18 15:55:50 +00:00
2016-09-20 13:29:46 +00:00
this . internalEvent + = new ManagerEventHandler ( internalEventHandler ) ;
2013-01-18 15:55:50 +00:00
}
#endregion
#region Constructor - ManagerConnection ( hostname , port , username , password )
/// <summary>
/// Creates a new instance with the given connection parameters.
/// </summary>
/// <param name="hostname">the hosname of the Asterisk server to connect to.</param>
/// <param name="port">the port where Asterisk listens for incoming Manager API connections, usually 5038.</param>
/// <param name="username">the username to use for login</param>
/// <param name="password">the password to use for login</param>
public ManagerConnection ( string hostname , int port , string username , string password )
: this ( )
{
this . hostname = hostname ;
this . port = port ;
this . username = username ;
this . password = password ;
}
#endregion
#region Constructor - ManagerConnection ( hostname , port , username , password , Encoding socketEncoding )
/// <summary>
/// Creates a new instance with the given connection parameters.
/// </summary>
/// <param name="hostname">the hosname of the Asterisk server to connect to.</param>
/// <param name="port">the port where Asterisk listens for incoming Manager API connections, usually 5038.</param>
/// <param name="username">the username to use for login</param>
/// <param name="password">the password to use for login</param>
/// <param name="socketEncoding">text encoding to asterisk input/output stream</param>
public ManagerConnection ( string hostname , int port , string username , string password , Encoding encoding )
: this ( )
{
this . hostname = hostname ;
this . port = port ;
this . username = username ;
this . password = password ;
this . socketEncoding = encoding ;
}
#endregion
/// <summary>
/// Default Fast Reconnect retry counter.
/// </summary>
public int ReconnectRetryFast
{
get { return reconnectRetryFast ; }
set { reconnectRetryFast = value ; }
}
/// <summary> Default Maximum Reconnect retry counter.</summary>
public int ReconnectRetryMax
{
get { return reconnectRetryMax ; }
set { reconnectRetryMax = value ; }
}
/// <summary> Default Fast Reconnect interval in milliseconds.</summary>
public int ReconnectIntervalFast
{
get { return reconnectIntervalFast ; }
set { reconnectIntervalFast = value ; }
}
/// <summary> Default Slow Reconnect interval in milliseconds.</summary>
public int ReconnectIntervalMax
{
get { return reconnectIntervalMax ; }
set { reconnectIntervalMax = value ; }
}
#region CallerThread
internal Thread CallerThread
{
get { return callerThread ; }
}
#endregion
#region internalEventHandler ( object sender , ManagerEvent e )
private void internalEventHandler ( object sender , ManagerEvent e )
{
int eventHash = e . GetType ( ) . Name . GetHashCode ( ) ;
if ( registeredEventHandlers . ContainsKey ( eventHash ) )
{
switch ( registeredEventHandlers [ eventHash ] )
{
#region A - C
case 0 :
if ( AgentCallbackLogin ! = null )
{
AgentCallbackLogin ( this , ( AgentCallbackLoginEvent ) e ) ;
return ;
}
break ;
case 1 :
if ( AgentCallbackLogoff ! = null )
{
AgentCallbackLogoff ( this , ( AgentCallbackLogoffEvent ) e ) ;
return ;
}
break ;
case 2 :
if ( AgentCalled ! = null )
{
AgentCalled ( this , ( Event . AgentCalledEvent ) e ) ;
return ;
}
break ;
case 3 :
if ( AgentComplete ! = null )
{
AgentComplete ( this , ( Event . AgentCompleteEvent ) e ) ;
return ;
}
break ;
case 4 :
if ( AgentConnect ! = null )
{
AgentConnect ( this , ( Event . AgentConnectEvent ) e ) ;
return ;
}
break ;
case 5 :
if ( AgentDump ! = null )
{
AgentDump ( this , ( Event . AgentDumpEvent ) e ) ;
return ;
}
break ;
case 6 :
if ( AgentLogin ! = null )
{
AgentLogin ( this , ( Event . AgentLoginEvent ) e ) ;
return ;
}
break ;
case 7 :
if ( AgentLogoff ! = null )
{
AgentLogoff ( this , ( Event . AgentLogoffEvent ) e ) ;
return ;
}
break ;
case 8 :
if ( AgentsComplete ! = null )
{
AgentsComplete ( this , ( AgentsCompleteEvent ) e ) ;
return ;
}
break ;
case 9 :
if ( Agents ! = null )
{
Agents ( this , ( AgentsEvent ) e ) ;
return ;
}
break ;
case 10 :
if ( AlarmClear ! = null )
{
AlarmClear ( this , ( AlarmClearEvent ) e ) ;
return ;
}
break ;
case 11 :
if ( Alarm ! = null )
{
Alarm ( this , ( AlarmEvent ) e ) ;
return ;
}
break ;
case 12 :
if ( Cdr ! = null )
{
Cdr ( this , ( CdrEvent ) e ) ;
return ;
}
break ;
#endregion
#region D - L
case 14 :
if ( DBGetResponse ! = null )
{
DBGetResponse ( this , ( DBGetResponseEvent ) e ) ;
return ;
}
break ;
case 15 :
if ( Dial ! = null )
{
Dial ( this , ( DialEvent ) e ) ;
return ;
}
break ;
case 17 :
if ( DNDState ! = null )
{
DNDState ( this , ( DNDStateEvent ) e ) ;
return ;
}
break ;
case 18 :
if ( ExtensionStatus ! = null )
{
ExtensionStatus ( this , ( ExtensionStatusEvent ) e ) ;
return ;
}
break ;
case 19 :
if ( Hangup ! = null )
{
Hangup ( this , ( HangupEvent ) e ) ;
return ;
}
break ;
case 20 :
if ( HoldedCall ! = null )
{
HoldedCall ( this , ( HoldedCallEvent ) e ) ;
return ;
}
break ;
case 21 :
if ( Hold ! = null )
{
Hold ( this , ( HoldEvent ) e ) ;
return ;
}
break ;
case 22 :
if ( Join ! = null )
{
Join ( this , ( JoinEvent ) e ) ;
return ;
}
break ;
case 23 :
if ( Leave ! = null )
{
Leave ( this , ( LeaveEvent ) e ) ;
return ;
}
break ;
case 24 :
if ( Link ! = null )
{
Link ( this , ( LinkEvent ) e ) ;
return ;
}
break ;
case 25 :
if ( LogChannel ! = null )
{
LogChannel ( this , ( LogChannelEvent ) e ) ;
return ;
}
break ;
#endregion
#region M - P
case 26 :
if ( MeetMeJoin ! = null )
{
MeetMeJoin ( this , ( MeetmeJoinEvent ) e ) ;
return ;
}
break ;
case 27 :
if ( MeetMeLeave ! = null )
{
MeetMeLeave ( this , ( MeetmeLeaveEvent ) e ) ;
return ;
}
break ;
case 28 :
if ( MeetMeTalking ! = null )
{
MeetMeTalking ( this , ( MeetmeTalkingEvent ) e ) ;
return ;
}
break ;
case 29 :
if ( MessageWaiting ! = null )
{
MessageWaiting ( this , ( MessageWaitingEvent ) e ) ;
return ;
}
break ;
case 30 :
if ( NewCallerId ! = null )
{
NewCallerId ( this , ( NewCallerIdEvent ) e ) ;
return ;
}
break ;
case 31 :
if ( NewChannel ! = null )
{
NewChannel ( this , ( NewChannelEvent ) e ) ;
return ;
}
break ;
case 32 :
if ( NewExten ! = null )
{
NewExten ( this , ( NewExtenEvent ) e ) ;
return ;
}
break ;
case 33 :
if ( NewState ! = null )
{
NewState ( this , ( NewStateEvent ) e ) ;
return ;
}
break ;
case 34 :
if ( OriginateResponse ! = null )
{
OriginateResponse ( this , ( OriginateResponseEvent ) e ) ;
return ;
}
break ;
case 35 :
if ( ParkedCall ! = null )
{
ParkedCall ( this , ( ParkedCallEvent ) e ) ;
return ;
}
break ;
case 36 :
if ( ParkedCallGiveUp ! = null )
{
ParkedCallGiveUp ( this , ( ParkedCallGiveUpEvent ) e ) ;
return ;
}
break ;
case 37 :
if ( ParkedCallsComplete ! = null )
{
ParkedCallsComplete ( this , ( ParkedCallsCompleteEvent ) e ) ;
return ;
}
break ;
case 38 :
if ( ParkedCallTimeOut ! = null )
{
ParkedCallTimeOut ( this , ( ParkedCallTimeOutEvent ) e ) ;
return ;
}
break ;
case 39 :
if ( PeerEntry ! = null )
{
PeerEntry ( this , ( PeerEntryEvent ) e ) ;
return ;
}
break ;
case 40 :
if ( PeerlistComplete ! = null )
{
PeerlistComplete ( this , ( PeerlistCompleteEvent ) e ) ;
return ;
}
break ;
case 41 :
if ( PeerStatus ! = null )
{
PeerStatus ( this , ( PeerStatusEvent ) e ) ;
return ;
}
break ;
#endregion
#region Q - Z
case 42 :
if ( QueueEntry ! = null )
{
QueueEntry ( this , ( QueueEntryEvent ) e ) ;
return ;
}
break ;
case 43 :
if ( QueueMemberAdded ! = null )
{
QueueMemberAdded ( this , ( QueueMemberAddedEvent ) e ) ;
return ;
}
break ;
case 44 :
if ( QueueMember ! = null )
{
QueueMember ( this , ( QueueMemberEvent ) e ) ;
return ;
}
break ;
case 45 :
if ( QueueMemberPaused ! = null )
{
QueueMemberPaused ( this , ( QueueMemberPausedEvent ) e ) ;
return ;
}
break ;
case 46 :
if ( QueueMemberRemoved ! = null )
{
QueueMemberRemoved ( this , ( QueueMemberRemovedEvent ) e ) ;
return ;
}
break ;
case 47 :
if ( QueueMemberStatus ! = null )
{
QueueMemberStatus ( this , ( QueueMemberStatusEvent ) e ) ;
return ;
}
break ;
case 48 :
if ( QueueParams ! = null )
{
QueueParams ( this , ( QueueParamsEvent ) e ) ;
return ;
}
break ;
case 49 :
if ( QueueStatusComplete ! = null )
{
QueueStatusComplete ( this , ( QueueStatusCompleteEvent ) e ) ;
return ;
}
break ;
case 50 :
if ( Registry ! = null )
{
Registry ( this , ( RegistryEvent ) e ) ;
return ;
}
break ;
2014-01-08 14:16:39 +00:00
case 51 :
if ( QueueCallerAbandon ! = null )
{
QueueCallerAbandon ( this , ( QueueCallerAbandonEvent ) e ) ;
return ;
}
break ;
2013-01-18 15:55:50 +00:00
case 52 :
if ( Rename ! = null )
{
Rename ( this , ( RenameEvent ) e ) ;
return ;
}
break ;
case 54 :
if ( StatusComplete ! = null )
{
StatusComplete ( this , ( StatusCompleteEvent ) e ) ;
return ;
}
break ;
case 55 :
if ( Status ! = null )
{
Status ( this , ( StatusEvent ) e ) ;
return ;
}
break ;
case 56 :
if ( Unhold ! = null )
{
Unhold ( this , ( UnholdEvent ) e ) ;
return ;
}
break ;
case 57 :
if ( Unlink ! = null )
{
Unlink ( this , ( UnlinkEvent ) e ) ;
return ;
}
break ;
case 58 :
if ( UnparkedCall ! = null )
{
UnparkedCall ( this , ( UnparkedCallEvent ) e ) ;
return ;
}
break ;
case 59 :
if ( UserEvents ! = null )
{
UserEvents ( this , ( UserEvent ) e ) ;
return ;
}
break ;
case 60 :
if ( ZapShowChannelsComplete ! = null )
{
ZapShowChannelsComplete ( this , ( ZapShowChannelsCompleteEvent ) e ) ;
return ;
}
break ;
case 61 :
if ( ZapShowChannels ! = null )
{
ZapShowChannels ( this , ( ZapShowChannelsEvent ) e ) ;
return ;
}
break ;
#endregion
case 62 :
if ( ConnectionState ! = null )
{
ConnectionState ( this , ( ConnectionStateEvent ) e ) ;
return ;
}
break ;
2014-01-08 14:16:39 +00:00
case 63 :
if ( Bridge ! = null )
{
Bridge ( this , ( BridgeEvent ) e ) ;
}
break ;
case 64 :
if ( Transfer ! = null )
{
Transfer ( this , ( TransferEvent ) e ) ;
}
break ;
case 65 :
if ( DTMF ! = null )
{
DTMF ( this , ( DTMFEvent ) e ) ;
}
break ;
case 70 :
if ( VarSet ! = null )
{
VarSet ( this , ( VarSetEvent ) e ) ;
}
break ;
case 80 :
if ( AGIExec ! = null )
{
AGIExec ( this , ( AGIExecEvent ) e ) ;
}
break ;
case 81 :
if ( ConfbridgeStart ! = null )
{
ConfbridgeStart ( this , ( ConfbridgeStartEvent ) e ) ;
}
break ;
case 82 :
if ( ConfbridgeJoin ! = null )
{
ConfbridgeJoin ( this , ( ConfbridgeJoinEvent ) e ) ;
}
break ;
case 83 :
if ( ConfbridgeLeave ! = null )
{
ConfbridgeLeave ( this , ( ConfbridgeLeaveEvent ) e ) ;
}
break ;
case 84 :
if ( ConfbridgeEnd ! = null )
{
ConfbridgeEnd ( this , ( ConfbridgeEndEvent ) e ) ;
}
break ;
case 85 :
if ( ConfbridgeTalking ! = null )
{
ConfbridgeTalking ( this , ( ConfbridgeTalkingEvent ) e ) ;
}
break ;
2014-04-07 10:35:39 +00:00
case 86 :
if ( FailedACL ! = null )
{
FailedACL ( this , ( FailedACLEvent ) e ) ;
}
2015-03-16 20:48:35 +00:00
break ;
case 87 :
if ( AttendedTransfer ! = null )
{
AttendedTransfer ( this , ( AttendedTransferEvent ) e ) ;
}
break ;
case 88 :
if ( BridgeCreate ! = null )
{
BridgeCreate ( this , ( BridgeCreateEvent ) e ) ;
}
break ;
case 89 :
if ( BridgeDestroy ! = null )
{
BridgeDestroy ( this , ( BridgeDestroyEvent ) e ) ;
}
break ;
case 90 :
if ( BridgeEnter ! = null )
{
BridgeEnter ( this , ( BridgeEnterEvent ) e ) ;
}
break ;
case 91 :
if ( BridgeLeave ! = null )
{
BridgeLeave ( this , ( BridgeLeaveEvent ) e ) ;
}
break ;
case 92 :
if ( BlindTransfer ! = null )
{
BlindTransfer ( this , ( BlindTransferEvent ) e ) ;
}
2014-04-07 10:35:39 +00:00
break ;
2016-09-20 13:29:46 +00:00
case 93 :
if ( DialBegin ! = null )
{
DialBegin ( this , ( DialBeginEvent ) e ) ;
}
break ;
2016-09-21 07:04:36 +00:00
case 94 :
if ( DialEnd ! = null )
{
DialEnd ( this , ( DialEndEvent ) e ) ;
}
break ;
case 95 :
if ( QueueCallerJoin ! = null )
{
QueueCallerJoin ( this , ( QueueCallerJoinEvent ) e ) ;
}
break ;
case 96 :
if ( QueueCallerLeave ! = null )
{
QueueCallerLeave ( this , ( QueueCallerLeaveEvent ) e ) ;
}
break ;
2016-10-18 14:07:25 +00:00
case 97 :
if ( QueueMemberPause ! = null )
{
QueueMemberPause ( this , ( QueueMemberPauseEvent ) e ) ;
}
break ;
2017-05-03 15:00:27 +00:00
case 98 :
if ( ChallengeResponseFailed ! = null )
{
ChallengeResponseFailed ( this , ( ChallengeResponseFailedEvent ) e ) ;
return ;
}
break ;
case 99 :
if ( InvalidAccountID ! = null )
{
InvalidAccountID ( this , ( InvalidAccountIDEvent ) e ) ;
return ;
}
break ;
case 100 :
if ( DeviceStateChanged ! = null )
{
DeviceStateChanged ( this , ( DeviceStateChangeEvent ) e ) ;
return ;
}
break ;
case 101 :
if ( ChallengeSent ! = null )
{
ChallengeSent ( this , ( ChallengeSentEvent ) e ) ;
return ;
}
break ;
case 102 :
if ( SuccessfulAuth ! = null )
{
SuccessfulAuth ( this , ( SuccessfulAuthEvent ) e ) ;
return ;
}
break ;
2016-09-20 13:29:46 +00:00
default :
2013-01-18 15:55:50 +00:00
if ( UnhandledEvent ! = null )
UnhandledEvent ( this , e ) ;
return ;
}
}
if ( fireAllEvents & & UnhandledEvent ! = null )
UnhandledEvent ( this , e ) ;
}
#endregion
#region FireAllEvents
/// <summary>
/// If this property set to <b>true</b> then ManagerConnection send all unassigned events to UnhandledEvent handler,<br/>
/// if set to <b>false</b> then all unassgned events lost and send only UnhandledEvent.<br/>
/// Default: <b>false</b>
/// </summary>
public bool FireAllEvents
{
get { return this . fireAllEvents ; }
set { this . fireAllEvents = value ; }
}
#endregion
#region PingInterval
/// <summary>
/// Timeout from Ping to Pong. If no Pong received send Disconnect event. Set to zero to disable.
/// </summary>
public int PingInterval
{
get { return pingInterval ; }
set { pingInterval = value ; }
}
#endregion
#region Hostname
/// <summary> Sets the hostname of the asterisk server to connect to.<br/>
2015-01-04 14:26:32 +00:00
/// Default is localhost.
2013-01-18 15:55:50 +00:00
/// </summary>
public string Hostname
{
get { return hostname ; }
set { hostname = value ; }
}
#endregion
#region Port
/// <summary>
/// Sets the port to use to connect to the asterisk server. This is the port
2015-01-04 14:26:32 +00:00
/// specified in asterisk's manager.conf file.<br/>
2013-01-18 15:55:50 +00:00
/// Default is 5038.
/// </summary>
public int Port
{
get { return port ; }
set { port = value ; }
}
#endregion
#region UserName
/// <summary>
/// Sets the username to use to connect to the asterisk server. This is the
2015-01-04 14:26:32 +00:00
/// username specified in asterisk's manager.conf file.
2013-01-18 15:55:50 +00:00
/// </summary>
public string Username
{
get { return username ; }
set { username = value ; }
}
#endregion
#region Password
/// <summary>
/// Sets the password to use to connect to the asterisk server. This is the
2015-01-04 14:26:32 +00:00
/// password specified in asterisk's manager.conf file.
2013-01-18 15:55:50 +00:00
/// </summary>
public string Password
{
get { return password ; }
set { password = value ; }
}
#endregion
#region DefaultResponseTimeout
/// <summary> Sets the time in milliseconds the synchronous method
/// will wait for a response before throwing a TimeoutException.<br/>
/// Default is 2000.
/// </summary>
public int DefaultResponseTimeout
{
get { return defaultResponseTimeout ; }
set { defaultResponseTimeout = value ; }
}
#endregion
#region DefaultEventTimeout
/// <summary> Sets the time in milliseconds the synchronous method
/// will wait for a response and the last response event before throwing a TimeoutException.<br/>
/// Default is 5000.
/// </summary>
public int DefaultEventTimeout
{
get { return defaultEventTimeout ; }
set { defaultEventTimeout = value ; }
}
#endregion
#region SleepTime
/// <summary> Sets the time in milliseconds the synchronous methods
/// SendAction(Action.ManagerAction) and
/// SendAction(Action.ManagerAction, long) will sleep between two checks
/// for the arrival of a response. This value should be rather small.<br/>
/// The sleepTime attribute is also used when checking for the protocol
/// identifer.<br/>
/// Default is 50.
/// </summary>
/// <deprecated> this has been replaced by an interrupt based response checking approach.</deprecated>
public int SleepTime
{
get { return sleepTime ; }
set { sleepTime = value ; }
}
#endregion
#region KeepAliveAfterAuthenticationFailure
2015-01-04 14:26:32 +00:00
/// <summary> Set to true to try reconnecting to ther asterisk serve
2013-01-18 15:55:50 +00:00
/// even if the reconnection attempt threw an AuthenticationFailedException.<br/>
2015-01-04 14:26:32 +00:00
/// Default is false.
2013-01-18 15:55:50 +00:00
/// </summary>
public bool KeepAliveAfterAuthenticationFailure
{
set { keepAliveAfterAuthenticationFailure = value ; }
get { return keepAliveAfterAuthenticationFailure ; }
}
#endregion
#region KeepAlive
/// <summary>
/// Should we attempt to reconnect when the connection is lost?<br/>
2015-01-04 14:26:32 +00:00
/// This is set to true after successful login and to false after logoff or after an authentication failure when keepAliveAfterAuthenticationFailure is false.
2013-01-18 15:55:50 +00:00
/// </summary>
public bool KeepAlive
{
get { return keepAlive ; }
set { keepAlive = value ; }
}
#endregion
#region SocketEncoding
/// <summary>
/// Socket Encoding - default ASCII
/// </summary>
public Encoding SocketEncoding
{
get { return socketEncoding ; }
set { socketEncoding = value ; }
}
#endregion
#region Version
public string Version
{
get { return version ; }
}
#endregion
#region AsteriskVersion
public AsteriskVersion AsteriskVersion
{
get { return asteriskVersion ; }
}
#endregion
#region login ( timeout )
/// <summary>
/// Does the real login, following the steps outlined below.<br/>
/// Connects to the asterisk server by calling connect() if not already connected<br/>
/// Waits until the protocol identifier is received. This is checked every sleepTime ms but not longer than timeout ms in total.<br/>
/// Sends a ChallengeAction requesting a challenge for authType MD5.<br/>
/// When the ChallengeResponse is received a LoginAction is sent using the calculated key (MD5 hash of the password appended to the received challenge).<br/>
/// </summary>
/// <param name="timeout">the maximum time to wait for the protocol identifier (in ms)</param>
/// <throws>
/// AuthenticationFailedException if username or password are incorrect and the login action returns an error or if the MD5
/// hash cannot be computed. The connection is closed in this case.
/// </throws>
/// <throws>
/// TimeoutException if a timeout occurs either while waiting for the
/// protocol identifier or when sending the challenge or login
/// action. The connection is closed in this case.
/// </throws>
private void login ( int timeout )
{
enableEvents = false ;
if ( reconnected )
{
#if LOGGER
logger . Error ( "Login during reconnect state." ) ;
#endif
throw new AuthenticationFailedException ( "Unable login during reconnect state." ) ;
}
reconnectEnable = false ;
DateTime start = DateTime . Now ;
do
{
if ( connect ( ) )
{
// Increase delay after connection up to 500 ms
Thread . Sleep ( 10 * sleepTime ) ; // 200 milliseconds delay
}
try
{
Thread . Sleep ( 4 * sleepTime ) ; // 200 milliseconds delay
}
catch
{ }
if ( string . IsNullOrEmpty ( protocolIdentifier ) & & timeout > 0 & & Helper . GetMillisecondsFrom ( start ) > timeout )
{
disconnect ( true ) ;
throw new TimeoutException ( "Timeout waiting for protocol identifier" ) ;
}
} while ( string . IsNullOrEmpty ( protocolIdentifier ) ) ;
ChallengeAction challengeAction = new ChallengeAction ( ) ;
Response . ManagerResponse response = SendAction ( challengeAction , defaultResponseTimeout * 2 ) ;
if ( response is ChallengeResponse )
{
ChallengeResponse challengeResponse = ( ChallengeResponse ) response ;
string key , challenge = challengeResponse . Challenge ;
try
{
Util . MD5Support md = Util . MD5Support . GetInstance ( ) ;
if ( challenge ! = null )
md . Update ( UTF8Encoding . UTF8 . GetBytes ( challenge ) ) ;
if ( password ! = null )
md . Update ( UTF8Encoding . UTF8 . GetBytes ( password ) ) ;
key = Helper . ToHexString ( md . DigestData ) ;
}
catch ( Exception ex )
{
disconnect ( true ) ;
#if LOGGER
logger . Error ( "Unable to create login key using MD5 Message Digest." , ex ) ;
#endif
throw new AuthenticationFailedException ( "Unable to create login key using MD5 Message Digest." , ex ) ;
}
Action . LoginAction loginAction = new Action . LoginAction ( username , "MD5" , key ) ;
Response . ManagerResponse loginResponse = SendAction ( loginAction ) ;
if ( loginResponse is Response . ManagerError )
{
disconnect ( true ) ;
throw new AuthenticationFailedException ( loginResponse . Message ) ;
}
// successfully logged in so assure that we keep trying to reconnect when disconnected
reconnectEnable = keepAlive ;
#if LOGGER
logger . Info ( "Successfully logged in" ) ;
#endif
asteriskVersion = determineVersion ( ) ;
#if LOGGER
logger . Info ( "Determined Asterisk version: " + asteriskVersion ) ;
#endif
enableEvents = true ;
ConnectEvent ce = new ConnectEvent ( this ) ;
ce . ProtocolIdentifier = this . protocolIdentifier ;
DispatchEvent ( ce ) ;
}
else if ( response is ManagerError )
throw new ManagerException ( "Unable login to Asterisk - " + response . Message ) ;
else
throw new ManagerException ( "Unknown response during login to Asterisk - " + response . GetType ( ) . Name + " with message " + response . Message ) ;
}
#endregion
#region determineVersion ( )
protected internal AsteriskVersion determineVersion ( )
{
Response . ManagerResponse response ;
response = SendAction ( new Action . CommandAction ( "core show version" ) , defaultResponseTimeout * 2 ) ;
if ( response is Response . CommandResponse )
{
foreach ( string line in ( ( Response . CommandResponse ) response ) . Result )
{
foreach ( Match m in Common . ASTERISK_VERSION . Matches ( line ) )
{
if ( m . Groups . Count > = 2 )
{
version = m . Groups [ 1 ] . Value ;
2020-01-21 11:33:47 +00:00
if ( version . StartsWith ( "1.4." ) )
{
VAR_DELIMITER = new char [ ] { '|' } ;
return AsteriskVersion . ASTERISK_1_4 ;
}
else if ( version . StartsWith ( "1.6." ) )
{
VAR_DELIMITER = new char [ ] { '|' } ;
return Manager . AsteriskVersion . ASTERISK_1_6 ;
}
else if ( version . StartsWith ( "1.8." ) )
{
VAR_DELIMITER = new char [ ] { '|' } ;
return Manager . AsteriskVersion . ASTERISK_1_8 ;
}
else if ( version . StartsWith ( "10." ) )
{
VAR_DELIMITER = new char [ ] { '|' } ;
return Manager . AsteriskVersion . ASTERISK_10 ;
}
else if ( version . StartsWith ( "11." ) )
{
VAR_DELIMITER = new char [ ] { ',' } ;
return Manager . AsteriskVersion . ASTERISK_11 ;
}
else if ( version . StartsWith ( "12." ) )
{
VAR_DELIMITER = new char [ ] { ',' } ;
return Manager . AsteriskVersion . ASTERISK_12 ;
}
else if ( version . StartsWith ( "13." ) )
{
VAR_DELIMITER = new char [ ] { ',' } ;
return Manager . AsteriskVersion . ASTERISK_13 ;
}
else if ( version . StartsWith ( "14." ) )
{
VAR_DELIMITER = new char [ ] { ',' } ;
return Manager . AsteriskVersion . ASTERISK_14 ;
}
else if ( version . StartsWith ( "15." ) )
{
VAR_DELIMITER = new char [ ] { ',' } ;
return Manager . AsteriskVersion . ASTERISK_15 ;
}
else if ( version . StartsWith ( "16." ) )
{
VAR_DELIMITER = new char [ ] { ',' } ;
return Manager . AsteriskVersion . ASTERISK_16 ;
}
else if ( version . StartsWith ( "17." ) )
{
VAR_DELIMITER = new char [ ] { ',' } ;
return Manager . AsteriskVersion . ASTERISK_17 ;
}
else if ( version . IndexOf ( '.' ) > = 2 )
{
VAR_DELIMITER = new char [ ] { ',' } ;
return Manager . AsteriskVersion . ASTERISK_Newer ;
}
else
throw new ManagerException ( "Unknown Asterisk version " + version ) ;
2013-01-18 15:55:50 +00:00
}
}
}
}
Response . ManagerResponse showVersionFilesResponse = SendAction ( new Action . CommandAction ( "show version files" ) , defaultResponseTimeout * 2 ) ;
if ( showVersionFilesResponse is Response . CommandResponse )
{
IList showVersionFilesResult = ( ( Response . CommandResponse ) showVersionFilesResponse ) . Result ;
if ( showVersionFilesResult ! = null & & showVersionFilesResult . Count > 0 )
{
string line1 ;
line1 = ( string ) showVersionFilesResult [ 0 ] ;
if ( line1 ! = null & & line1 . StartsWith ( "File" ) )
2020-01-21 11:33:47 +00:00
{
VAR_DELIMITER = new char [ ] { '|' } ;
2013-01-18 15:55:50 +00:00
return AsteriskVersion . ASTERISK_1_2 ;
2020-01-21 11:33:47 +00:00
}
2013-01-18 15:55:50 +00:00
}
}
return AsteriskVersion . ASTERISK_1_0 ;
}
#endregion
#region connect ( )
protected internal bool connect ( )
{
bool result = false ;
bool startReader = false ;
lock ( lockSocket )
{
if ( mrSocket = = null )
{
#if LOGGER
logger . Info ( "Connecting to {0}:{1}" , hostname , port ) ;
#endif
try
{
mrSocket = new SocketConnection ( hostname , port , socketEncoding ) ;
result = mrSocket . IsConnected ;
}
#if LOGGER
catch ( Exception ex )
{
logger . Info ( "Connect - Exception : {0}" , ex . Message ) ;
#else
catch
{
#endif
result = false ;
}
2014-06-12 08:08:52 +00:00
if ( result )
{
if ( mrReader = = null )
{
mrReader = new ManagerReader ( this ) ;
mrReaderThread = new Thread ( mrReader . Run ) { IsBackground = true , Name = "ManagerReader-" + DateTime . Now . Second } ;
mrReader . Socket = mrSocket ;
startReader = true ;
}
else
{
mrReader . Socket = mrSocket ;
}
mrReader . Reinitialize ( ) ;
}
else
{
mrSocket = null ;
}
2013-01-18 15:55:50 +00:00
}
}
2014-06-12 08:08:52 +00:00
if ( startReader )
{
mrReaderThread . Start ( ) ;
}
2013-01-18 15:55:50 +00:00
return IsConnected ( ) ;
}
#endregion
#region disconnect ( )
/// <summary> Closes the socket connection.</summary>
private void disconnect ( bool withDie )
{
lock ( lockSocket )
{
if ( withDie )
{
reconnectEnable = false ;
reconnected = false ;
enableEvents = true ;
}
if ( mrReader ! = null )
{
if ( withDie )
{
mrReader . Die = true ;
mrReader = null ;
}
else
mrReader . Socket = null ;
}
if ( this . mrSocket ! = null )
{
mrSocket . Close ( ) ;
mrSocket = null ;
}
responseEventHandlers . Clear ( ) ;
responseHandlers . Clear ( ) ;
pingHandlers . Clear ( ) ;
}
}
#endregion
#region reconnect ( bool init )
/// <summary>
/// Reconnects to the asterisk server when the connection is lost.<br/>
2015-01-04 14:26:32 +00:00
/// While keepAlive is true we will try to reconnect.
2013-01-18 15:55:50 +00:00
/// Reconnection attempts will be stopped when the logoff() method
/// is called or when the login after a successful reconnect results in an
/// AuthenticationFailedException suggesting that the manager
/// credentials have changed and keepAliveAfterAuthenticationFailure is not set.<br/>
/// This method is called when a DisconnectEvent is received from the reader.
/// </summary>
private void reconnect ( bool init )
{
#if LOGGER
logger . Warning ( "reconnect (init: {0}), reconnectCount:{1}" , init , reconnectCount ) ;
#endif
if ( init )
reconnectCount = 0 ;
else if ( reconnectCount + + > reconnectRetryMax )
reconnectEnable = false ;
if ( reconnectEnable )
{
#if LOGGER
logger . Warning ( "Try reconnect." ) ;
#endif
enableEvents = false ;
reconnected = true ;
disconnect ( false ) ;
int retryCount = 0 ;
while ( reconnectEnable & & ! mrReader . Die )
{
if ( retryCount > = reconnectRetryMax )
reconnectEnable = false ;
else
{
try
{
if ( retryCount < reconnectRetryFast )
{
// Try to reconnect quite fast for the first times
// this succeeds if the server has just been restarted
#if LOGGER
logger . Info ( "Reconnect delay : {0}, retry : {1}" , reconnectIntervalFast , retryCount ) ;
#endif
Thread . Sleep ( reconnectIntervalFast ) ;
}
else
{
// slow down after unsuccessful attempts assuming a shutdown of the server
#if LOGGER
logger . Info ( "Reconnect delay : {0}, retry : {1}" , reconnectIntervalMax , retryCount ) ;
#endif
Thread . Sleep ( reconnectIntervalMax ) ;
}
}
catch ( ThreadInterruptedException )
{
continue ;
}
#if LOGGER
catch ( Exception ex )
{
logger . Info ( "Reconnect delay exception : " , ex . Message ) ;
#else
catch
{
#endif
continue ;
}
try
{
#if LOGGER
logger . Info ( "Try connect." ) ;
#endif
if ( connect ( ) )
break ;
}
#if LOGGER
catch ( Exception ex )
{
logger . Info ( "Connect exception : " , ex . Message ) ;
#else
catch
{
#endif
}
retryCount + + ;
}
}
}
if ( ! reconnectEnable )
{
#if LOGGER
logger . Info ( "Can't reconnect." ) ;
#endif
enableEvents = true ;
reconnected = false ;
disconnect ( true ) ;
fireEvent ( new DisconnectEvent ( this ) ) ;
}
}
#endregion
#region createInternalActionId ( )
/// <summary>
/// Creates a new unique internal action id based on the hash code of this connection and a sequence.
/// </summary>
private string createInternalActionId ( )
{
return this . GetHashCode ( ) + "_" + ( this . actionIdCount + + ) ;
}
#endregion
#region Login ( )
/// <summary>
/// Logs in to the Asterisk manager using asterisk's MD5 based
/// challenge/response protocol. The login is delayed until the protocol
/// identifier has been received by the reader.
/// </summary>
/// <throws> AuthenticationFailedException if the username and/or password are incorrect</throws>
/// <throws> TimeoutException if no response is received within the specified timeout period</throws>
/// <seealso cref="Action.ChallengeAction"/>
/// <seealso cref="Action.LoginAction"/>
public void Login ( )
{
login ( defaultResponseTimeout ) ;
}
/// <summary>
/// Log in to the Asterisk manager using asterisk's MD5 based
/// challenge/response protocol. The login is delayed until the protocol
/// identifier has been received by the reader.
/// </summary>
/// <param name="timeout">Timeout in milliseconds to login.</param>
public void Login ( int timeout )
{
login ( timeout ) ;
}
#endregion
#region IsConnected ( )
2015-01-04 14:26:32 +00:00
/// <summary> Returns true if there is a socket connection to the
/// asterisk server, false otherwise.
2013-01-18 15:55:50 +00:00
///
/// </summary>
2015-01-04 14:26:32 +00:00
/// <returns> true if there is a socket connection to the
/// asterisk server, false otherwise.
2013-01-18 15:55:50 +00:00
/// </returns>
public bool IsConnected ( )
{
bool result = false ;
lock ( lockSocket )
result = mrSocket ! = null & & mrSocket . IsConnected ;
return result ;
}
#endregion
#region Logoff ( )
/// <summary>
/// Sends a LogoffAction and disconnects from the server.
/// </summary>
public void Logoff ( )
{
lock ( lockSocket )
{
// stop reconnecting when we got disconnected
reconnectEnable = false ;
if ( mrReader ! = null & & mrSocket ! = null )
try
{
mrReader . IsLogoff = true ;
SendAction ( new Action . LogoffAction ( ) ) ;
}
catch
{ }
}
disconnect ( true ) ;
}
#endregion
#region SendAction ( action )
/// <summary>
/// Send Action with default timeout.
/// </summary>
/// <param name="action"></param>
/// <returns></returns>
public Response . ManagerResponse SendAction ( Action . ManagerAction action )
{
return SendAction ( action , defaultResponseTimeout ) ;
}
#endregion
#region SendAction ( action , timeout )
/// <summary>
/// Send action ans with timeout (milliseconds)
/// </summary>
/// <param name="action">action to send</param>
/// <param name="timeout">timeout in milliseconds</param>
/// <returns></returns>
public Response . ManagerResponse SendAction ( ManagerAction action , int timeOut )
{
AutoResetEvent autoEvent = new AutoResetEvent ( false ) ;
ResponseHandler handler = new ResponseHandler ( action , autoEvent ) ;
int hash = SendAction ( action , handler ) ;
bool result = autoEvent . WaitOne ( timeOut < = 0 ? - 1 : timeOut , true ) ;
RemoveResponseHandler ( handler ) ;
if ( result )
return handler . Response ;
throw new TimeoutException ( "Timeout waiting for response to " + action . Action ) ;
}
#endregion
#region SendAction ( action , responseHandler )
public int SendAction ( ManagerAction action , ResponseHandler responseHandler )
{
if ( action = = null )
throw new ArgumentException ( "Unable to send action: action is null." ) ;
if ( mrSocket = = null )
throw new SystemException ( "Unable to send " + action . Action + " action: not connected." ) ;
// if the responseHandler is null the user is obviously not interested in the response, thats fine.
string internalActionId = string . Empty ;
if ( responseHandler ! = null )
{
internalActionId = createInternalActionId ( ) ;
responseHandler . Hash = internalActionId . GetHashCode ( ) ;
AddResponseHandler ( responseHandler ) ;
}
SendToAsterisk ( action , internalActionId ) ;
return responseHandler ! = null ? responseHandler . Hash : 0 ;
}
#endregion
#region SendEventGeneratingAction ( action )
public ResponseEvents SendEventGeneratingAction ( ManagerActionEvent action )
{
return SendEventGeneratingAction ( action , defaultEventTimeout ) ;
}
#endregion
#region SendEventGeneratingAction ( action , timeout )
/// <summary>
///
/// </summary>
/// <param name="action"></param>
/// <param name="timeout">wait timeout in milliseconds</param>
/// <returns></returns>
public ResponseEvents SendEventGeneratingAction ( ManagerActionEvent action , int timeout )
{
if ( action = = null )
throw new ArgumentException ( "Unable to send action: action is null." ) ;
else if ( action . ActionCompleteEventClass ( ) = = null )
throw new ArgumentException ( "Unable to send action: ActionCompleteEventClass is null." ) ;
else if ( ! typeof ( ResponseEvent ) . IsAssignableFrom ( action . ActionCompleteEventClass ( ) ) )
throw new ArgumentException ( "Unable to send action: ActionCompleteEventClass is not a ResponseEvent." ) ;
if ( mrSocket = = null )
throw new SystemException ( "Unable to send " + action . Action + " action: not connected." ) ;
AutoResetEvent autoEvent = new AutoResetEvent ( false ) ;
ResponseEventHandler handler = new ResponseEventHandler ( this , action , autoEvent ) ;
string internalActionId = createInternalActionId ( ) ;
handler . Hash = internalActionId . GetHashCode ( ) ;
AddResponseHandler ( handler ) ;
AddResponseEventHandler ( handler ) ;
SendToAsterisk ( action , internalActionId ) ;
bool result = autoEvent . WaitOne ( timeout < = 0 ? - 1 : timeout , true ) ;
RemoveResponseHandler ( handler ) ;
RemoveResponseEventHandler ( handler ) ;
if ( result )
return handler . ResponseEvents ;
throw new EventTimeoutException ( "Timeout waiting for response or response events to " + action . Action , handler . ResponseEvents ) ;
}
#endregion
#region Response Handler helpers
private void AddResponseHandler ( IResponseHandler handler )
{
lock ( lockHandlers )
{
if ( handler . Action is PingAction )
pingHandlers [ handler . Hash ] = handler ;
else
responseHandlers [ handler . Hash ] = handler ;
}
}
private void AddResponseEventHandler ( IResponseHandler handler )
{
lock ( lockHandlers )
responseEventHandlers [ handler . Hash ] = handler ;
}
internal void RemoveResponseHandler ( IResponseHandler handler )
{
int hash = handler . Hash ;
if ( hash ! = 0 )
lock ( lockHandlers )
if ( responseHandlers . ContainsKey ( hash ) )
responseHandlers . Remove ( hash ) ;
}
internal void RemoveResponseEventHandler ( IResponseHandler handler )
{
int hash = handler . Hash ;
if ( hash ! = 0 )
lock ( lockHandlers )
if ( responseEventHandlers . ContainsKey ( hash ) )
responseEventHandlers . Remove ( hash ) ;
}
private IResponseHandler GetRemoveResponseHandler ( int hash )
{
IResponseHandler handler = null ;
if ( hash ! = 0 )
lock ( lockHandlers )
if ( responseHandlers . ContainsKey ( hash ) )
{
handler = responseHandlers [ hash ] ;
responseHandlers . Remove ( hash ) ;
}
return handler ;
}
private IResponseHandler GetRemoveResponseEventHandler ( int hash )
{
IResponseHandler handler = null ;
if ( hash ! = 0 )
lock ( lockHandlers )
if ( responseEventHandlers . ContainsKey ( hash ) )
{
handler = responseEventHandlers [ hash ] ;
responseEventHandlers . Remove ( hash ) ;
}
return handler ;
}
private IResponseHandler GetResponseHandler ( int hash )
{
IResponseHandler handler = null ;
if ( hash ! = 0 )
lock ( lockHandlers )
if ( responseHandlers . ContainsKey ( hash ) )
handler = responseHandlers [ hash ] ;
return handler ;
}
private IResponseHandler GetResponseEventHandler ( int hash )
{
IResponseHandler handler = null ;
if ( hash ! = 0 )
lock ( lockHandlers )
if ( responseEventHandlers . ContainsKey ( hash ) )
handler = responseEventHandlers [ hash ] ;
return handler ;
}
#endregion
#region SendToAsterisk ( ManagerAction action , string internalActionId )
internal void SendToAsterisk ( ManagerAction action , string internalActionId )
{
if ( mrSocket = = null )
throw new SystemException ( "Unable to send action: socket is null" ) ;
string buffer = BuildAction ( action , internalActionId ) ;
#if LOGGER
logger . Debug ( "Sent action : '{0}' : {1}" , internalActionId , action ) ;
#endif
if ( sa = = null )
sa = new SendToAsteriskDelegate ( sendToAsterisk ) ;
sa . Invoke ( buffer ) ;
}
private delegate void SendToAsteriskDelegate ( string buffer ) ;
private SendToAsteriskDelegate sa = null ;
private void sendToAsterisk ( string buffer )
{
mrSocket . Write ( buffer ) ;
}
#endregion
#region BuildAction ( action )
public string BuildAction ( Action . ManagerAction action )
{
return BuildAction ( action , null ) ;
}
#endregion
#region BuildAction ( action , internalActionId )
public string BuildAction ( ManagerAction action , string internalActionId )
{
MethodInfo getter ;
object value ;
StringBuilder sb = new StringBuilder ( ) ;
string valueAsString = string . Empty ;
if ( typeof ( Action . ProxyAction ) . IsAssignableFrom ( action . GetType ( ) ) )
sb . Append ( string . Concat ( "ProxyAction: " , action . Action , Common . LINE_SEPARATOR ) ) ;
else
sb . Append ( string . Concat ( "Action: " , action . Action , Common . LINE_SEPARATOR ) ) ;
if ( string . IsNullOrEmpty ( internalActionId ) )
valueAsString = action . ActionId ;
else
valueAsString = string . Concat ( internalActionId , Common . INTERNAL_ACTION_ID_DELIMITER , action . ActionId ) ;
if ( ! string . IsNullOrEmpty ( valueAsString ) )
sb . Append ( string . Concat ( "ActionID: " , valueAsString , Common . LINE_SEPARATOR ) ) ;
Dictionary < string , MethodInfo > getters = Helper . GetGetters ( action . GetType ( ) ) ;
foreach ( string name in getters . Keys )
{
string nameLower = name . ToLower ( Helper . CultureInfo ) ;
if ( nameLower = = "class" | | nameLower = = "action" | | nameLower = = "actionid" )
continue ;
getter = getters [ name ] ;
Type propType = getter . ReturnType ;
if ( ! ( propType = = typeof ( string )
| | propType = = typeof ( bool )
| | propType = = typeof ( double )
| | propType = = typeof ( DateTime )
| | propType = = typeof ( int )
| | propType = = typeof ( long )
| | propType = = typeof ( Dictionary < string , string > )
)
)
continue ;
try
{
value = getter . Invoke ( action , new object [ ] { } ) ;
}
catch ( UnauthorizedAccessException ex )
{
#if LOGGER
logger . Error ( "Unable to retrieve property '" + name + "' of " + action . GetType ( ) , ex ) ;
continue ;
#else
throw new ManagerException ( "Unable to retrieve property '" + name + "' of " + action . GetType ( ) , ex ) ;
#endif
}
catch ( TargetInvocationException ex )
{
#if LOGGER
logger . Error ( "Unable to retrieve property '" + name + "' of " + action . GetType ( ) , ex ) ;
continue ;
#else
throw new ManagerException ( "Unable to retrieve property '" + name + "' of " + action . GetType ( ) , ex ) ;
#endif
}
if ( value = = null )
continue ;
if ( value is string )
{
valueAsString = ( string ) value ;
if ( valueAsString . Length = = 0 )
continue ;
}
else if ( value is bool )
valueAsString = ( ( bool ) value ? "true" : "false" ) ;
else if ( value is DateTime )
valueAsString = value . ToString ( ) ;
else if ( value is IDictionary )
{
valueAsString = Helper . JoinVariables ( ( IDictionary ) value , Common . LINE_SEPARATOR , ": " ) ;
if ( valueAsString . Length = = 0 )
continue ;
sb . Append ( valueAsString ) ;
sb . Append ( Common . LINE_SEPARATOR ) ;
continue ;
}
else
valueAsString = value . ToString ( ) ;
sb . Append ( string . Concat ( name , ": " , valueAsString , Common . LINE_SEPARATOR ) ) ;
}
2015-04-02 17:23:08 +00:00
IActionVariable actionVar = action as IActionVariable ;
2016-03-18 17:43:20 +00:00
if ( actionVar ! = null )
2015-04-02 17:23:08 +00:00
{
2016-03-18 17:43:20 +00:00
var variables = actionVar . GetVariables ( ) ;
if ( variables ! = null & & variables . Count > 0 )
{
sb . Append ( string . Concat ( "Variable: " , Helper . JoinVariables ( actionVar . GetVariables ( ) , VAR_DELIMITER , "=" ) , Common . LINE_SEPARATOR ) ) ;
}
2015-04-02 17:23:08 +00:00
}
2016-03-18 17:43:20 +00:00
sb . Append ( Common . LINE_SEPARATOR ) ;
2013-01-18 15:55:50 +00:00
return sb . ToString ( ) ;
}
#endregion
#region GetProtocolIdentifier ( )
public string GetProtocolIdentifier ( )
{
return this . protocolIdentifier ;
}
#endregion
#region RegisterUserEventClass ( class )
/// <summary>
/// Register User Event Class
/// </summary>
/// <param name="userEventClass"></param>
public void RegisterUserEventClass ( Type userEventClass )
{
Helper . RegisterEventClass ( registeredEventClasses , userEventClass ) ;
}
#endregion
#region DispatchResponse ( response )
/// <summary>
/// This method is called by the reader whenever a ManagerResponse is
/// received. The response is dispatched to the associated <see cref="IManagerResponseHandler"/>ManagerResponseHandler.
/// </summary>
/// <param name="response">the response received by the reader</param>
/// <seealso cref="ManagerReader" />
internal void DispatchResponse ( Dictionary < string , string > buffer )
{
#if LOGGER
logger . Debug ( "Dispatch response packet : {0}" , Helper . JoinVariables ( buffer , ", " , ": " ) ) ;
#endif
DispatchResponse ( buffer , null ) ;
}
internal void DispatchResponse ( ManagerResponse response )
{
#if LOGGER
logger . Debug ( "Dispatch response : {0}" , response ) ;
#endif
DispatchResponse ( null , response ) ;
}
internal void DispatchResponse ( Dictionary < string , string > buffer , ManagerResponse response )
{
string responseActionId = string . Empty ;
string actionId = string . Empty ;
IResponseHandler responseHandler = null ;
if ( buffer ! = null )
{
if ( buffer [ "response" ] . ToLower ( Helper . CultureInfo ) = = "error" )
response = new ManagerError ( buffer ) ;
else if ( buffer . ContainsKey ( "actionid" ) )
actionId = buffer [ "actionid" ] ;
}
if ( response ! = null )
actionId = response . ActionId ;
2014-01-08 14:16:39 +00:00
if ( ! string . IsNullOrEmpty ( actionId ) )
2013-01-18 15:55:50 +00:00
{
int hash = Helper . GetInternalActionId ( actionId ) . GetHashCode ( ) ;
responseActionId = Helper . StripInternalActionId ( actionId ) ;
responseHandler = GetRemoveResponseHandler ( hash ) ;
if ( response ! = null )
response . ActionId = responseActionId ;
if ( responseHandler ! = null )
{
if ( response = = null )
{
ManagerActionResponse action = responseHandler . Action as ManagerActionResponse ;
if ( action = = null | | ( response = action . ActionCompleteResponseClass ( ) as ManagerResponse ) = = null )
response = Helper . BuildResponse ( buffer ) ;
else
Helper . SetAttributes ( response , buffer ) ;
response . ActionId = responseActionId ;
}
try
{
responseHandler . HandleResponse ( response ) ;
}
catch ( Exception ex )
{
#if LOGGER
logger . Error ( "Unexpected exception in responseHandler {0}\n{1}" , response , ex ) ;
#else
throw new ManagerException ( "Unexpected exception in responseHandler " + responseHandler . GetType ( ) . FullName , ex ) ;
#endif
}
}
}
2013-03-15 19:26:36 +00:00
2014-01-08 14:16:39 +00:00
if ( response = = null & & buffer . ContainsKey ( "ping" ) & & buffer [ "ping" ] = = "Pong" )
2013-01-18 15:55:50 +00:00
{
response = Helper . BuildResponse ( buffer ) ;
foreach ( ResponseHandler pingHandler in pingHandlers . Values )
pingHandler . HandleResponse ( response ) ;
pingHandlers . Clear ( ) ;
}
if ( ! reconnected )
return ;
if ( response = = null )
{
response = Helper . BuildResponse ( buffer ) ;
response . ActionId = responseActionId ;
}
#if LOGGER
logger . Info ( "Reconnected - DispatchEvent : " + response ) ;
#endif
#region Support background reconnect
if ( response is ChallengeResponse )
{
string key = null ;
if ( response . IsSuccess ( ) )
{
ChallengeResponse challengeResponse = ( ChallengeResponse ) response ;
string challenge = challengeResponse . Challenge ;
try
{
Util . MD5Support md = Util . MD5Support . GetInstance ( ) ;
if ( challenge ! = null )
md . Update ( UTF8Encoding . UTF8 . GetBytes ( challenge ) ) ;
if ( password ! = null )
md . Update ( UTF8Encoding . UTF8 . GetBytes ( password ) ) ;
key = Helper . ToHexString ( md . DigestData ) ;
}
#if LOGGER
catch ( Exception ex )
{
logger . Error ( "Unable to create login key using MD5 Message Digest" , ex ) ;
#else
catch
{
#endif
key = null ;
}
}
bool fail = true ;
if ( ! string . IsNullOrEmpty ( key ) )
try
{
Action . LoginAction loginAction = new Action . LoginAction ( username , "MD5" , key ) ;
SendAction ( loginAction , null ) ;
fail = false ;
}
catch { }
if ( fail )
if ( keepAliveAfterAuthenticationFailure )
reconnect ( true ) ;
else
disconnect ( true ) ;
}
else if ( response is ManagerError )
{
if ( keepAliveAfterAuthenticationFailure )
reconnect ( true ) ;
else
disconnect ( true ) ;
}
else if ( response is ManagerResponse )
{
if ( response . IsSuccess ( ) )
{
reconnected = false ;
enableEvents = true ;
reconnectEnable = keepAlive ;
ConnectEvent ce = new ConnectEvent ( this ) ;
ce . Reconnect = true ;
ce . ProtocolIdentifier = protocolIdentifier ;
fireEvent ( ce ) ;
}
else if ( keepAliveAfterAuthenticationFailure )
reconnect ( true ) ;
else
disconnect ( true ) ;
}
#endregion
}
#endregion
#region DispatchEvent ( . . . )
/// <summary>
/// This method is called by the reader whenever a ManagerEvent is received.
/// The event is dispatched to all registered ManagerEventHandlers.
/// </summary>
/// <param name="e">the event received by the reader</param>
/// <seealso cref="ManagerReader"/>
internal void DispatchEvent ( Dictionary < string , string > buffer )
{
ManagerEvent e = Helper . BuildEvent ( registeredEventClasses , this , buffer ) ;
DispatchEvent ( e ) ;
}
internal void DispatchEvent ( ManagerEvent e )
{
#if LOGGER
logger . Debug ( "Dispatching event: {0}" , e ) ;
#endif
if ( e is ResponseEvent )
{
ResponseEvent responseEvent = ( ResponseEvent ) e ;
if ( ! string . IsNullOrEmpty ( responseEvent . ActionId ) & & ! string . IsNullOrEmpty ( responseEvent . InternalActionId ) )
{
ResponseEventHandler eventHandler = ( ResponseEventHandler ) GetResponseEventHandler ( responseEvent . InternalActionId . GetHashCode ( ) ) ;
if ( eventHandler ! = null )
try
{
eventHandler . HandleEvent ( e ) ;
}
catch ( SystemException ex )
{
#if LOGGER
logger . Error ( "Unexpected exception" , ex ) ;
#else
throw ex ;
#endif
}
}
}
#region ConnectEvent
if ( e is ConnectEvent )
{
string protocol = ( ( ConnectEvent ) e ) . ProtocolIdentifier ;
#if LOGGER
logger . Info ( "Connected via {0}" , protocol ) ;
#endif
if ( ! string . IsNullOrEmpty ( protocol ) & & protocol . StartsWith ( "Asterisk Call Manager" ) )
{
this . protocolIdentifier = protocol ;
}
else
{
this . protocolIdentifier = ( string . IsNullOrEmpty ( protocol ) ? "Empty" : protocol ) ;
#if LOGGER
logger . Warning ( "Unsupported protocol version '{0}'. Use at your own risk." , protocol ) ;
#endif
}
if ( reconnected )
{
#if LOGGER
logger . Info ( "Send Challenge action." ) ;
#endif
ChallengeAction challengeAction = new ChallengeAction ( ) ;
try
{
SendAction ( challengeAction , null ) ;
}
#if LOGGER
catch ( Exception ex )
{
logger . Info ( "Send Challenge fail : " , ex . Message ) ;
#else
catch
{
#endif
disconnect ( true ) ;
}
return ;
}
}
#endregion
if ( reconnected & & e is DisconnectEvent )
{
( ( DisconnectEvent ) e ) . Reconnect = true ;
fireEvent ( e ) ;
reconnect ( false ) ;
}
else if ( ! reconnected & & reconnectEnable & & ( e is DisconnectEvent | | e is ReloadEvent | | e is ShutdownEvent ) )
{
( ( ConnectionStateEvent ) e ) . Reconnect = true ;
fireEvent ( e ) ;
reconnect ( true ) ;
}
else
fireEvent ( e ) ;
}
private void eventComplete ( IAsyncResult result )
{
}
private void fireEvent ( ManagerEvent e )
{
if ( enableEvents & & internalEvent ! = null )
2014-01-08 14:16:39 +00:00
if ( UseASyncEvents )
internalEvent . BeginInvoke ( this , e , new AsyncCallback ( eventComplete ) , null ) ;
else
internalEvent . Invoke ( this , e ) ;
2013-01-18 15:55:50 +00:00
}
#endregion
}
2017-05-03 15:00:27 +00:00
}