// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
// Implementation of CallContext ... currently leverages off
// the LocalDataStore facility.
namespace System.Runtime.Remoting.Messaging{    

    using System.Threading;
    using System.Runtime.Remoting;
    using System.Security.Principal;
    using System.Collections;
    using System.Runtime.Serialization;
    using System.Security.Permissions;    
    // This class exposes the API for the users of call context. All methods
    // in CallContext are static and operate upon the call context in the Thread.
    // NOTE: CallContext is a specialized form of something that behaves like 
    // TLS for method calls. However, since the call objects may get serialized 
    // and deserialized along the path, it is tough to guarantee identity
    // preservation.
    // The LogicalCallContext class has all the actual functionality. We have
    // to use this scheme because Remoting message sinks etc do need to have
    // the distinction between the call context on the physical thread and 
    // the call context that the remoting message actually carries. In most cases
    // they will operate on the message's call context and hence the latter 
    // exposes the same set of methods as instance methods.

    // Only statics does not need to marked with the serializable attribute
    [System.Security.SecurityCritical]  // auto-generated_required
    [Serializable]
    [System.Runtime.InteropServices.ComVisible(true)]
    public sealed class CallContext
    {
        private CallContext()
        {
        }

#if MONO
        internal static object SetCurrentCallContext (LogicalCallContext ctx)
        {
            return null;
        }
#endif

        // Sets the given logical call context object on the thread.
        // Returns the previous one.
        internal static LogicalCallContext SetLogicalCallContext(
            LogicalCallContext callCtx)
        {
            ExecutionContext ec = Thread.CurrentThread.GetMutableExecutionContext();
            LogicalCallContext prev = ec.LogicalCallContext;
            ec.LogicalCallContext = callCtx;
            return prev;
        }

        /*=========================================================================
        ** Frees a named data slot.
        =========================================================================*/
        [System.Security.SecurityCritical]  // auto-generated
        public static void FreeNamedDataSlot(String name)
        {
            ExecutionContext ec = Thread.CurrentThread.GetMutableExecutionContext();
            ec.LogicalCallContext.FreeNamedDataSlot(name);
            ec.IllogicalCallContext.FreeNamedDataSlot(name);
        }

        /*=========================================================================
        ** Get data on the logical call context
        =========================================================================*/
        [System.Security.SecurityCritical]  // auto-generated
        public static Object LogicalGetData(String name)
        {
            return Thread.CurrentThread.GetExecutionContextReader().LogicalCallContext.GetData(name);              
        }

        /*=========================================================================
        ** Get data on the illogical call context
        =========================================================================*/
        private static Object IllogicalGetData(String name)
        {
            return Thread.CurrentThread.GetExecutionContextReader().IllogicalCallContext.GetData(name);
        }

        internal static IPrincipal Principal
        {
            [System.Security.SecurityCritical]  // auto-generated
            get
            {
                return Thread.CurrentThread.GetExecutionContextReader().LogicalCallContext.Principal;
            }

            [System.Security.SecurityCritical]  // auto-generated
            set
            {
                Thread.CurrentThread.
                    GetMutableExecutionContext().LogicalCallContext.Principal = value;
            }
        }

        public static Object HostContext
        {
            [System.Security.SecurityCritical]  // auto-generated
            get
            {
                ExecutionContext.Reader ec = Thread.CurrentThread.GetExecutionContextReader();

                Object hC = ec.IllogicalCallContext.HostContext;
                if (hC == null)
                    hC = ec.LogicalCallContext.HostContext;
                return hC;
            }
            [System.Security.SecurityCritical]  // auto-generated_required
            set
            {
                ExecutionContext ec = Thread.CurrentThread.GetMutableExecutionContext();
                if (value is ILogicalThreadAffinative)
                {
                    ec.IllogicalCallContext.HostContext = null;
                    ec.LogicalCallContext.HostContext = value;
                }
                else
                {
                    ec.IllogicalCallContext.HostContext = value;
                    ec.LogicalCallContext.HostContext = null;
                }
            }
        }
        
        // <


        [System.Security.SecurityCritical]  // auto-generated
        public static Object GetData(String name)
        {
            Object o = LogicalGetData(name);
            if (o == null)
            {
                return IllogicalGetData(name);
            }
            else
            {
                return o;
            }
        }

