////////////////////////////////////////////////////////////////////////////////
//
//  Microsoft Research Singularity
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
//  File:   EndpointCore.cs
//

//  HACK: Because we currently compile this file as part of the Kernel with C#, we can't
//        make EndpointCore a rep struct, which is necessary for inheriting from it.
//        The compiler and Bartok recognize EndpointCore as special and treat it as
//        unsealed.
//        This hack can be removed, once we compile it with Sing#.
//        DON'T change the name until then!

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Diagnostics;
using Microsoft.Singularity.Channels;
using Microsoft.Singularity.Security;
using Microsoft.Singularity.Memory;

namespace Microsoft.Singularity.Channels
{
    using Microsoft.Singularity.V1.Threads;
    using Microsoft.Singularity.V1.Security;
    using Microsoft.Singularity.V1.Services;
    using Microsoft.Singularity.V1.Types;
    using Allocation = Microsoft.Singularity.Memory.SharedHeap.Allocation;
    using System.Threading;
    using SharedHeap = Microsoft.Singularity.Memory.SharedHeap;

    [CLSCompliant(false)]
    public enum EndpointCoreEvent : ushort
    {
        Connect = 4,
        TransferToProcess = 5,
    }


    [CLSCompliant(false)]
    [CCtorIsRunDuringStartup]
    unsafe public struct EndpointCore
    {
        ////////////////////////////////////////////////////////////////////
        // Fields
        ////////////////////////////////////////////////////////////////////
        //
        // NOTE: The fields specified here must match those in:
        //         Kernel/Singularity/Channels/EndpointCore.cs
        //         Kernel/Singularity/V1/Services/ChannelServices.cs
        //         Libraries/Singuarity.V1/Services/ChannelServices.cs

        /// <summary>
        /// Handle to the actual message delivery mechanism
        /// </summary>
        private DeliveryHandle deliveryHandle;

        /// <summary>
        /// Event handle in case this endpoint is part of a collection
        /// </summary>
        private AutoResetEventHandle collectionEvent;

        //
        // These "cached" fields are directly accessable by user programs,
        // but are not trusted by the kernel (as they could be modified by untrusted
        // code).  The kernel relies on the trusted shadow copies held in the
        // deliveryImpl object, but updates these fields to reflect any changes to user
        // apps.
        //

        /// <summary>
        /// Event on which sends are signaled to this endpoint.
        /// The handle is owned by the kernel, since the endpoint can move.
        /// The kernel deallocates the handle when the channel is deallocated.
        /// NOTE: stays valid until the entire channel gets collected.
        /// </summary>
        private AutoResetEventHandle cachedMessageEvent;

        /// <summary>
        /// Closed flag
        /// </summary>
        private bool cachedClosed;

        /// <summary>
        /// Contains the process id of the process currently owning this end of the
        /// channel.
        /// </summary>
        private int cachedOwnerProcessId;

        /// <summary>
        /// Contains the channelId (positive on the EXP endpoint, negative on the imp endpoint)
        /// </summary>
        private int cachedChannelId;

        /// <summary>
        /// Whether to marshall or not
        /// </summary>
        private bool cachedMarshall;

        /// <summary>
        /// Points to the peer endpoint
        /// </summary>
        private Allocation* /*EndpointCore* opt(ExHeap)*/ cachedPeer;

        /// <summary>
        /// If true then the peer state can be queried directly from cachedPeer
        /// </summary>
        private bool peerStateValid;


        ////////////////////////////////////////////////////////////////////
        // Static Fields
        ////////////////////////////////////////////////////////////////////

        /// <summary>
        /// Number of open channels (using any delivery mechanism)
        /// </summary>
        private static int openChannelCount;     // TODO open channel count!!!

        /// <summary>
        /// Channel id generator used to create unique channel id's accross delivery
        /// mechanisms.
        /// </summary>
        private static int channelIdGenerator;


        ////////////////////////////////////////////////////////////////////
        // Types
        ////////////////////////////////////////////////////////////////////
        public enum DelegationState {None, ByCapability, ByMediation, Mediated};



        ////////////////////////////////////////////////////////////////////
        // Methods
        ////////////////////////////////////////////////////////////////////

        /// <summary>
        /// Retrieve underlying delivery mechanism for a given endpoint allocation pointer
        /// </summary>
        [NoHeapAllocation]
        internal static DeliveryImpl AllocationEndpointDeliveryImpl(
            Allocation* /*EndpointCore* opt(ExHeap)!*/ endpoint)
        {
            EndpointCore * ep = (EndpointCore *) Allocation.GetData(endpoint);
            if (ep == null) {
                return null;
            } else {
                return ep->EndpointDeliveryImpl;
            }
        }

