The Labs \ Source Viewer \ SSCLI \ System.Runtime.Remoting.Messaging \ CallContextSecurityData

  1. // ==++==
  2. //
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. //
  14. // ==--==
  15. // Implementation of CallContext ... currently leverages off
  16. // the LocalDataStore facility.
  17. namespace System.Runtime.Remoting.Messaging
  18. {
  19.    
  20.     using System.Threading;
  21.     using System.Runtime.Remoting;
  22.     using System.Security.Principal;
  23.     using System.Collections;
  24.     using System.Runtime.Serialization;
  25.     using System.Security.Permissions;
  26.     // This class exposes the API for the users of call context. All methods
  27.     // in CallContext are static and operate upon the call context in the Thread.
  28.     // NOTE: CallContext is a specialized form of something that behaves like
  29.     // TLS for method calls. However, since the call objects may get serialized
  30.     // and deserialized along the path, it is tough to guarantee identity
  31.     // preservation.
  32.     // The LogicalCallContext class has all the actual functionality. We have
  33.     // to use this scheme because Remoting message sinks etc do need to have
  34.     // the distinction between the call context on the physical thread and
  35.     // the call context that the remoting message actually carries. In most cases
  36.     // they will operate on the message's call context and hence the latter
  37.     // exposes the same set of methods as instance methods.
  38.    
  39.     // Only statics does not need to marked with the serializable attribute
  40.     [Serializable()]
  41.     [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.Infrastructure)]
  42.     [System.Runtime.InteropServices.ComVisible(true)]
  43.     public sealed class CallContext
  44.     {
  45.         private CallContext()
  46.         {
  47.         }
  48.        
  49.         // Get the logical call context object for the current thread.
  50.         static internal LogicalCallContext GetLogicalCallContext()
  51.         {
  52.             return Thread.CurrentThread.GetLogicalCallContext();
  53.         }
  54.        
  55.         // Sets the given logical call context object on the thread.
  56.         // Returns the previous one.
  57.         static internal LogicalCallContext SetLogicalCallContext(LogicalCallContext callCtx)
  58.         {
  59.             return Thread.CurrentThread.SetLogicalCallContext(callCtx);
  60.         }
  61.        
  62.         static internal LogicalCallContext SetLogicalCallContext(Thread currThread, LogicalCallContext callCtx)
  63.         {
  64.             return currThread.SetLogicalCallContext(callCtx);
  65.         }
  66.        
  67.         static internal CallContextSecurityData SecurityData {
  68.             get { return Thread.CurrentThread.GetLogicalCallContext().SecurityData; }
  69.         }
  70.        
  71.         static internal CallContextRemotingData RemotingData {
  72.             get { return Thread.CurrentThread.GetLogicalCallContext().RemotingData; }
  73.         }
  74.        
  75.        
  76. /*=========================================================================
  77.         ** Frees a named data slot.
  78.         =========================================================================*/       
  79.         public static void FreeNamedDataSlot(string name)
  80.         {
  81.             Thread.CurrentThread.GetLogicalCallContext().FreeNamedDataSlot(name);
  82.             Thread.CurrentThread.GetIllogicalCallContext().FreeNamedDataSlot(name);
  83.         }
  84.        
  85. /*=========================================================================
  86.         ** Get data on the logical call context
  87.         =========================================================================*/       
  88.         public static object LogicalGetData(string name)
  89.         {
  90.             LogicalCallContext lcc = Thread.CurrentThread.GetLogicalCallContext();
  91.             return lcc.GetData(name);
  92.         }
  93.        
  94. /*=========================================================================
  95.         ** Get data on the illogical call context
  96.         =========================================================================*/       
  97.         private static object IllogicalGetData(string name)
  98.         {
  99.             IllogicalCallContext ilcc = Thread.CurrentThread.GetIllogicalCallContext();
  100.             return ilcc.GetData(name);
  101.         }
  102.        
  103.         static internal IPrincipal Principal {
  104.             get {
  105.                 LogicalCallContext lcc = GetLogicalCallContext();
  106.                 return lcc.Principal;
  107.             }
  108.            
  109.             set {
  110.                 LogicalCallContext lcc = GetLogicalCallContext();
  111.                 lcc.Principal = value;
  112.             }
  113.         }
  114.        
  115.         public static object HostContext {
  116.             get {
  117.                 object hC;
  118.                 IllogicalCallContext ilcc = Thread.CurrentThread.GetIllogicalCallContext();
  119.                 hC = ilcc.HostContext;
  120.                 if (hC == null) {
  121.                     LogicalCallContext lcc = GetLogicalCallContext();
  122.                     hC = lcc.HostContext;
  123.                 }
  124.                 return hC;
  125.             }
  126.             [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.Infrastructure)]
  127.             set {
  128.                 if (value is ILogicalThreadAffinative) {
  129.                     IllogicalCallContext ilcc = Thread.CurrentThread.GetIllogicalCallContext();
  130.                     ilcc.HostContext = null;
  131.                     LogicalCallContext lcc = GetLogicalCallContext();
  132.                     lcc.HostContext = value;
  133.                 }
  134.                 else {
  135.                     LogicalCallContext lcc = GetLogicalCallContext();
  136.                     lcc.HostContext = null;
  137.                     IllogicalCallContext ilcc = Thread.CurrentThread.GetIllogicalCallContext();
  138.                     ilcc.HostContext = value;
  139.                 }
  140.             }
  141.         }
  142.        
  143.         public static object GetData(string name)
  144.         {
  145.             object o = LogicalGetData(name);
  146.             if (o == null) {
  147.                 return IllogicalGetData(name);
  148.             }
  149.             else {
  150.                 return o;
  151.             }
  152.         }
  153.        
  154.         public static void SetData(string name, object data)
  155.         {
  156.             if (data is ILogicalThreadAffinative) {
  157.                 LogicalSetData(name, data);
  158.             }
  159.             else {
  160.                 LogicalCallContext lcc = Thread.CurrentThread.GetLogicalCallContext();
  161.                 lcc.FreeNamedDataSlot(name);
  162.                 IllogicalCallContext ilcc = Thread.CurrentThread.GetIllogicalCallContext();
  163.                 ilcc.SetData(name, data);
  164.             }
  165.         }
  166.         public static void LogicalSetData(string name, object data)
  167.         {
  168.             IllogicalCallContext ilcc = Thread.CurrentThread.GetIllogicalCallContext();
  169.             ilcc.FreeNamedDataSlot(name);
  170.             LogicalCallContext lcc = Thread.CurrentThread.GetLogicalCallContext();
  171.             lcc.SetData(name, data);
  172.         }
  173.        
  174.        
  175.         public static Header[] GetHeaders()
  176.         {
  177.             LogicalCallContext lcc = Thread.CurrentThread.GetLogicalCallContext();
  178.             return lcc.InternalGetHeaders();
  179.         }
  180.         // GetHeaders
  181.         public static void SetHeaders(Header[] headers)
  182.         {
  183.             LogicalCallContext lcc = Thread.CurrentThread.GetLogicalCallContext();
  184.             lcc.InternalSetHeaders(headers);
  185.         }
  186.         // SetHeaders
  187.     }
  188.     // class CallContext
  189.     [System.Runtime.InteropServices.ComVisible(true)]
  190.     public interface ILogicalThreadAffinative
  191.     {
  192.     }
  193.    
  194.     internal class IllogicalCallContext : ICloneable
  195.     {
  196.         private Hashtable m_Datastore;
  197.         private object m_HostContext;
  198.        
  199.         private Hashtable Datastore {
  200.             get {
  201.                 if (null == m_Datastore) {
  202.                     // The local store has not yet been created for this thread.
  203.                     m_Datastore = new Hashtable();
  204.                 }
  205.                 return m_Datastore;
  206.             }
  207.         }
  208.        
  209.         internal object HostContext {
  210.             get { return m_HostContext; }
  211.             set { m_HostContext = value; }
  212.         }
  213.        
  214.         internal bool HasUserData {
  215.             get { return ((m_Datastore != null) && (m_Datastore.Count > 0)); }
  216.         }
  217.        
  218. /*=========================================================================
  219.         ** Frees a named data slot.
  220.         =========================================================================*/       
  221.         public void FreeNamedDataSlot(string name)
  222.         {
  223.             Datastore.Remove(name);
  224.         }
  225.        
  226.         public object GetData(string name)
  227.         {
  228.             return Datastore[name];
  229.         }
  230.        
  231.         public void SetData(string name, object data)
  232.         {
  233.             Datastore[name] = data;
  234.         }
  235.        
  236.         public object Clone()
  237.         {
  238.             IllogicalCallContext ilcc = new IllogicalCallContext();
  239.             if (HasUserData) {
  240.                 IDictionaryEnumerator de = m_Datastore.GetEnumerator();
  241.                
  242.                 while (de.MoveNext()) {
  243.                     ilcc.Datastore[(string)de.Key] = de.Value;
  244.                 }
  245.             }
  246.             return ilcc;
  247.         }
  248.     }
  249.    
  250.     // This class handles the actual call context functionality. It leverages on the
  251.     // implementation of local data store ... except that the local store manager is
  252.     // not static. That is to say, allocating a slot in one call context has no effect
  253.     // on another call contexts. Different call contexts are entirely unrelated.
  254.    
  255.     [Serializable()]
  256.     [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.Infrastructure)]
  257.     [System.Runtime.InteropServices.ComVisible(true)]
  258.     public sealed class LogicalCallContext : ISerializable, ICloneable
  259.     {
  260.         // Private static data
  261.         private static Type s_callContextType = typeof(LogicalCallContext);
  262.         private const string s_CorrelationMgrSlotName = "System.Diagnostics.Trace.CorrelationManagerSlot";
  263.        
  264.         // Private member data
  265.         private Hashtable m_Datastore;
  266.         private CallContextRemotingData m_RemotingData = null;
  267.         private CallContextSecurityData m_SecurityData = null;
  268.         private object m_HostContext = null;
  269.         private bool m_IsCorrelationMgr = false;
  270.        
  271.         // _sendHeaders is for Headers that should be sent out on the next call.
  272.         // _recvHeaders are for Headers that came from a response.
  273.         private Header[] _sendHeaders = null;
  274.         private Header[] _recvHeaders = null;
  275.        
  276.        
  277.         internal LogicalCallContext()
  278.         {
  279.         }
  280.        
  281.         internal LogicalCallContext(SerializationInfo info, StreamingContext context)
  282.         {
  283.             SerializationInfoEnumerator e = info.GetEnumerator();
  284.             while (e.MoveNext()) {
  285.                 if (e.Name.Equals("__RemotingData")) {
  286.                     m_RemotingData = (CallContextRemotingData)e.Value;
  287.                 }
  288.                 else if (e.Name.Equals("__SecurityData")) {
  289.                     if (context.State == StreamingContextStates.CrossAppDomain) {
  290.                         m_SecurityData = (CallContextSecurityData)e.Value;
  291.                     }
  292.                     else {
  293.                         BCLDebug.Assert(false, "Security data should only be serialized in cross appdomain case.");
  294.                     }
  295.                 }
  296.                 else if (e.Name.Equals("__HostContext")) {
  297.                     m_HostContext = e.Value;
  298.                 }
  299.                 else if (e.Name.Equals("__CorrelationMgrSlotPresent")) {
  300.                     m_IsCorrelationMgr = (bool)e.Value;
  301.                 }
  302.                 else {
  303.                     Datastore[e.Name] = e.Value;
  304.                 }
  305.                
  306.             }
  307.         }
  308.        
  309.         [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
  310.         public void GetObjectData(SerializationInfo info, StreamingContext context)
  311.         {
  312.             if (info == null)
  313.                 throw new ArgumentNullException("info");
  314.             info.SetType(s_callContextType);
  315.             if (m_RemotingData != null) {
  316.                 info.AddValue("__RemotingData", m_RemotingData);
  317.             }
  318.             if (m_SecurityData != null) {
  319.                 if (context.State == StreamingContextStates.CrossAppDomain) {
  320.                     info.AddValue("__SecurityData", m_SecurityData);
  321.                 }
  322.             }
  323.             if (m_HostContext != null) {
  324.                 info.AddValue("__HostContext", m_HostContext);
  325.             }
  326.             if (m_IsCorrelationMgr) {
  327.                 info.AddValue("__CorrelationMgrSlotPresent", m_IsCorrelationMgr);
  328.             }
  329.             if (HasUserData) {
  330.                 IDictionaryEnumerator de = m_Datastore.GetEnumerator();
  331.                
  332.                 while (de.MoveNext()) {
  333.                     info.AddValue((string)de.Key, de.Value);
  334.                 }
  335.             }
  336.            
  337.         }
  338.        
  339.        
  340.         // ICloneable::Clone
  341.         // Used to create a deep copy of the call context when an async
  342.         // call starts.
  343.        
  344.         public object Clone()
  345.         {
  346.             LogicalCallContext lc = new LogicalCallContext();
  347.             if (m_RemotingData != null)
  348.                 lc.m_RemotingData = (CallContextRemotingData)m_RemotingData.Clone();
  349.             if (m_SecurityData != null)
  350.                 lc.m_SecurityData = (CallContextSecurityData)m_SecurityData.Clone();
  351.             if (m_HostContext != null)
  352.                 lc.m_HostContext = m_HostContext;
  353.             if (HasUserData) {
  354.                 IDictionaryEnumerator de = m_Datastore.GetEnumerator();
  355.                
  356.                 if (!lc.m_IsCorrelationMgr) {
  357.                     while (de.MoveNext()) {
  358.                         lc.Datastore[(string)de.Key] = de.Value;
  359.                     }
  360.                 }
  361.                 else {
  362.                     while (de.MoveNext()) {
  363.                         string key = (string)de.Key;
  364.                        
  365.                         // Deep clone "System.Diagnostics.Trace.CorrelationManagerSlot"
  366.                         if (key.Equals(s_CorrelationMgrSlotName)) {
  367.                             lc.Datastore[key] = ((ICloneable)de.Value).Clone();
  368.                         }
  369.                         else
  370.                             lc.Datastore[key] = de.Value;
  371.                     }
  372.                 }
  373.             }
  374.             return lc;
  375.         }
  376.        
  377.         // Used to do a (limited) merge the call context from a returning async call
  378.         internal void Merge(LogicalCallContext lc)
  379.         {
  380.             // we ignore the RemotingData & SecurityData
  381.             // and only merge the user sections of the two call contexts
  382.             // the idea being that if the original call had any
  383.             // identity/remoting callID that should remain unchanged
  384.            
  385.             // If we have a non-null callContext and it is not the same
  386.             // as the one on the current thread (can happen in x-context async)
  387.             // and there is any userData in the callContext, do the merge
  388.             if ((lc != null) && (this != lc) && lc.HasUserData) {
  389.                 IDictionaryEnumerator de = lc.Datastore.GetEnumerator();
  390.                
  391.                 while (de.MoveNext()) {
  392.                     Datastore[(string)de.Key] = de.Value;
  393.                 }
  394.             }
  395.         }
  396.        
  397.         public bool HasInfo {
  398.             get {
  399.                 bool fInfo = false;
  400.                
  401.                 // Set the flag to true if there is either remoting data, or
  402.                 // security data or user data
  403.                 if ((m_RemotingData != null && m_RemotingData.HasInfo) || (m_SecurityData != null && m_SecurityData.HasInfo) || (m_HostContext != null) || HasUserData) {
  404.                     fInfo = true;
  405.                 }
  406.                
  407.                 return fInfo;
  408.             }
  409.         }
  410.        
  411.         private bool HasUserData {
  412.             get { return ((m_Datastore != null) && (m_Datastore.Count > 0)); }
  413.         }
  414.        
  415.         internal CallContextRemotingData RemotingData {
  416.             get {
  417.                 if (m_RemotingData == null)
  418.                     m_RemotingData = new CallContextRemotingData();
  419.                
  420.                 return m_RemotingData;
  421.             }
  422.         }
  423.        
  424.         internal CallContextSecurityData SecurityData {
  425.             get {
  426.                 if (m_SecurityData == null)
  427.                     m_SecurityData = new CallContextSecurityData();
  428.                
  429.                 return m_SecurityData;
  430.             }
  431.         }
  432.        
  433.         internal object HostContext {
  434.             get { return m_HostContext; }
  435.             set { m_HostContext = value; }
  436.         }
  437.        
  438.         private Hashtable Datastore {
  439.             get {
  440.                 if (null == m_Datastore) {
  441.                     // The local store has not yet been created for this thread.
  442.                     m_Datastore = new Hashtable();
  443.                 }
  444.                 return m_Datastore;
  445.             }
  446.         }
  447.        
  448.         // This is used for quick access to the current principal when going
  449.         // between appdomains.
  450.         internal IPrincipal Principal {
  451.             get {
  452.                 // This MUST not fault in the security data object if it doesn't exist.
  453.                 if (m_SecurityData != null)
  454.                     return m_SecurityData.Principal;
  455.                
  456.                 return null;
  457.             }
  458.             // get
  459.             set { SecurityData.Principal = value; }
  460.         }
  461.         // set
  462.         // Principal
  463. /*=========================================================================
  464.         ** Frees a named data slot.
  465.         =========================================================================*/       
  466.         public void FreeNamedDataSlot(string name)
  467.         {
  468.             Datastore.Remove(name);
  469.         }
  470.        
  471.         public object GetData(string name)
  472.         {
  473.             return Datastore[name];
  474.         }
  475.        
  476.         public void SetData(string name, object data)
  477.         {
  478.             Datastore[name] = data;
  479.             if (name.Equals(s_CorrelationMgrSlotName))
  480.                 m_IsCorrelationMgr = true;
  481.         }
  482.        
  483.         private Header[] InternalGetOutgoingHeaders()
  484.         {
  485.             Header[] outgoingHeaders = _sendHeaders;
  486.             _sendHeaders = null;
  487.            
  488.             // A new remote call is being made, so we null out the
  489.             // current received headers so these can't be confused
  490.             // with a response from the next call.
  491.             _recvHeaders = null;
  492.            
  493.             return outgoingHeaders;
  494.         }
  495.         // InternalGetOutgoingHeaders
  496.        
  497.         internal void InternalSetHeaders(Header[] headers)
  498.         {
  499.             _sendHeaders = headers;
  500.             _recvHeaders = null;
  501.         }
  502.         // InternalSetHeaders
  503.        
  504.         internal Header[] InternalGetHeaders()
  505.         {
  506.             // If _sendHeaders is currently set, we always want to return them.
  507.             if (_sendHeaders != null)
  508.                 return _sendHeaders;
  509.            
  510.             // Either _recvHeaders is non-null and those are the ones we want to
  511.             // return, or there are no currently set headers, so we'll return
  512.             // null.
  513.             return _recvHeaders;
  514.         }
  515.         // InternalGetHeaders
  516.         // Nulls out the principal if its not serializable.
  517.         // Since principals do flow for x-appdomain casses
  518.         // we need to handle this behaviour both during invoke
  519.         // and response
  520.         internal IPrincipal RemovePrincipalIfNotSerializable()
  521.         {
  522.             IPrincipal currentPrincipal = this.Principal;
  523.             // If the principal is not serializable, we need to
  524.             // null it out.
  525.             if (currentPrincipal != null) {
  526.                 if (!currentPrincipal.GetType().IsSerializable)
  527.                     this.Principal = null;
  528.             }
  529.             return currentPrincipal;
  530.         }
  531.        
  532.         // Takes outgoing headers and inserts them
  533.         internal void PropagateOutgoingHeadersToMessage(IMessage msg)
  534.         {
  535.             Header[] headers = InternalGetOutgoingHeaders();
  536.            
  537.             if (headers != null) {
  538.                 BCLDebug.Assert(msg != null, "Why is the message null?");
  539.                
  540.                 IDictionary properties = msg.Properties;
  541.                 BCLDebug.Assert(properties != null, "Why are the properties null?");
  542.                
  543.                 foreach (Header header in headers) {
  544.                     // add header to the message dictionary
  545.                     if (header != null) {
  546.                         // The header key is composed from its name and namespace.
  547.                        
  548.                         string name = GetPropertyKeyForHeader(header);
  549.                        
  550.                         properties[name] = header;
  551.                     }
  552.                 }
  553.             }
  554.         }
  555.         // PropagateOutgoingHeadersToMessage
  556.         // Retrieve key to use for header.
  557.         static internal string GetPropertyKeyForHeader(Header header)
  558.         {
  559.             if (header == null)
  560.                 return null;
  561.            
  562.             if (header.HeaderNamespace != null)
  563.                 return header.Name + ", " + header.HeaderNamespace;
  564.             else
  565.                 return header.Name;
  566.         }
  567.         // GetPropertyKeyForHeader
  568.        
  569.         // Take headers out of message and stores them in call context
  570.         internal void PropagateIncomingHeadersToCallContext(IMessage msg)
  571.         {
  572.             BCLDebug.Assert(msg != null, "Why is the message null?");
  573.            
  574.             // If it's an internal message, we can quickly tell if there are any
  575.             // headers.
  576.             IInternalMessage iim = msg as IInternalMessage;
  577.             if (iim != null) {
  578.                 if (!iim.HasProperties()) {
  579.                     // If there are no properties just return immediately.
  580.                     return;
  581.                 }
  582.             }
  583.            
  584.             IDictionary properties = msg.Properties;
  585.             BCLDebug.Assert(properties != null, "Why are the properties null?");
  586.            
  587.             IDictionaryEnumerator e = (IDictionaryEnumerator)properties.GetEnumerator();
  588.            
  589.             // cycle through the properties to get a count of the headers
  590.             int count = 0;
  591.             while (e.MoveNext()) {
  592.                 string key = (string)e.Key;
  593.                 if (!key.StartsWith("__", StringComparison.Ordinal)) {
  594.                     // We don't want to have to check for special values, so we
  595.                     // blanketly state that header names can't start with
  596.                     // double underscore.
  597.                     if (e.Value is Header)
  598.                         count++;
  599.                 }
  600.             }
  601.            
  602.             // If there are headers, create array and set it to the received header property
  603.             Header[] headers = null;
  604.             if (count > 0) {
  605.                 headers = new Header[count];
  606.                
  607.                 count = 0;
  608.                 e.Reset();
  609.                 while (e.MoveNext()) {
  610.                     string key = (string)e.Key;
  611.                     if (!key.StartsWith("__", StringComparison.Ordinal)) {
  612.                         Header header = e.Value as Header;
  613.                         if (header != null)
  614.                             headers[count++] = header;
  615.                     }
  616.                 }
  617.             }
  618.            
  619.             _recvHeaders = headers;
  620.             _sendHeaders = null;
  621.         }
  622.         // PropagateIncomingHeadersToCallContext
  623.     }
  624.     // class LogicalCallContext
  625.    
  626.    
  627.     [Serializable()]
  628.     internal class CallContextSecurityData : ICloneable
  629.     {
  630.         // This is used for the special getter/setter for security related
  631.         // info in the callContext.
  632.         IPrincipal _principal;
  633.         internal IPrincipal Principal {
  634.             get { return _principal; }
  635.             set { _principal = value; }
  636.         }
  637.        
  638.         // Checks if there is any useful data to be serialized
  639.         internal bool HasInfo {
  640.             get { return (null != _principal); }
  641.         }
  642.        
  643.        
  644.         public object Clone()
  645.         {
  646.             CallContextSecurityData sd = new CallContextSecurityData();
  647.             sd._principal = _principal;
  648.             return sd;
  649.         }
  650.        
  651.     }
  652.    
  653.     [Serializable()]
  654.     internal class CallContextRemotingData : ICloneable
  655.     {
  656.         // This is used for the special getter/setter for remoting related
  657.         // info in the callContext.
  658.         string _logicalCallID;
  659.        
  660.         internal string LogicalCallID {
  661.             get { return _logicalCallID; }
  662.             set { _logicalCallID = value; }
  663.         }
  664.        
  665.         // Checks if there is any useful data to be serialized
  666.         internal bool HasInfo {
  667. // Keep this updated if we add more stuff to remotingData!
  668.             get { return (_logicalCallID != null); }
  669.         }
  670.        
  671.         public object Clone()
  672.         {
  673.             CallContextRemotingData rd = new CallContextRemotingData();
  674.             rd.LogicalCallID = LogicalCallID;
  675.             return rd;
  676.         }
  677.     }
  678. }

Developer Fusion