        [System.Security.SecurityCritical]  // auto-generated
        public static void SetData(String name, Object data)
        {
            if (data is ILogicalThreadAffinative)
            {
                LogicalSetData(name, data);
            }
            else
            {
                ExecutionContext ec = Thread.CurrentThread.GetMutableExecutionContext();
                ec.LogicalCallContext.FreeNamedDataSlot(name);
                ec.IllogicalCallContext.SetData(name, data);
            }
        }
        [System.Security.SecurityCritical]  // auto-generated
        public static void LogicalSetData(String name, Object data)
        {
            ExecutionContext ec = Thread.CurrentThread.GetMutableExecutionContext();
            ec.IllogicalCallContext.FreeNamedDataSlot(name);
            ec.LogicalCallContext.SetData(name, data);
        }


        [System.Security.SecurityCritical]  // auto-generated
        public static Header[] GetHeaders()
        {
            // Header is mutable, so we need to get these from a mutable ExecutionContext
            LogicalCallContext lcc =  Thread.CurrentThread.GetMutableExecutionContext().LogicalCallContext;
            return lcc.InternalGetHeaders();
        } // GetHeaders

        [System.Security.SecurityCritical]  // auto-generated
        public static void SetHeaders(Header[] headers)
        {            
            LogicalCallContext lcc =  Thread.CurrentThread.GetMutableExecutionContext().LogicalCallContext;
            lcc.InternalSetHeaders(headers);
        } // SetHeaders
        
    } // class CallContext

[System.Runtime.InteropServices.ComVisible(true)]
    public interface ILogicalThreadAffinative
    {
    }

    internal class IllogicalCallContext
    {
        private Hashtable m_Datastore;
        private Object m_HostContext;

        internal struct Reader
        {
            IllogicalCallContext m_ctx;

            public Reader(IllogicalCallContext ctx) { m_ctx = ctx; }

            public bool IsNull { get { return m_ctx == null; } }

            [System.Security.SecurityCritical]
            public Object GetData(String name) { return IsNull ? null : m_ctx.GetData(name); }

            public Object HostContext { get { return IsNull ? null : m_ctx.HostContext; } }
        }

        private Hashtable Datastore
        {
            get 
            { 
                if (null == m_Datastore)
                {
                    // The local store has not yet been created for this thread.
                    m_Datastore = new Hashtable();
                }
                return m_Datastore;
            }
        }
        
        internal Object HostContext
        {
            get
            {
                return m_HostContext;
            }
            set
            {
                m_HostContext = value;
            }
        }

        internal bool HasUserData
        {
            get { return ((m_Datastore != null) && (m_Datastore.Count > 0));}
        }

        /*=========================================================================
        ** Frees a named data slot.
        =========================================================================*/
        public void FreeNamedDataSlot(String name)
        {
            Datastore.Remove(name);
        }

        public Object GetData(String name)
        {
            return Datastore[name];
        }

        public void SetData(String name, Object data)
        {
            Datastore[name] = data;
        }

        public IllogicalCallContext CreateCopy()
        {
            IllogicalCallContext ilcc = new IllogicalCallContext();
            ilcc.HostContext = this.HostContext;
            if (HasUserData)
            {
                IDictionaryEnumerator de = this.m_Datastore.GetEnumerator();
                
                while (de.MoveNext())
                {
                    ilcc.Datastore[(String)de.Key] = de.Value;    
                }
            }      
            return ilcc;
        }
    }

    // This class handles the actual call context functionality. It leverages on the
    // implementation of local data store ... except that the local store manager is
    // not static. That is to say, allocating a slot in one call context has no effect
    // on another call contexts. Different call contexts are entirely unrelated.

    [System.Security.SecurityCritical]  // auto-generated_required
    [Serializable]
    [System.Runtime.InteropServices.ComVisible(true)]
    public sealed class LogicalCallContext : ISerializable, ICloneable
    {
        // Private static data
        private static Type s_callContextType = typeof(LogicalCallContext);
        private const string s_CorrelationMgrSlotName = "System.Diagnostics.Trace.CorrelationManagerSlot";

        /*=========================================================================
        ** Data accessed from managed code that needs to be defined in 
        ** LogicalCallContextObject  to maintain alignment between the two classes.
        ** DON'T CHANGE THESE UNLESS YOU MODIFY LogicalContextObject in vm\object.h
        =========================================================================*/

        // Private member data
        private Hashtable m_Datastore;
        private CallContextRemotingData m_RemotingData = null; 
        private CallContextSecurityData m_SecurityData = null;
        private Object m_HostContext = null;
        private bool m_IsCorrelationMgr = false;