        /// <summary>
        /// Retrieve underlying delivery mechanism for a given endpoint allocation pointer
        /// using GetDataUnchecked.
        /// </summary>
        [NoHeapAllocation]
        internal static DeliveryImpl AllocationEndpointDeliveryImplUnchecked(
            Allocation* /*EndpointCore* opt(ExHeap)!*/ endpoint)
        {
            EndpointCore * ep = (EndpointCore *) Allocation.GetDataUnchecked(endpoint);
            if (ep == null) {
                return null;
            } else {
                return ep->EndpointDeliveryImpl;
            }
        }

        /// <summary>
        /// Retrieve underlying delivery mechanism for this endpoint
        /// </summary>
        internal DeliveryImpl EndpointDeliveryImpl {
            [NoHeapAllocation]
            get {
                if (deliveryHandle != DeliveryHandle.Zero) {
                    return DeliveryHandle.GetImpl(deliveryHandle);
                } else {
                    unsafe {
                        DebugStub.WriteLine("deliveryHandle value is {0,8:x}\n", __arglist((uint) deliveryHandle.id));
                    }
                    DebugStub.Break();
                    return null;
                }
            }
        }

        /// <summary>
        /// Performs the initialization of the core part of each endpoint and cross links
        /// them to form a channel.  Uses the standard shared address space delivery
        /// mechanism.
        /// </summary>
        public static void Connect(
            Allocation* /*EndpointCore* opt(ExHeap)!*/ imp,
            Allocation* /*EndpointCore* opt(ExHeap)!*/ exp,
            Allocation* /*EndpointCore* opt(ExHeap)!*/ securityEp)
        {
            Connect(imp, exp, securityEp, SingleAddrSpaceDelivery.ImplName);
        }

        /// <summary>
        /// Performs the initialization of the core part of each endpoint and cross links
        /// them to form a channel.  Uses the given delivery mechanism.
        /// </summary>
        public static void Connect(
            Allocation* /*EndpointCore* opt(ExHeap)!*/ imp,
            Allocation* /*EndpointCore* opt(ExHeap)!*/ exp,
            Allocation* /*EndpointCore* opt(ExHeap)!*/ securityEp,
            string deliveryImplType)
        {
            if (imp == null || exp == null) {
                throw new ApplicationException("Connect called with null endpoints");
            }
            EndpointCore* impData = (EndpointCore*)Allocation.GetData(imp);
            EndpointCore* expData = (EndpointCore*)Allocation.GetData(exp);
            if (impData == null || expData == null) {
                throw new ApplicationException("SharedHeap.GetData return null");
            }

            Tracing.Log(Tracing.Debug, "connect {0:x8} and {1:x8}",
                        (UIntPtr)imp, (UIntPtr)exp);

            if (!(DeliveryHandle.Create(deliveryImplType, imp, out impData->deliveryHandle) &&
                  DeliveryHandle.Create(deliveryImplType, exp, out expData->deliveryHandle))) {
                throw new EndpointCoreException(
                    "Error trying to create EndpointCore using \"" +
                    deliveryImplType +
                    "\" delivery implementation");
            }
#if false
            DebugStub.Print("imp handle {0,8:x} exp handle {1,8:x}\n",
                            __arglist((uint)impData->deliveryHandle.id, (uint)expData->deliveryHandle.id));
#endif
            DeliveryImpl impDi = impData->EndpointDeliveryImpl;
            DeliveryImpl expDi = expData->EndpointDeliveryImpl;

            VTable.Assert(impDi != null && expDi != null);

            impDi.Connect(expDi, securityEp);

            // keep track of how many channels are open
            Interlocked.Increment(ref openChannelCount);
#if CHANNEL_COUNT
            PerfCounters.IncrementChannelsCreated();
#endif
            Monitoring.Log(Monitoring.Provider.EndpointCore,
                         (ushort)EndpointCoreEvent.Connect, 0,
                         (uint)expData->ChannelId, 0, 0, 0, 0);
        }

        /// <summary>
        /// Set this end to closed
        /// </summary>
        public void Close()
        {
            DeliveryImpl di = EndpointDeliveryImpl;
            if (di != null) {
                di.Close();
            }
        }

        [NoHeapAllocation]
        public bool Closed()
        {
            DeliveryImpl di = EndpointDeliveryImpl;
            if (di != null) {
                return di.Closed;
            } else {
                // endpoint has not yet been connected
                return true;
            }
        }

