using System; using System.IO; using System.Threading; using System.Collections; using Asterisk.NET.Manager.Action; using Asterisk.NET.Manager.Event; using Asterisk.NET.Manager.Response; using System.Text.RegularExpressions; using System.Text; using System.Net.Sockets; using System.Collections.Generic; using System.Reflection; using Asterisk.NET.IO; using Asterisk.NET.Util; namespace Asterisk.NET.Manager { #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); public delegate void BridgeEventHandler(object sender, Event.BridgeEvent e); 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); public delegate void DTMFEventHandler(object sender, Event.DTMFEvent e); 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); 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); public delegate void TransferEventHandler(object sender, Event.TransferEvent e); 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); public delegate void QueueCallerAbandonEventHandler(object sender, Event.QueueCallerAbandonEvent e); 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); 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); #endregion /// /// Default implemention of the ManagerConnection interface. /// public class ManagerConnection { #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; private ThreadClass mrReaderThread; 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 responseHandlers; private Dictionary pingHandlers; private Dictionary 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 registeredEventClasses; private Dictionary registeredEventHandlers; private event ManagerEventHandler internalEvent; private bool fireAllEvents = false; private Thread callerThread; private bool traceCallerThread = true; /// Default Fast Reconnect retry counter. private int reconnectRetryFast = 5; /// Default Maximum Reconnect retry counter. private int reconnectRetryMax = 10; /// Default Fast Reconnect interval in milliseconds. private int reconnectIntervalFast = 5000; /// Default Slow Reconnect interval in milliseconds. private int reconnectIntervalMax = 10000; #endregion #region Events /// /// An UnhandledEvent is triggered on unknown event. /// public event ManagerEventHandler UnhandledEvent; /// /// An AgentCallbackLogin is triggered when an agent is successfully logged in. /// public event AgentCallbackLoginEventHandler AgentCallbackLogin; /// /// An AgentCallbackLogoff is triggered when an agent that previously logged in is logged of.
///
public event AgentCallbackLogoffEventHandler AgentCallbackLogoff; /// /// An AgentCalled is triggered when an agent is ring.
/// To enable AgentCalled you have to set eventwhencalled = yes in queues.conf.
///
public event AgentCalledEventHandler AgentCalled; /// /// An AgentCompleteEvent is triggered when at the end of a call if the caller was connected to an agent. /// public event AgentCompleteEventHandler AgentComplete; /// /// An AgentConnectEvent is triggered when a caller is connected to an agent. /// public event AgentConnectEventHandler AgentConnect; /// /// An AgentDumpEvent is triggered when an agent dumps the caller while listening to the queue announcement. /// public event AgentDumpEventHandler AgentDump; /// /// An AgentLoginEvent is triggered when an agent is successfully logged in using AgentLogin. /// public event AgentLoginEventHandler AgentLogin; /// /// An AgentCallbackLogoffEvent is triggered when an agent that previously logged in using AgentLogin is logged of. /// public event AgentLogoffEventHandler AgentLogoff; /// /// An AgentsCompleteEvent is triggered after the state of all agents has been reported in response to an AgentsAction. /// public event AgentsCompleteEventHandler AgentsComplete; /// /// An AgentsEvent is triggered for each agent in response to an AgentsAction. /// public event AgentsEventHandler Agents; /// /// An AlarmEvent is triggered when a Zap channel leaves alarm state. /// public event AlarmClearEventHandler AlarmClear; /// /// /// public event BridgeEventHandler Bridge; /// /// An AlarmEvent is triggered when a Zap channel enters or changes alarm state. /// public event AlarmEventHandler Alarm; /// /// A CdrEvent is triggered when a call detail record is generated, usually at the end of a call. /// public event CdrEventHandler Cdr; public event DBGetResponseEventHandler DBGetResponse; /// /// A Dial is triggered whenever a phone attempts to dial someone.
///
public event DialEventHandler Dial; public event DTMFEventHandler DTMF; /// /// A DNDStateEvent is triggered by the Zap channel driver when a channel enters or leaves DND (do not disturb) state. /// public event DNDStateEventHandler DNDState; /// /// An ExtensionStatus is triggered when the state of an extension changes.
///
public event ExtensionStatusEventHandler ExtensionStatus; /// /// A Hangup is triggered when a channel is hung up.
///
public event HangupEventHandler Hangup; /// /// A HoldedCall is triggered when a channel is put on hold.
///
public event HoldedCallEventHandler HoldedCall; /// /// A Hold is triggered by the SIP channel driver when a channel is put on hold.
///
public event HoldEventHandler Hold; /// /// A Join is triggered when a channel joines a queue.
///
public event JoinEventHandler Join; /// /// A Leave is triggered when a channel leaves a queue.
///
public event LeaveEventHandler Leave; /// /// A Link is triggered when two voice channels are linked together and voice data exchange commences.
/// 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. ///
public event LinkEventHandler Link; /// /// A LogChannel is triggered when logging is turned on or off.
///
public event LogChannelEventHandler LogChannel; /// /// A MeetMeJoin is triggered if a channel joins a meet me conference.
///
public event MeetMeJoinEventHandler MeetMeJoin; /// /// A MeetMeLeave is triggered if a channel leaves a meet me conference.
///
public event MeetMeLeaveEventHandler MeetMeLeave; // public event MeetMeStopTalkingEventHandler MeetMeStopTalking; /// /// A MeetMeTalkingEvent is triggered when a user starts talking in a meet me conference.
/// To enable talker detection you must pass the option 'T' to the MeetMe application. ///
public event MeetMeTalkingEventHandler MeetMeTalking; /// /// A MessageWaiting is triggered when someone leaves voicemail.
///
public event MessageWaitingEventHandler MessageWaiting; /// /// A NewCallerId is triggered when the caller id of a channel changes.
///
public event NewCallerIdEventHandler NewCallerId; /// /// A NewChannel is triggered when a new channel is created.
///
public event NewChannelEventHandler NewChannel; /// /// A NewExten is triggered when a channel is connected to a new extension.
///
public event NewExtenEventHandler NewExten; /// /// A NewState is triggered when the state of a channel has changed.
///
public event NewStateEventHandler NewState; // public event OriginateEventHandler Originate; /// /// An OriginateFailure is triggered when the execution of an OriginateAction failed. /// // public event OriginateFailureEventHandler OriginateFailure; /// /// An OriginateSuccess is triggered when the execution of an OriginateAction succeeded. /// // public event OriginateSuccessEventHandler OriginateSuccess; /// /// An OriginateResponse is triggered when the execution of an Originate. /// public event OriginateResponseEventHandler OriginateResponse; /// /// A ParkedCall is triggered when a channel is parked (in this case no /// action id is set) and in response to a ParkedCallsAction.
///
public event ParkedCallEventHandler ParkedCall; /// /// A ParkedCallGiveUp is triggered when a channel that has been parked is hung up.
///
public event ParkedCallGiveUpEventHandler ParkedCallGiveUp; /// /// A ParkedCallsComplete is triggered after all parked calls have been reported in response to a ParkedCallsAction. /// public event ParkedCallsCompleteEventHandler ParkedCallsComplete; /// /// A ParkedCallTimeOut is triggered when call parking times out for a given channel.
///
public event ParkedCallTimeOutEventHandler ParkedCallTimeOut; /// /// A PeerEntry is triggered in response to a SIPPeersAction or SIPShowPeerAction and contains information about a peer.
///
public event PeerEntryEventHandler PeerEntry; /// /// A PeerlistComplete is triggered after the details of all peers has been reported in response to an SIPPeersAction or SIPShowPeerAction.
///
public event PeerlistCompleteEventHandler PeerlistComplete; /// /// A PeerStatus is triggered when a SIP or IAX client attempts to registrer at this asterisk server.
///
public event PeerStatusEventHandler PeerStatus; /// /// A QueueEntryEvent is triggered in response to a QueueStatusAction and contains information about an entry in a queue. /// public event QueueCallerAbandonEventHandler QueueCallerAbandon; /// /// A QueueEntryEvent is triggered in response to a QueueStatusAction and contains information about an entry in a queue. /// public event QueueEntryEventHandler QueueEntry; /// /// A QueueMemberAddedEvent is triggered when a queue member is added to a queue. /// public event QueueMemberAddedEventHandler QueueMemberAdded; /// /// A QueueMemberEvent is triggered in response to a QueueStatusAction and contains information about a member of a queue. /// public event QueueMemberEventHandler QueueMember; /// /// A QueueMemberPausedEvent is triggered when a queue member is paused or unpaused. /// public event QueueMemberPausedEventHandler QueueMemberPaused; /// /// A QueueMemberRemovedEvent is triggered when a queue member is removed from a queue. /// public event QueueMemberRemovedEventHandler QueueMemberRemoved; /// /// A QueueMemberStatusEvent shows the status of a QueueMemberEvent. /// public event QueueMemberStatusEventHandler QueueMemberStatus; /// /// A QueueParamsEvent is triggered in response to a QueueStatusAction and contains the parameters of a queue. /// public event QueueParamsEventHandler QueueParams; /// /// A QueueStatusCompleteEvent is triggered after the state of all queues has been reported in response to a QueueStatusAction. /// public event QueueStatusCompleteEventHandler QueueStatusComplete; /// /// A Registry is triggered when this asterisk server attempts to register /// as a client at another SIP or IAX server.
///
public event RegistryEventHandler Registry; /// /// A RenameEvent is triggered when the name of a channel is changed. /// public event RenameEventHandler Rename; /// /// A StatusCompleteEvent is triggered after the state of all channels has been reported in response to a StatusAction. /// public event StatusCompleteEventHandler StatusComplete; /// /// A StatusEvent is triggered for each active channel in response to a StatusAction. /// public event StatusEventHandler Status; /// /// /// public event TransferEventHandler Transfer; /// /// An UnholdEvent is triggered by the SIP channel driver when a channel is no longer put on hold. /// public event UnholdEventHandler Unhold; /// /// An UnlinkEvent is triggered when a link between two voice channels is discontinued, for example, just before call completion. /// public event UnlinkEventHandler Unlink; /// /// A UnparkedCallEvent is triggered when a channel that has been parked is resumed. /// public event UnparkedCallEventHandler UnparkedCall; /// /// A ZapShowChannelsEvent is triggered on UserEvent in dialplan. /// public event UserEventHandler UserEvents; /// /// A ZapShowChannelsCompleteEvent is triggered after the state of all zap channels has been reported in response to a ZapShowChannelsAction. /// public event ZapShowChannelsCompleteEventHandler ZapShowChannelsComplete; /// /// A ZapShowChannelsEvent is triggered in response to a ZapShowChannelsAction and shows the state of a zap channel. /// public event ZapShowChannelsEventHandler ZapShowChannels; /// /// A ConnectionState is triggered after Connect/Disconnect/Reload/Shutdown events. /// public event ConnectionStateEventHandler ConnectionState; /// /// When a variable is set /// public event VarSetEventHandler VarSet; /// /// AgiExec is execute /// public event AGIExecHandler AGIExec; /// /// This event is sent when the first user requests a conference and it is instantiated /// public event ConfbridgeStartEventHandler ConfbridgeStart; /// /// 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. /// public event ConfbridgeJoinEventHandler ConfbridgeJoin; /// /// This event is sent when a user leaves a conference. /// public event ConfbridgeLeaveEventHandler ConfbridgeLeave; /// /// This event is sent when the last user leaves a conference and it is torn down. /// public event ConfbridgeEndEventHandler ConfbridgeEnd; /// /// This event is sent when the conference detects that a user has either begin or stopped talking. /// public event ConfbridgeTalkingEventHandler ConfbridgeTalking; #endregion #region Constructor - ManagerConnection() /// Creates a new instance. public ManagerConnection() { callerThread = Thread.CurrentThread; socketEncoding = Encoding.ASCII; responseHandlers = new Dictionary(); pingHandlers = new Dictionary(); responseEventHandlers = new Dictionary(); registeredEventClasses = new Dictionary(); Helper.RegisterBuiltinEventClasses(registeredEventClasses); registeredEventHandlers = new Dictionary(); #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)); Helper.RegisterEventHandler(registeredEventHandlers, 51, typeof(QueueCallerAbandonEvent)); 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)); Helper.RegisterEventHandler(registeredEventHandlers, 63, typeof(BridgeEvent)); Helper.RegisterEventHandler(registeredEventHandlers, 64, typeof(TransferEvent)); Helper.RegisterEventHandler(registeredEventHandlers, 65, typeof(DTMFEvent)); Helper.RegisterEventHandler(registeredEventHandlers, 70, typeof(VarSetEvent)); Helper.RegisterEventHandler(registeredEventHandlers, 80, typeof(AGIExecEvent)); Helper.RegisterEventHandler(registeredEventHandlers, 81, typeof(ConfbridgeStartEventHandler)); Helper.RegisterEventHandler(registeredEventHandlers, 82, typeof(ConfbridgeJoinEventHandler)); Helper.RegisterEventHandler(registeredEventHandlers, 83, typeof(ConfbridgeLeaveEventHandler)); Helper.RegisterEventHandler(registeredEventHandlers, 84, typeof(ConfbridgeEndEventHandler)); Helper.RegisterEventHandler(registeredEventHandlers, 85, typeof(ConfbridgeTalkingEventHandler)); #endregion this.internalEvent += new ManagerEventHandler(internalEventHandler); } #endregion #region Constructor - ManagerConnection(hostname, port, username, password) /// /// Creates a new instance with the given connection parameters. /// /// the hosname of the Asterisk server to connect to. /// the port where Asterisk listens for incoming Manager API connections, usually 5038. /// the username to use for login /// the password to use for login 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) /// /// Creates a new instance with the given connection parameters. /// /// the hosname of the Asterisk server to connect to. /// the port where Asterisk listens for incoming Manager API connections, usually 5038. /// the username to use for login /// the password to use for login /// text encoding to asterisk input/output stream 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 /// /// Default Fast Reconnect retry counter. /// public int ReconnectRetryFast { get { return reconnectRetryFast; } set { reconnectRetryFast = value; } } /// Default Maximum Reconnect retry counter. public int ReconnectRetryMax { get { return reconnectRetryMax; } set { reconnectRetryMax = value; } } /// Default Fast Reconnect interval in milliseconds. public int ReconnectIntervalFast { get { return reconnectIntervalFast; } set { reconnectIntervalFast = value; } } /// Default Slow Reconnect interval in milliseconds. public int ReconnectIntervalMax { get { return reconnectIntervalMax; } set { reconnectIntervalMax = value; } } #region TraceCallerThread /// /// Enable /// internal bool TraceCallerThread { get { return traceCallerThread; } set { traceCallerThread = value; } } #endregion #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; case 51: if (QueueCallerAbandon != null) { QueueCallerAbandon(this, (QueueCallerAbandonEvent)e); return; } break; 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; 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; default: if (UnhandledEvent != null) UnhandledEvent(this, e); return; } } if (fireAllEvents && UnhandledEvent != null) UnhandledEvent(this, e); } #endregion #region FireAllEvents /// /// If this property set to true then ManagerConnection send all unassigned events to UnhandledEvent handler,
/// if set to false then all unassgned events lost and send only UnhandledEvent.
/// Default: false ///
public bool FireAllEvents { get { return this.fireAllEvents; } set { this.fireAllEvents = value; } } #endregion #region PingInterval /// /// Timeout from Ping to Pong. If no Pong received send Disconnect event. Set to zero to disable. /// public int PingInterval { get { return pingInterval; } set { pingInterval = value; } } #endregion #region Hostname /// Sets the hostname of the asterisk server to connect to.
/// Default is localhost. ///
public string Hostname { get { return hostname; } set { hostname = value; } } #endregion #region Port /// /// Sets the port to use to connect to the asterisk server. This is the port /// specified in asterisk's manager.conf file.
/// Default is 5038. ///
public int Port { get { return port; } set { port = value; } } #endregion #region UserName /// /// Sets the username to use to connect to the asterisk server. This is the /// username specified in asterisk's manager.conf file. /// public string Username { get { return username; } set { username = value; } } #endregion #region Password /// /// Sets the password to use to connect to the asterisk server. This is the /// password specified in asterisk's manager.conf file. /// public string Password { get { return password; } set { password = value; } } #endregion #region DefaultResponseTimeout /// Sets the time in milliseconds the synchronous method /// will wait for a response before throwing a TimeoutException.
/// Default is 2000. ///
public int DefaultResponseTimeout { get { return defaultResponseTimeout; } set { defaultResponseTimeout = value; } } #endregion #region DefaultEventTimeout /// Sets the time in milliseconds the synchronous method /// will wait for a response and the last response event before throwing a TimeoutException.
/// Default is 5000. ///
public int DefaultEventTimeout { get { return defaultEventTimeout; } set { defaultEventTimeout = value; } } #endregion #region SleepTime /// 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.
/// The sleepTime attribute is also used when checking for the protocol /// identifer.
/// Default is 50. ///
/// this has been replaced by an interrupt based response checking approach. public int SleepTime { get { return sleepTime; } set { sleepTime = value; } } #endregion #region KeepAliveAfterAuthenticationFailure /// Set to true to try reconnecting to ther asterisk serve /// even if the reconnection attempt threw an AuthenticationFailedException.
/// Default is false. ///
public bool KeepAliveAfterAuthenticationFailure { set { keepAliveAfterAuthenticationFailure = value; } get { return keepAliveAfterAuthenticationFailure; } } #endregion #region KeepAlive /// /// Should we attempt to reconnect when the connection is lost?
/// This is set to true after successful login and to false after logoff or after an authentication failure when keepAliveAfterAuthenticationFailure is false. ///
public bool KeepAlive { get { return keepAlive; } set { keepAlive = value; } } #endregion #region SocketEncoding /// /// Socket Encoding - default ASCII /// 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) /// /// Does the real login, following the steps outlined below.
/// Connects to the asterisk server by calling connect() if not already connected
/// Waits until the protocol identifier is received. This is checked every sleepTime ms but not longer than timeout ms in total.
/// Sends a ChallengeAction requesting a challenge for authType MD5.
/// When the ChallengeResponse is received a LoginAction is sent using the calculated key (MD5 hash of the password appended to the received challenge).
///
/// the maximum time to wait for the protocol identifier (in ms) /// /// 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. /// /// /// 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. /// 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; if (version.StartsWith("1.4.")) return AsteriskVersion.ASTERISK_1_4; else if (version.StartsWith("1.6.")) return Manager.AsteriskVersion.ASTERISK_1_6; else if (version.StartsWith("1.8.")) return Manager.AsteriskVersion.ASTERISK_1_8; else if (version.StartsWith("10.")) return Manager.AsteriskVersion.ASTERISK_10; else if (version.StartsWith("11.")) return Manager.AsteriskVersion.ASTERISK_11; else if (version.StartsWith("12.")) return Manager.AsteriskVersion.ASTERISK_12; else throw new ManagerException("Unknown Asterisk version " + version); } } } } 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")) return AsteriskVersion.ASTERISK_1_2; } } 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; } if (result) { if (this.mrReader == null) { mrReader = new ManagerReader(this); mrReaderThread = new Util.ThreadClass(new ThreadStart(this.mrReader.Run), "ManagerReader-" + DateTime.Now.Second); mrReader.Socket = mrSocket; startReader = true; } else mrReader.Socket = mrSocket; mrReader.Reinitialize(); } else mrSocket = null; } } if (startReader) mrReaderThread.Start(); return IsConnected(); } #endregion #region disconnect() /// Closes the socket connection. 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) /// /// Reconnects to the asterisk server when the connection is lost.
/// While keepAlive is true we will try to reconnect. /// 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.
/// This method is called when a DisconnectEvent is received from the reader. ///
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() /// /// Creates a new unique internal action id based on the hash code of this connection and a sequence. /// private string createInternalActionId() { return this.GetHashCode() + "_" + (this.actionIdCount++); } #endregion #region Login() /// /// 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. /// /// AuthenticationFailedException if the username and/or password are incorrect /// TimeoutException if no response is received within the specified timeout period /// /// public void Login() { login(defaultResponseTimeout); } /// /// 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. /// /// Timeout in milliseconds to login. public void Login(int timeout) { login(timeout); } #endregion #region IsConnected() /// Returns true if there is a socket connection to the /// asterisk server, false otherwise. /// /// /// true if there is a socket connection to the /// asterisk server, false otherwise. /// public bool IsConnected() { bool result = false; lock (lockSocket) result = mrSocket != null && mrSocket.IsConnected; return result; } #endregion #region Logoff() /// /// Sends a LogoffAction and disconnects from the server. /// 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) /// /// Send Action with default timeout. /// /// /// public Response.ManagerResponse SendAction(Action.ManagerAction action) { return SendAction(action, defaultResponseTimeout); } #endregion #region SendAction(action, timeout) /// /// Send action ans with timeout (milliseconds) /// /// action to send /// timeout in milliseconds /// 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) /// /// /// /// /// wait timeout in milliseconds /// 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 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) ) ) 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)); } sb.Append(Common.LINE_SEPARATOR); return sb.ToString(); } #endregion #region GetProtocolIdentifier() public string GetProtocolIdentifier() { return this.protocolIdentifier; } #endregion #region RegisterUserEventClass(class) /// /// Register User Event Class /// /// public void RegisterUserEventClass(Type userEventClass) { Helper.RegisterEventClass(registeredEventClasses, userEventClass); } #endregion #region DispatchResponse(response) /// /// This method is called by the reader whenever a ManagerResponse is /// received. The response is dispatched to the associated ManagerResponseHandler. /// /// the response received by the reader /// internal void DispatchResponse(Dictionary 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 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; if (!string.IsNullOrEmpty(actionId)) { 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 } } } if (response == null && buffer.ContainsKey("ping") && buffer["ping"] == "Pong") { 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(...) /// /// This method is called by the reader whenever a ManagerEvent is received. /// The event is dispatched to all registered ManagerEventHandlers. /// /// the event received by the reader /// internal void DispatchEvent(Dictionary 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) internalEvent.BeginInvoke(this, e, new AsyncCallback(eventComplete), null); } #endregion } }