        // _sendHeaders is for Headers that should be sent out on the next call.
        // _recvHeaders are for Headers that came from a response.
        private Header[] _sendHeaders = null;
        private Header[] _recvHeaders = null;
        

        internal LogicalCallContext()
        {   
        }

        internal struct Reader
        {
            LogicalCallContext m_ctx;

            public Reader(LogicalCallContext ctx) { m_ctx = ctx; }

            public bool IsNull { get { return m_ctx == null; } }
            public bool HasInfo { get { return IsNull ? false : m_ctx.HasInfo; } }

            public LogicalCallContext Clone() { return (LogicalCallContext)m_ctx.Clone(); }

            public IPrincipal Principal { get { return IsNull ? null : m_ctx.Principal; } }

            [System.Security.SecurityCritical]
            public Object GetData(String name) { return IsNull ? null : m_ctx.GetData(name); }

            public Object HostContext { get { return IsNull ? null : m_ctx.HostContext; } }
        }

        [System.Security.SecurityCritical]  // auto-generated
        internal LogicalCallContext(SerializationInfo info, StreamingContext context)
        {
            SerializationInfoEnumerator e = info.GetEnumerator();
            while (e.MoveNext())
            {
                if (e.Name.Equals("__RemotingData"))
                {
                    m_RemotingData = (CallContextRemotingData) e.Value;
                }
                else if (e.Name.Equals("__SecurityData"))
                {
                    if (context.State == StreamingContextStates.CrossAppDomain)                        
                    {
                        m_SecurityData = (CallContextSecurityData) e.Value;
                    }
                    else
                    {
                        BCLDebug.Assert(false, "Security data should only be serialized in cross appdomain case.");
                    }
                }
                else if (e.Name.Equals("__HostContext"))
                {
                    m_HostContext = e.Value;
                }
                else if (e.Name.Equals("__CorrelationMgrSlotPresent"))
                {
                    m_IsCorrelationMgr = (bool)e.Value;
                }
                else
                {
                    Datastore[e.Name] = e.Value;    
                }

            }
        }

        [System.Security.SecurityCritical]  // auto-generated_required
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            if (info == null)
                throw new ArgumentNullException("info");
            info.SetType(s_callContextType);
            if (m_RemotingData != null)
            {
                info.AddValue("__RemotingData", m_RemotingData);
            }
            if (m_SecurityData != null)
            {
                if (context.State == StreamingContextStates.CrossAppDomain)
                {
                    info.AddValue("__SecurityData", m_SecurityData);
                }
            }
            if (m_HostContext != null)
            {
                info.AddValue("__HostContext", m_HostContext);
            }
            if (m_IsCorrelationMgr)
            {
                info.AddValue("__CorrelationMgrSlotPresent", m_IsCorrelationMgr);
            }
            if (HasUserData)
            {
                IDictionaryEnumerator de = m_Datastore.GetEnumerator();
                
                while (de.MoveNext())
                {
                    info.AddValue((String)de.Key, de.Value);
                }
            }

        }

        
        // ICloneable::Clone
        // Used to create a deep copy of the call context when an async
        // call starts.

        // <



        [System.Security.SecuritySafeCritical] // overrides public transparent member
        public Object Clone()
        {
            LogicalCallContext lc = new LogicalCallContext();
            if (m_RemotingData != null)
                lc.m_RemotingData = (CallContextRemotingData)m_RemotingData.Clone();
            if (m_SecurityData != null)
                lc.m_SecurityData = (CallContextSecurityData)m_SecurityData.Clone();
            if (m_HostContext != null)
                lc.m_HostContext = m_HostContext;
            lc.m_IsCorrelationMgr = m_IsCorrelationMgr;
            if (HasUserData)
            {
                IDictionaryEnumerator de = m_Datastore.GetEnumerator();
                
                if (!m_IsCorrelationMgr) 
                {
                    while (de.MoveNext())
                    {
                        lc.Datastore[(String)de.Key] = de.Value;  
                    }
                }
                else 
                {
                    while (de.MoveNext())
                    {
                        String key = (String)de.Key;

                        // Deep clone "System.Diagnostics.Trace.CorrelationManagerSlot" 
                        if (key.Equals(s_CorrelationMgrSlotName))
                        {
                            lc.Datastore[key] = ((ICloneable)de.Value).Clone();   
                        }
                        else
                            lc.Datastore[key] = de.Value;    
                    }
                }
            }      
            return lc;
        }