        /// <summary>
        /// Closes this end of the channel and frees associated resources, EXCEPT the block
        /// of memory for this endpoint. It must be released by the caller. Sing# does this
        /// for the programmer.
        ///
        /// This runs in the kernel to avoid a race condition with Process.Stop.
        /// </summary>
        public bool Dispose()
        {
            DeliveryImpl di = EndpointDeliveryImpl;
            if (di != null) {
                return EndpointDeliveryImpl.Dispose();
            } else {
                return true;   // endpoint was not yet connected
            }
        }

        /// <summary>
        /// Explicitly frees this end of the channel.
        ///
        /// Since both threads on the channel could try to do this simultaneously,
        /// we use the ref counting on the underlying endpoints to let the last
        /// free operation (the one pulling the ref count to 0) to free the associated
        /// event.
        /// </summary>
        public unsafe static void Free(Allocation* /*EndpointCore* opt(ExHeap)!*/ endpoint)
        {


            // Use unchecked GetData here, since this may be called from the
            // cleanup threading running in the kernel.
            EndpointCore * ep = ((EndpointCore*)Allocation.GetDataUnchecked(endpoint));
            if (ep->deliveryHandle != DeliveryHandle.Zero) {
                if (DeliveryHandle.Free(ep->deliveryHandle)) {
                    Interlocked.Decrement(ref openChannelCount);
                }
            } else {
                // was never connected, just free this endpoint
                AutoResetEventHandle areHandle = ep->cachedMessageEvent;

                SharedHeap.KernelSharedHeap.Free(endpoint,
                              SharedHeap.CurrentProcessSharedHeap.EndpointPeerOwnerId);
                if (areHandle.id != UIntPtr.Zero) {
                    Process.kernelProcess.ReleaseHandle(areHandle.id);
                }
            }
        }

        /// <summary>
        /// The event to wait for messages on this endpoint. Used by Select.
        /// </summary>
        public SyncHandle GetWaitHandle() {
            DeliveryImpl di = EndpointDeliveryImpl;
            VTable.Assert(di != null);
            return di.MessageEvent;
        }

        /// <summary>
        /// Get the AutoResetEventHandle
        /// </summary>
        public AutoResetEventHandle GetAreHandle() {
            DeliveryImpl di = EndpointDeliveryImpl;
            VTable.Assert(di != null);
            return di.AreHandle;
        }

        /// <summary>
        /// Notify the peer of this endpoint that a message is ready.
        /// Notifies the set owner if this endpoint is part of a set.
        /// </summary>
        public void NotifyPeer() {
            DeliveryImpl di = EndpointDeliveryImpl;
            VTable.Assert(di != null);
            di.NotifyPeer();
        }

        /// <summary>
        /// Used internally by the kernel to transfer an endpoint to a new owner
        ///
        /// Can be used to transfer ANY kind of shared heap data, not just endpoints.
        /// </summary>
        public static Allocation* MoveEndpoint(SharedHeap fromHeap,
                                               SharedHeap toHeap,
                                               Process newOwner,
                                               Allocation *ep)
        {
            return DeliveryImpl.MoveData(fromHeap, toHeap, newOwner, ep);
        }


        public static void AcceptDelegation(Allocation* /*EndpointCore* opt(ExHeap)!*/ imp,
                                            Allocation* /*EndpointCore* opt(ExHeap)!*/ exp,
                                            Allocation* /*EndpointCore* opt(ExHeap)!*/ ep)
        {
            DeliveryImpl impDi = AllocationEndpointDeliveryImpl(imp);
            DeliveryImpl expDi = AllocationEndpointDeliveryImpl(exp);
            DeliveryImpl epDi  = AllocationEndpointDeliveryImpl(ep);
            VTable.Assert((impDi != null && expDi != null && epDi != null));
            VTable.Assert((impDi.GetType() == expDi.GetType()) &&
                          (impDi.GetType() == epDi.GetType()));
            impDi.AcceptDelegation(expDi, epDi);
        }

        public void EnableDelegation(bool allowMediation)
        {
            EndpointDeliveryImpl.EnableDelegation(allowMediation);
        }

        public DelegationState OwnerDelegationState
        {
            [NoHeapAllocation]
            get { return EndpointDeliveryImpl.OwnerDelegationState; }
        }

        public PrincipalHandle OwnerPrincipalHandle
        {
            [NoHeapAllocation]
            get { return EndpointDeliveryImpl.OwnerPrincipalHandle; }
        }

