asternet/Asterisk.2013/Asterisk.NET/Helper.cs

836 lines
25 KiB
C#

using System;
using System.Globalization;
using System.Text;
using System.Collections;
using System.Collections.Specialized;
using System.Threading;
using System.Reflection;
using System.Security.Cryptography;
using System.Collections.Generic;
using Asterisk.NET.Manager.Event;
using Asterisk.NET.Manager.Response;
using Asterisk.NET.Manager;
namespace Asterisk.NET
{
internal class Helper
{
private static CultureInfo defaultCulture;
#if LOGGER
private static Logger logger = Logger.Instance();
#endif
#region CultureInfo
internal static CultureInfo CultureInfo
{
get
{
if (defaultCulture == null)
defaultCulture = System.Globalization.CultureInfo.GetCultureInfo("en");
return defaultCulture;
}
}
#endregion
#region ToHexString(sbyte[])
/// <summary> The hex digits used to build a hex string representation of a byte array.</summary>
internal static readonly char[] hexChar = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
/// <summary>
/// Converts a byte array to a hex string representing it. The hex digits are lower case.
/// </summary>
/// <param name="b">the byte array to convert</param>
/// <returns> the hex representation of b</returns>
internal static string ToHexString(sbyte[] b)
{
StringBuilder sb = new StringBuilder(b.Length * 2);
for (int i = 0; i < b.Length; i++)
{
sb.Append(hexChar[Helper.URShift((b[i] & 0xf0), 4)]);
sb.Append(hexChar[b[i] & 0x0f]);
}
return sb.ToString();
}
#endregion
#region GetInternalActionId(actionId)
internal static string GetInternalActionId(string actionId)
{
if (string.IsNullOrEmpty(actionId))
return string.Empty;
int delimiterIndex = actionId.IndexOf(Common.INTERNAL_ACTION_ID_DELIMITER);
if (delimiterIndex > 0)
return actionId.Substring(0, delimiterIndex).Trim();
return string.Empty;
}
#endregion
#region StripInternalActionId(actionId)
internal static string StripInternalActionId(string actionId)
{
if (string.IsNullOrEmpty(actionId))
return string.Empty;
int delimiterIndex = actionId.IndexOf(Common.INTERNAL_ACTION_ID_DELIMITER);
if (delimiterIndex > 0)
{
if (actionId.Length > delimiterIndex + 1)
return actionId.Substring(delimiterIndex + 1).Trim();
else
return actionId.Substring(0, delimiterIndex).Trim();
}
return string.Empty;
}
#endregion
#region IsTrue(string)
/// <summary>
/// Checks if a String represents <code>true</code> or <code>false</code> according to Asterisk's logic.<br/>
/// The original implementation is <code>util.c</code> is as follows:
/// </summary>
/// <param name="s">the String to check for <code>true</code>.</param>
/// <returns>
/// <code>true</code> if s represents <code>true</code>,
/// <code>false</code> otherwise.
/// </returns>
internal static bool IsTrue(string s)
{
if (s == null || s.Length == 0)
return false;
string sx = s.ToLower(Helper.CultureInfo);
if (sx == "yes" || sx == "true" || sx == "y" || sx == "t" || sx == "1" || sx == "on")
return true;
return false;
}
#endregion
#region URShift(...)
/// <summary>
/// Performs an unsigned bitwise right shift with the specified number
/// </summary>
/// <param name="number">Number to operate on</param>
/// <param name="bits">Ammount of bits to shift</param>
/// <returns>The resulting number from the shift operation</returns>
internal static int URShift(int number, int bits)
{
if (number >= 0)
return number >> bits;
else
return (number >> bits) + (2 << ~bits);
}
/// <summary>
/// Performs an unsigned bitwise right shift with the specified number
/// </summary>
/// <param name="number">Number to operate on</param>
/// <param name="bits">Ammount of bits to shift</param>
/// <returns>The resulting number from the shift operation</returns>
internal static int URShift(int number, long bits)
{
return URShift(number, (int)bits);
}
/// <summary>
/// Performs an unsigned bitwise right shift with the specified number
/// </summary>
/// <param name="number">Number to operate on</param>
/// <param name="bits">Ammount of bits to shift</param>
/// <returns>The resulting number from the shift operation</returns>
internal static long URShift(long number, int bits)
{
if (number >= 0)
return number >> bits;
else
return (number >> bits) + (2L << ~bits);
}
/// <summary>
/// Performs an unsigned bitwise right shift with the specified number
/// </summary>
/// <param name="number">Number to operate on</param>
/// <param name="bits">Ammount of bits to shift</param>
/// <returns>The resulting number from the shift operation</returns>
internal static long URShift(long number, long bits)
{
return URShift(number, (int)bits);
}
#endregion
#region ToArray(ICollection c, object[] objects)
/// <summary>
/// Obtains an array containing all the elements of the collection.
/// </summary>
/// <param name="objects">The array into which the elements of the collection will be stored.</param>
/// <returns>The array containing all the elements of the collection.</returns>
internal static object[] ToArray(ICollection c, object[] objects)
{
int index = 0;
Type type = objects.GetType().GetElementType();
object[] objs = (object[])Array.CreateInstance(type, c.Count);
IEnumerator e = c.GetEnumerator();
while (e.MoveNext())
objs[index++] = e.Current;
//If objects is smaller than c then do not return the new array in the parameter
if (objects.Length >= c.Count)
objs.CopyTo(objects, 0);
return objs;
}
#endregion
#region ParseVariables(Dictionary<string, string> dictionary, string variables, char[] delim)
/// <summary>
/// Parse variable(s) string to dictionary.
/// </summary>
/// <param name="dictionary"></param>
/// <param name="variables">variable(a) string</param>
/// <param name="delim">variable pairs delimiter</param>
/// <returns></returns>
internal static Dictionary<string, string> ParseVariables(Dictionary<string, string> dictionary, string variables, char[] delim)
{
if (dictionary == null)
dictionary = new Dictionary<string, string>();
else
dictionary.Clear();
if (string.IsNullOrEmpty(variables))
return dictionary;
string[] vars = variables.Split(delim);
int idx;
string vname, vval;
foreach (string var in vars)
{
idx = var.IndexOf('=');
if (idx > 0)
{
vname = var.Substring(0, idx);
vval = var.Substring(idx + 1);
}
else
{
vname = var;
vval = string.Empty;
}
dictionary.Add(vname, vval);
}
return dictionary;
}
#endregion
#region JoinVariables(IDictionary dictionary, string delim)
/// <summary>
/// Join variables dictionary to string.
/// </summary>
/// <param name="dictionary"></param>
/// <param name="delim"></param>
/// <returns></returns>
internal static string JoinVariables(IDictionary dictionary, char[] delim, string delimKeyValue)
{
return JoinVariables(dictionary, new string(delim), delimKeyValue);
}
internal static string JoinVariables(IDictionary dictionary, string delim, string delimKeyValue)
{
if (dictionary == null)
return string.Empty;
StringBuilder sb = new StringBuilder();
foreach (DictionaryEntry var in dictionary)
{
if (sb.Length > 0)
sb.Append(delim);
sb.Append(string.Concat(var.Key, delimKeyValue, var.Value));
}
return sb.ToString();
}
#endregion
#region GetMillisecondsFrom(DateTime start)
internal static long GetMillisecondsFrom(DateTime start)
{
TimeSpan ts = (TimeSpan)(DateTime.Now - start);
return (long)ts.TotalMilliseconds;
}
#endregion
#region ParseString(string val)
internal static object ParseString(string val)
{
if (val == "none")
return string.Empty;
return val;
}
#endregion
#region GetGetters(class)
/// <summary>
/// Returns a Map of getter methods of the given class.<br/>
/// The key of the map contains the name of the attribute that can be accessed by the getter, the
/// value the getter itself . A method is considered a getter if its name starts with "get",
/// it is declared internal and takes no arguments.
/// </summary>
/// <param name="clazz">the class to return the getters for</param>
/// <returns> a Map of attributes and their accessor methods (getters)</returns>
internal static Dictionary<string, MethodInfo> GetGetters(Type clazz)
{
string name;
string methodName;
MethodInfo method;
Dictionary<string, MethodInfo> accessors = new Dictionary<string, MethodInfo>();
MethodInfo[] methods = clazz.GetMethods();
for (int i = 0; i < methods.Length; i++)
{
method = methods[i];
methodName = method.Name;
// skip not "get..." methods and skip methods with != 0 parameters
if (!methodName.StartsWith("get_") || method.GetParameters().Length != 0)
continue;
name = methodName.Substring(4);
if (name.Length == 0)
continue;
accessors[name] = method;
}
return accessors;
}
#endregion
#region GetSetters(Type clazz)
/// <summary>
/// Returns a Map of setter methods of the given class.<br/>
/// The key of the map contains the name of the attribute that can be accessed by the setter, the
/// value the setter itself. A method is considered a setter if its name starts with "set",
/// it is declared internal and takes no arguments.
/// </summary>
/// <param name="clazz">the class to return the setters for</param>
/// <returns> a Map of attributes and their accessor methods (setters)</returns>
internal static IDictionary GetSetters(Type clazz)
{
IDictionary accessors = new Hashtable();
MethodInfo[] methods = clazz.GetMethods();
string name;
string methodName;
MethodInfo method;
for (int i = 0; i < methods.Length; i++)
{
method = methods[i];
methodName = method.Name;
// skip not "set..." methods and skip methods with != 1 parameters
if (!methodName.StartsWith("set_") || method.GetParameters().Length != 1)
continue;
name = methodName.Substring("set_".Length).ToLower(Helper.CultureInfo);
if (name.Length == 0) continue;
accessors[name] = method;
}
return accessors;
}
#endregion
#region ToString(object obj)
/// <summary>
/// Convert object with all properties to string
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
internal static string ToString(object obj)
{
object value;
StringBuilder sb = new StringBuilder(obj.GetType().Name, 1024);
sb.Append(" {");
string strValue;
IDictionary getters = Helper.GetGetters(obj.GetType());
bool notFirst = false;
List<MethodInfo> arrays = new List<MethodInfo>();
// First step - all values properties (not a list)
foreach (string name in getters.Keys)
{
MethodInfo getter = (MethodInfo)getters[name];
Type propType = getter.ReturnType;
if (propType == typeof(object))
continue;
if (!(propType == typeof(string) || propType == typeof(bool) || propType == typeof(double) || propType == typeof(DateTime) || propType == typeof(int) || propType == typeof(long)))
{
string propTypeName = propType.Name;
if (propTypeName.StartsWith("Dictionary") || propTypeName.StartsWith("List"))
{
arrays.Add(getter);
continue;
}
continue;
}
try
{
value = getter.Invoke(obj, new object[] { });
}
catch
{
continue;
}
if (value == null)
continue;
if (value is string)
{
strValue = (string)value;
if (strValue.Length == 0)
continue;
}
else if (value is bool)
{
strValue = ((bool)value ? "true" : "false");
}
else if (value is double)
{
double d = (double)value;
if (d == 0.0)
continue;
strValue = d.ToString();
}
else if (value is DateTime)
{
DateTime dt = (DateTime)value;
if (dt == DateTime.MinValue)
continue;
strValue = dt.ToLongTimeString();
}
else if (value is int)
{
int i = (int)value;
if (i == 0)
continue;
strValue = i.ToString();
}
else if (value is long)
{
long l = (long)value;
if (l == 0)
continue;
strValue = l.ToString();
}
else
strValue = value.ToString();
if (notFirst)
sb.Append("; ");
notFirst = true;
sb.Append(string.Concat(getter.Name.Substring(4), ":", strValue));
}
// Second step - all lists
foreach (MethodInfo getter in arrays)
{
value = null;
try
{
value = getter.Invoke(obj, new object[] { });
}
catch
{
continue;
}
if (value == null)
continue;
#region List
IList list;
if (value is IList && (list = (IList)value).Count > 0)
{
if (notFirst)
sb.Append("; ");
notFirst = true;
sb.Append(getter.Name.Substring(4));
sb.Append(":[");
bool notFirst2 = false;
foreach (object o in list)
{
if (notFirst2)
sb.Append("; ");
notFirst2 = true;
sb.Append(o.ToString());
}
sb.Append("]");
continue;
}
#endregion
#region IDictionary
else if (value is IDictionary && ((IDictionary)value).Count > 0)
{
if (notFirst)
sb.Append("; ");
notFirst = true;
sb.Append(getter.Name.Substring(4));
sb.Append(":[");
bool notFirst2 = false;
foreach (object key in ((IDictionary)value).Keys)
{
object o = ((IDictionary)value)[key];
if (notFirst2)
sb.Append("; ");
notFirst2 = true;
sb.Append(string.Concat(key, ":", o));
}
sb.Append("]");
continue;
}
#endregion
}
sb.Append("}");
return sb.ToString();
}
#endregion
#region SetAttributes(object evt, IDictionary attributes)
internal static void SetAttributes(IParseSupport o, Dictionary<string, string> attributes)
{
Type dataType;
object val;
// Preparse attributes
attributes = o.ParseSpecial(attributes);
IDictionary setters = Helper.GetSetters(o.GetType());
MethodInfo setter;
foreach (string name in attributes.Keys)
{
if (name == "event")
continue;
if (name == "source")
setter = (MethodInfo)setters["src"];
else
setter = (MethodInfo)setters[stripIllegalCharacters(name)];
if (setter == null)
{
// No setter found to key, try general parser
if (!o.Parse(name, (string)attributes[name]))
{
#if LOGGER
logger.Error("Unable to set property '" + name + "' on " + o.GetType() + ": no setter");
#endif
throw new ManagerException("Parse error key '" + name + "' on " + o.GetType());
}
}
else
{
dataType = (setter.GetParameters()[0]).ParameterType;
if (dataType == typeof(bool))
val = Helper.IsTrue((string)attributes[name]);
else if (dataType == typeof(string))
val = Helper.ParseString((string)attributes[name]);
else if (dataType == typeof(Int32))
{
Int32 v = 0;
Int32.TryParse((string)attributes[name], out v);
val = v;
}
else if (dataType == typeof(Int64))
{
Int64 v = 0;
Int64.TryParse((string)attributes[name], out v);
val = v;
}
else if (dataType == typeof(double))
{
Double v = 0.0;
Double.TryParse((string)attributes[name], System.Globalization.NumberStyles.AllowDecimalPoint, Common.CultureInfoEn, out v);
val = v;
}
else if (dataType == typeof(decimal))
{
Decimal v = 0;
Decimal.TryParse((string)attributes[name], System.Globalization.NumberStyles.AllowDecimalPoint, Common.CultureInfoEn, out v);
val = v;
}
else if (dataType.IsEnum)
{
try
{
val = Convert.ChangeType(Enum.Parse(dataType, (string)attributes[name], true), dataType);
}
catch (Exception ex)
{
#if LOGGER
logger.Error("Unable to convert value '" + attributes[name] + "' of property '" + name + "' on " + o.GetType() + " to required enum type " + dataType, ex);
continue;
#else
throw new ManagerException("Unable to convert value '" + attributes[name] + "' of property '" + name + "' on " + o.GetType() + " to required enum type " + dataType, ex); #endif
#endif
}
}
else
{
try
{
ConstructorInfo constructor = dataType.GetConstructor(new Type[] { typeof(string) });
val = constructor.Invoke(new object[] { attributes[name] });
}
catch (Exception ex)
{
#if LOGGER
logger.Error("Unable to convert value '" + attributes[name] + "' of property '" + name + "' on " + o.GetType() + " to required type " + dataType, ex);
continue;
#else
throw new ManagerException("Unable to convert value '" + attributes[name] + "' of property '" + name + "' on " + o.GetType() + " to required type " + dataType, ex);
#endif
}
}
try
{
setter.Invoke(o, new object[] { val });
}
catch (Exception ex)
{
#if LOGGER
logger.Error("Unable to set property '" + name + "' on " + o.GetType(), ex);
continue;
#else
throw new ManagerException("Unable to set property '" + name + "' on " + o.GetType(), ex);
#endif
}
}
}
}
#endregion
#region AddKeyValue(IDictionary list, string line)
internal static void AddKeyValue(IDictionary list, string line)
{
int delimiterIndex = line.IndexOf(":");
if (delimiterIndex > 0 && line.Length > delimiterIndex + 1)
{
string name = line.Substring(0, delimiterIndex).ToLower(Helper.CultureInfo).Trim();
string val = line.Substring(delimiterIndex + 1).Trim();
if (val == "<null>")
list[name] = null;
else
list[name] = val;
}
}
#endregion
#region stripIllegalCharacters(string s)
/// <summary>
/// Strips all illegal charaters from the given lower case string.
/// </summary>
/// <param name="s">the original string</param>
/// <returns>the string with all illegal characters stripped</returns>
private static string stripIllegalCharacters(string s)
{
char c;
bool needsStrip = false;
if (string.IsNullOrEmpty(s))
return null;
for (int i = 0; i < s.Length; i++)
{
c = s[i];
if (c >= '0' && c <= '9')
continue;
else if (c >= 'a' && c <= 'z')
continue;
else if (c >= 'A' && c <= 'Z')
continue;
else
{
needsStrip = true;
break;
}
}
if (!needsStrip)
return s;
StringBuilder sb = new StringBuilder(s.Length);
for (int i = 0; i < s.Length; i++)
{
c = s[i];
if (c >= '0' && c <= '9')
sb.Append(c);
else if (c >= 'a' && c <= 'z')
sb.Append(c);
else if (c >= 'A' && c <= 'Z')
sb.Append(c);
}
return sb.ToString();
}
#endregion
#region BuildResponse(IDictionary attributes)
/// <summary>
/// Constructs an instance of ManagerResponse based on a map of attributes.
/// </summary>
/// <param name="attributes">the attributes and their values. The keys of this map must be all lower case.</param>
/// <returns>the response with the given attributes.</returns>
internal static ManagerResponse BuildResponse(Dictionary<string, string> attributes)
{
ManagerResponse response;
string responseType = ((string)attributes["response"]).ToLower(Helper.CultureInfo);
// Determine type
if (responseType == "error")
response = new ManagerError();
else if (attributes.ContainsKey("challenge"))
response = new ChallengeResponse();
else if (attributes.ContainsKey("mailbox") && attributes.ContainsKey("waiting"))
response = new MailboxStatusResponse();
else if (attributes.ContainsKey("mailbox") && attributes.ContainsKey("newmessages") && attributes.ContainsKey("oldmessages"))
response = new MailboxCountResponse();
else if (attributes.ContainsKey("exten") && attributes.ContainsKey("context") && attributes.ContainsKey("hint") && attributes.ContainsKey("status"))
response = new ExtensionStateResponse();
else
response = new ManagerResponse();
Helper.SetAttributes(response, attributes);
return response;
}
#endregion
#region BuildEvent(Hashtable list, object source, IDictionary attributes)
/// <summary>
/// Builds the event based on the given map of attributes and the registered event classes.
/// </summary>
/// <param name="source">source attribute for the event</param>
/// <param name="attributes">map containing event attributes</param>
/// <returns>a concrete instance of ManagerEvent or <code>null</code> if no event class was registered for the event type.</returns>
internal static ManagerEvent BuildEvent(IDictionary<int, ConstructorInfo> list, ManagerConnection source, Dictionary<string, string> attributes)
{
ManagerEvent e;
string eventType;
ConstructorInfo constructor = null;
int hash, hashEvent;
eventType = ((string)attributes["event"]).ToLower(Helper.CultureInfo);
// Remove Event tail from event name (ex. JabberEvent)
if (eventType.EndsWith("event"))
eventType = eventType.Substring(0, eventType.Length - 5);
hashEvent = eventType.GetHashCode();
if (eventType == "user")
{
string userevent = ((string)attributes["userevent"]).ToLower(Helper.CultureInfo);
hash = string.Concat(eventType, userevent).GetHashCode();
if(list.ContainsKey(hash))
constructor = list[hash];
else
constructor = list[hashEvent];
}
else if (list.ContainsKey(hashEvent))
constructor = list[hashEvent];
if (constructor == null)
e = new UnknownEvent(source);
else
{
try
{
e = (ManagerEvent)constructor.Invoke(new object[] { source });
}
catch (Exception ex)
{
#if LOGGER
logger.Error("Unable to create new instance of " + eventType, ex);
return null;
#else
throw ex;
#endif
}
}
SetAttributes(e, attributes);
// ResponseEvents are sent in response to a ManagerAction if the
// response contains lots of data. They include the actionId of
// the corresponding ManagerAction.
if (e is ResponseEvent)
{
ResponseEvent responseEvent = (ResponseEvent)e;
string actionId = responseEvent.ActionId;
if (actionId != null)
{
responseEvent.ActionId = Helper.StripInternalActionId(actionId);
responseEvent.InternalActionId = Helper.GetInternalActionId(actionId);
}
}
return e;
}
#endregion
#region RegisterBuiltinEventClasses(Hashtable list)
/// <summary>
/// Register buildin Event classes
/// </summary>
/// <param name="list"></param>
internal static void RegisterBuiltinEventClasses(Dictionary<int, ConstructorInfo> list)
{
Assembly assembly = Assembly.GetExecutingAssembly();
Type manager = typeof(ManagerEvent);
foreach (Type type in assembly.GetTypes())
if (type.IsPublic && !type.IsAbstract && manager.IsAssignableFrom(type))
RegisterEventClass(list, type);
}
#endregion
#region RegisterEventClass(Dictionary<string, ConstructorInfo> list, Type clazz)
internal static void RegisterEventClass(Dictionary<int, ConstructorInfo> list, Type clazz)
{
// Ignore all abstract classes
// Class not derived from ManagerEvent
if (clazz.IsAbstract || !typeof(ManagerEvent).IsAssignableFrom(clazz))
return;
string eventType = clazz.Name.ToLower(Helper.CultureInfo);
// Remove "event" at the end (if presents)
if (eventType.EndsWith("event"))
eventType = eventType.Substring(0, eventType.Length - 5);
// If assignable from UserEvent and no "userevent" at the start - add "userevent" to beginning
if (typeof(UserEvent).IsAssignableFrom(clazz) && !eventType.StartsWith("user"))
eventType = "user" + eventType;
int hash = eventType.GetHashCode();
if (list.ContainsKey(hash))
return;
ConstructorInfo constructor = null;
try
{
constructor = clazz.GetConstructor(new Type[] { typeof(ManagerConnection) });
}
catch (MethodAccessException ex)
{
throw new ArgumentException("RegisterEventClass : " + clazz + " has no usable constructor.", ex);
}
if (constructor != null && constructor.IsPublic)
list.Add(hash, constructor);
else
throw new ArgumentException("RegisterEventClass : " + clazz + " has no public default constructor");
}
#endregion
#region RegisterEventHandler(Dictionary<int, int> list, int index, Type eventType)
internal static void RegisterEventHandler(Dictionary<int, int> list, int index, Type eventType)
{
int eventHash = eventType.Name.GetHashCode();
if (list.ContainsKey(eventHash))
throw new ArgumentException("Event class already registered : " + eventType.Name);
list.Add(eventHash, index);
}
#endregion
}
}