        // Used to do a (limited) merge the call context from a returning async call
        [System.Security.SecurityCritical]  // auto-generated
        internal void Merge(LogicalCallContext lc)
        {
            // we ignore the RemotingData & SecurityData 
            // and only merge the user sections of the two call contexts
            // the idea being that if the original call had any 
            // identity/remoting callID that should remain unchanged

            // If we have a non-null callContext and it is not the same
            // as the one on the current thread (can happen in x-context async)
            // and there is any userData in the callContext, do the merge
            if ((lc != null) && (this != lc) && lc.HasUserData)
            {
                IDictionaryEnumerator de = lc.Datastore.GetEnumerator();
                
                while (de.MoveNext())
                {
                    Datastore[(String)de.Key] = de.Value;  
                }                
            }
        }
        
        public bool HasInfo
        {
            [System.Security.SecurityCritical]  // auto-generated
            get 
            { 
                bool fInfo = false;

                // Set the flag to true if there is either remoting data, or
                // security data or user data
                if( 
                    (m_RemotingData != null &&  m_RemotingData.HasInfo) ||
                    (m_SecurityData != null &&  m_SecurityData.HasInfo) ||
                    (m_HostContext != null) ||
                    HasUserData
                  )
                {
                    fInfo = true;
                }

                return fInfo;
            }
        }

        private bool HasUserData
        {
            get { return ((m_Datastore != null) && (m_Datastore.Count > 0));}
        }

        internal CallContextRemotingData RemotingData
        {
            get 
            {
                if (m_RemotingData == null)
                    m_RemotingData = new CallContextRemotingData();

                return m_RemotingData; 
            }
        }

        internal CallContextSecurityData SecurityData
        {
            get 
            {
                if (m_SecurityData == null)
                    m_SecurityData = new CallContextSecurityData();

                return m_SecurityData; 
            }
        }
        
        internal Object HostContext
        {
            get
            {
                return m_HostContext;
            }
            set
            {
                m_HostContext = value;
            }
        }

        private Hashtable Datastore
        {
            get 
            { 
                if (null == m_Datastore)
                {
                    // The local store has not yet been created for this thread.
                    m_Datastore = new Hashtable();
                }
                return m_Datastore;
            }
        }

        // This is used for quick access to the current principal when going
        // between appdomains.
        internal IPrincipal Principal
        {
            get 
            { 
                // This MUST not fault in the security data object if it doesn't exist.
                if (m_SecurityData != null)
                    return m_SecurityData.Principal;

                return null;
            } // get

            [System.Security.SecurityCritical]  // auto-generated
            set
            {
                SecurityData.Principal = value;
            } // set
        } // Principal

        /*=========================================================================
        ** Frees a named data slot.
        =========================================================================*/
        [System.Security.SecurityCritical]  // auto-generated
        public void FreeNamedDataSlot(String name)
        {
            Datastore.Remove(name);
        }

        [System.Security.SecurityCritical]  // auto-generated
        public Object GetData(String name)
        {
            return Datastore[name];
        }

        [System.Security.SecurityCritical]  // auto-generated
        public void SetData(String name, Object data)
        {
            Datastore[name] = data;
            if (name.Equals(s_CorrelationMgrSlotName)) 
                m_IsCorrelationMgr = true;
        }

        private Header[] InternalGetOutgoingHeaders()
        {
            Header[] outgoingHeaders = _sendHeaders;
            _sendHeaders = null;
        
            // A new remote call is being made, so we null out the
            //   current received headers so these can't be confused
            //   with a response from the next call.
            _recvHeaders = null;

            return outgoingHeaders;
        } // InternalGetOutgoingHeaders


        internal void InternalSetHeaders(Header[] headers)
        {
            _sendHeaders = headers;
            _recvHeaders = null;
        } // InternalSetHeaders


        internal Header[] InternalGetHeaders()
        {
            // If _sendHeaders is currently set, we always want to return them.
            if (_sendHeaders != null)
                return _sendHeaders;

            // Either _recvHeaders is non-null and those are the ones we want to
            //   return, or there are no currently set headers, so we'll return
            //    null.
            return _recvHeaders;
        } // InternalGetHeaders