        public PrincipalHandle PeerPrincipalHandle
        {
            [NoHeapAllocation]
            get { return EndpointDeliveryImpl.PeerPrincipalHandle; }
        }

        public int PeerReceiveCount
        {
            [NoHeapAllocation]
            get {
                DeliveryImpl di = EndpointDeliveryImpl;
                if (di != null) {
                    return di.PeerReceiveCount;
                } else {
                    // not yet connected, therefore no peer yet
                    return 0;
                }
            }
        }

        //versions of begin update and marshallpointer that handle objects with pointers
        //greater than one level deep

        internal void BeginUpdate(byte* basep, byte* source, int* tagAddress, int msgSize)
        {
            DeliveryImpl di = EndpointDeliveryImpl;
            VTable.Assert(di != null);
            //            DebugStub.Print("BeginUpdate delivery handle {0,8:x}\n", __arglist((uint)deliveryHandle.id));
            di.BeginUpdate(basep, source, tagAddress, msgSize);
        }

        public void MarshallPointer(byte* basep, byte** target, SystemType type, byte* parent, int offset)
        {
            if (target == null) return;
            DeliveryImpl di = EndpointDeliveryImpl;
            //            DebugStub.Print("Marshall Pointer delivery handle {0,8:x}\n", __arglist((uint)deliveryHandle.id));
            VTable.Assert(di != null);
            di.MarshallPointer(basep, target, type, parent, offset);
        }

        public static PrincipalHandle TransferPrincipal(PrincipalHandle oldH,
                                                        int processId,
                                                        ref DelegationState delegationState)
        {
            Process p = Process.GetProcessByID(processId);
            if (p == null)
                throw new ApplicationException("Delegate endpoint process is null");

            DelegationState newstate;
            Principal pr;
            switch (delegationState) {
                case DelegationState.ByMediation:
                    pr = PrincipalImpl.NewDelegation(
                        PrincipalImpl.MakePrincipal(oldH.val), p.Principal);
                    newstate = DelegationState.Mediated;
                    break;
                case DelegationState.ByCapability:
                    pr = PrincipalImpl.NewDelegation(
                        PrincipalImpl.MakePrincipal(oldH.val), p.Principal);
                    newstate = DelegationState.None;
                    break;
                case DelegationState.Mediated:
                    pr = p.Principal;
                    newstate = DelegationState.None;
                    break;
                case DelegationState.None:
                default:
                    pr = p.Principal;
                    newstate = DelegationState.None;
                    break;
            }

            delegationState = newstate;
            return new PrincipalHandle(pr.Val);
        }

#if SINGULARITY_KERNEL && CHANNEL_COUNT

        internal static void IncreaseBytesSentCount(long bytes)
        {
              PerfCounters.AddBytesSent(bytes);

        }

#endif // SINGULARITY_KERNEL

        /// <summary>
        /// Transfer the given Allocation block to the target endpoint
        /// </summary>
        public static void TransferBlockOwnership(Allocation* ptr, ref EndpointCore target)
        {
            Allocation.SetOwnerProcessId(ptr, target.cachedOwnerProcessId);
            // TODO MAKE THIS APROPRIATE TO BOTH SINGLE AND PAGED IMPLS
            DeliveryImpl di = target.EndpointDeliveryImpl;
            VTable.Assert(di != null);
            //Monitoring.Log(Monitoring.Provider.ChannelService,
            //               (ushort)ChannelServiceEvent.TransferBlockOwnership, 0,
            //               (uint)di.ChannelId,
            //               (uint)di.ProcessId,
            //               0, 0, 0);
#if CHANNEL_COUNT
            IncreaseBytesSentCount((long) Allocation.GetSize(ptr));
#endif
            Allocation.SetOwnerProcessId(ptr, di.ProcessId);
        }

        /// <summary>
        /// Transfer any contents that needs to be adjusted from the transferee to the target
        /// endpoint.
        /// </summary>
        // TODO: change "ref EndpointCore" to "EndpointCore"
        public static void TransferContentOwnership(
           ref EndpointCore transferee,
           ref EndpointCore target)
        {
            // TODO MAKE THIS APROPRIATE TO BOTH SINGLE AND PAGED IMPLS
            DeliveryImpl transfereeDi = transferee.EndpointDeliveryImpl;

            // XXX BUG? BUG? BUG?
            //   targetDi = transferee.EndpointDeliveryImpl
            // should be:
            //   targetDi = target.EndpointDeliveryImpl
            DeliveryImpl targetDi = transferee.EndpointDeliveryImpl;
            VTable.Assert((transfereeDi != null) && (targetDi != null));
            //Monitoring.Log(Monitoring.Provider.ChannelService,
            //               (ushort)ChannelServiceEvent.TransferContentOwnership, 0,
            //               (uint)transfereeDi.ProcessId,
            //               (uint)targetDi.ProcessId,
            //               (uint)transfereeDi.ChannelId,
            //               (uint)targetDi.ChannelId,
            //               (uint)targetDi.Peer.ChannelId);
            int toProcessId = targetDi.ProcessId;
            transfereeDi.ProcessId = toProcessId;
            DelegationState newstate = transfereeDi.OwnerDelegationState;
            transfereeDi.OwnerPrincipalHandle =
                TransferPrincipal(transfereeDi.OwnerPrincipalHandle, toProcessId, ref newstate);
            transfereeDi.OwnerDelegationState = newstate;
            Allocation* transfereePeerAllocation = transfereeDi.Peer();
            // also transfer the peer allocation
            Allocation.SetOwnerProcessId(transfereePeerAllocation, toProcessId);
        }

        public static int OpenChannelCount {
            [NoHeapAllocation]
            get { return openChannelCount; }
        }

        public static void IncOpenChannelCount () {
            Interlocked.Increment(ref openChannelCount);
        }

        public static void DecOpenChannelCount () {
            Interlocked.Decrement(ref openChannelCount);
        }

        //
        //  These getter defer to the delivery implementation, as the kernel only
        //  trusts the shadow versions held in Kernel memory space.
        //
        public bool PeerClosed()
        {
            DeliveryImpl di = EndpointDeliveryImpl;
            if (di != null) {
                return di.PeerClosed();
            } else {
                // endpoint has not yet been connected
                return true;
            }
        }

        /// <summary>
        /// Return a handle for the peer
        /// </summary>
        public Allocation* Peer(out bool marshall)
        {
            DeliveryImpl di = EndpointDeliveryImpl;
            VTable.Assert(di != null);
            return di.Peer(out marshall);
        }

        public int ChannelId
        {
            [NoHeapAllocation]
            get {
                DeliveryImpl di = EndpointDeliveryImpl;
                VTable.Assert(di != null);
                return di.ChannelId;
            }
        }

        public int ProcessId
        {
            [NoHeapAllocation]
            get {
                DeliveryImpl di = EndpointDeliveryImpl;
                VTable.Assert(di != null);
                return di.ProcessId;
            }
        }

        public int ReceiveCount
        {
            get {
                DeliveryImpl di = EndpointDeliveryImpl;
                VTable.Assert(di != null);
                return di.ReceiveCount;
            }
        }

        public int PeerProcessId
        {
            [NoHeapAllocation]
            get {
                DeliveryImpl di = EndpointDeliveryImpl;
                VTable.Assert(di != null);
                return di.PeerProcessId;
            }
        }

        public DeliveryHandle dImpHandle
        {
            get {
                return deliveryHandle;
            }
            set {
                deliveryHandle = value;
            }
        }

        public AutoResetEventHandle CollectionEvent
        {
            get {
                return collectionEvent;
            }
            set {
                collectionEvent = value;
            }
        }

        internal static int GetNextChannelId()
        {
            return Interlocked.Increment(ref channelIdGenerator);
        }

        //
        // These methods only set the user accessable cached copies, real changes should
        // be made to the delivery implementation copies.
        //
        [NoHeapAllocation]
        public void SetCachedClose(bool closed) { cachedClosed = closed; }
        [NoHeapAllocation]
        public void SetCachedProcessId(int pid) { cachedOwnerProcessId = pid; }
        [NoHeapAllocation]
        public void SetCachedChannelId(int cid) { cachedChannelId = cid; }
        [NoHeapAllocation]
        public void SetCachedMarshall(bool marshall) { cachedMarshall = marshall; }
        [NoHeapAllocation]
        public void SetCachedMessageEvent(AutoResetEventHandle mh)
        {
            cachedMessageEvent = mh;
        }
        [NoHeapAllocation]
        public void SetCachedPeer(Allocation * /* EndpointCore */ peer)
        {
            cachedPeer = peer;
        }
        [NoHeapAllocation]
        public void SetPeerStateValid(bool valid)
        {
            peerStateValid = valid;
        }

    }

    public class EndpointCoreException: Exception
    {
        public EndpointCoreException (string message) : base(message)
        {
        }
    }
}