        // Nulls out the principal if its not serializable.
        // Since principals do flow for x-appdomain cases
        // we need to handle this behaviour both during invoke
        // and response
        [System.Security.SecurityCritical]  // auto-generated
        internal IPrincipal RemovePrincipalIfNotSerializable()
        {
            IPrincipal currentPrincipal = this.Principal;
            // If the principal is not serializable, we need to
            //   null it out.
            if (currentPrincipal != null)
            {
                if (!currentPrincipal.GetType().IsSerializable)
                    this.Principal = null;
            }
            return currentPrincipal;
        }

        // Takes outgoing headers and inserts them        
        [System.Security.SecurityCritical]  // auto-generated
        internal void PropagateOutgoingHeadersToMessage(IMessage msg)
        {
            Header[] headers = InternalGetOutgoingHeaders();
        
            if (headers != null)
            {
                BCLDebug.Assert(msg != null, "Why is the message null?");
            
                IDictionary properties = msg.Properties;
                BCLDebug.Assert(properties != null, "Why are the properties null?");
        
                foreach (Header header in headers)
                {
                    // add header to the message dictionary
                    if (header != null)
                    {
                        // The header key is composed from its name and namespace.
                        
                        String name = GetPropertyKeyForHeader(header);

                        properties[name] = header;
                    }
                }
            }
        } // PropagateOutgoingHeadersToMessage

        // Retrieve key to use for header.
        internal static String GetPropertyKeyForHeader(Header header)
        {
            if (header == null)
                return null;

            if (header.HeaderNamespace != null)
                return header.Name + ", " + header.HeaderNamespace;
            else
                return header.Name;                
        } // GetPropertyKeyForHeader

        // Take headers out of message and stores them in call context
        [System.Security.SecurityCritical]  // auto-generated
        internal void PropagateIncomingHeadersToCallContext(IMessage msg)
        {                  
            BCLDebug.Assert(msg != null, "Why is the message null?");

            // If it's an internal message, we can quickly tell if there are any
            //   headers.
            IInternalMessage iim = msg as IInternalMessage;
            if (iim != null)
            {
                if (!iim.HasProperties())
                {
                    // If there are no properties just return immediately.
                    return;
                }
            }
            
            IDictionary properties = msg.Properties;
            BCLDebug.Assert(properties != null, "Why are the properties null?");
            
            IDictionaryEnumerator e = (IDictionaryEnumerator) properties.GetEnumerator();

            // cycle through the properties to get a count of the headers
            int count = 0;
            while (e.MoveNext())
            {   
                String key = (String)e.Key;
                if (!key.StartsWith("__", StringComparison.Ordinal)) 
                {
                    // We don't want to have to check for special values, so we
                    //   blanketly state that header names can't start with
                    //   double underscore.
                    if (e.Value is Header)
                        count++;
                }
            }

            // If there are headers, create array and set it to the received header property
            Header[] headers = null;
            if (count > 0)
            {
                headers = new Header[count];

                count = 0;
                e.Reset();
                while (e.MoveNext())
                {   
                    String key = (String)e.Key;
                    if (!key.StartsWith("__", StringComparison.Ordinal)) 
                    {
                        Header header = e.Value as Header;
                        if (header != null)
                            headers[count++] = header;
                    }
                }                
            }   

            _recvHeaders = headers;
            _sendHeaders = null;
        } // PropagateIncomingHeadersToCallContext
    } // class LogicalCallContext

    

    [Serializable]   
    internal class CallContextSecurityData : ICloneable
    {
        // This is used for the special getter/setter for security related
        // info in the callContext.
        IPrincipal _principal;
        // <
        internal IPrincipal Principal
        {
            get {return _principal;}
            set {_principal = value;}
        }

        // Checks if there is any useful data to be serialized
        internal bool HasInfo
        {
            get
            {
                return (null != _principal);
            }
            
        }

        public Object Clone()
        {
            CallContextSecurityData sd = new CallContextSecurityData();
            sd._principal = _principal;
            return sd;
        }

    }

    [Serializable]    
    internal class CallContextRemotingData : ICloneable
    {
        // This is used for the special getter/setter for remoting related
        // info in the callContext.
        String _logicalCallID;

        internal String LogicalCallID
        {
            get  {return _logicalCallID;}
            set  {_logicalCallID = value;}
        }
        
        // Checks if there is any useful data to be serialized
        internal bool HasInfo
        {
            get
            {
                // Keep this updated if we add more stuff to remotingData!
                return (_logicalCallID!=null);
            }
        }

        public Object Clone()
        {
            CallContextRemotingData rd = new CallContextRemotingData();
            rd.LogicalCallID = LogicalCallID;
            return rd;
        }
    }
 }
