//  Copyright 2019 Google Inc. All Rights Reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.

using System;
using System.Runtime.InteropServices;

namespace NtApiDotNet
{
#pragma warning disable 1591
    /// <summary>
    /// Access rights for ALPC
    /// </summary>
    [Flags]
    public enum AlpcAccessRights : uint
    {
        Connect = 0x1,
        GenericRead = GenericAccessRights.GenericRead,
        GenericWrite = GenericAccessRights.GenericWrite,
        GenericExecute = GenericAccessRights.GenericExecute,
        GenericAll = GenericAccessRights.GenericAll,
        Delete = GenericAccessRights.Delete,
        ReadControl = GenericAccessRights.ReadControl,
        WriteDac = GenericAccessRights.WriteDac,
        WriteOwner = GenericAccessRights.WriteOwner,
        Synchronize = GenericAccessRights.Synchronize,
        MaximumAllowed = GenericAccessRights.MaximumAllowed,
        AccessSystemSecurity = GenericAccessRights.AccessSystemSecurity
    }

    /// <summary>
    /// ALPC Port Information Class
    /// </summary>
    public enum AlpcPortInformationClass
    {
        AlpcBasicInformation,
        AlpcPortInformation,
        AlpcAssociateCompletionPortInformation,
        AlpcConnectedSIDInformation,
        AlpcServerInformation,
        AlpcMessageZoneInformation,
        AlpcRegisterCompletionListInformation,
        AlpcUnregisterCompletionListInformation,
        AlpcAdjustCompletionListConcurrencyCountInformation,
        AlpcRegisterCallbackInformation,
        AlpcCompletionListRundownInformation,
        AlpcWaitForPortReferences
    }

    public enum AlpcMessageInformationClass
    {
        AlpcMessageSidInformation = 0,
        AlpcMessageTokenModifiedIdInformation,
        AlpcMessageDirectStatusInformation,
        AlpcMessageHandleInformation,
    }

    public enum AlpcMessageType
    {
        None = 0,
        Request = 1,
        Reply = 2,
        Datagram = 3,
        LostReply = 4,
        PortClosed = 5,
        ClientDied = 6,
        Exception = 7,
        DebugEvent = 8,
        ErrorEvent = 9,
        ConnectionRequest = 10,
        // Used by the kernel when disconnecting an exception port.
        PortDisconnected = 13,
    }

    [Flags]
    public enum AlpcMessageTypeFlags
    {
        None = 0,
        Unknown1000 = 0x1000,
        ContinuationRequired = 0x2000,
        Unknown4000 = 0x4000,
        Unknown8000 = 0x8000,
    }

    [StructLayout(LayoutKind.Sequential)]
    public class AlpcPortMessage
    {
        [StructLayout(LayoutKind.Explicit)]
        public struct PortMessageUnion1
        {
            [FieldOffset(0)]
            public ushort DataLength;
            [FieldOffset(2)]
            public ushort TotalLength;
            [FieldOffset(0)]
            public int Length;
        }
        public PortMessageUnion1 u1;

        [StructLayout(LayoutKind.Explicit)]
        public struct PortMessageUnion2
        {
            [FieldOffset(0)]
            public ushort Type;
            [FieldOffset(2)]
            public ushort DataInfoOffset;
            [FieldOffset(0)]
            public int ZeroInit;
        }
        public PortMessageUnion2 u2;

        public ClientIdStruct ClientId;
        public int MessageId;

        [StructLayout(LayoutKind.Explicit)]
        public struct PortMessageUnion3
        {
            [FieldOffset(0)]
            public IntPtr ClientViewSize;
            [FieldOffset(0)]
            public int CallbackId;
        }
        public PortMessageUnion3 u3;

        internal AlpcPortMessage Clone()
        {
            return (AlpcPortMessage)MemberwiseClone();
        }
    }

    [Flags]
    public enum AlpcPortAttributeFlags
    {
        None = 0,
        LpcPort = 0x1000, // Not accessible outside the kernel.
        AllowImpersonation = 0x10000,
        AllowLpcRequests = 0x20000,
        WaitablePort = 0x40000,
        AllowDupObject = 0x80000,
        SystemProcess = 0x100000, // Not accessible outside the kernel.
        LrpcWakePolicy1 = 0x200000,
        LrpcWakePolicy2 = 0x400000,
        LrpcWakePolicy3 = 0x800000,
        DirectMessage = 0x1000000,
        /// <summary>
        /// If set then object duplication won't complete. Used by RPC to ensure
        /// multi-handle attributes don't fail when receiving.
        /// </summary>
        AllowMultiHandleAttribute = 0x2000000,
    }

    [StructLayout(LayoutKind.Sequential)]
    public class AlpcPortAttributes
    {
        public AlpcPortAttributeFlags Flags;
        public SecurityQualityOfServiceStruct SecurityQos;
        public IntPtr MaxMessageLength;
        public IntPtr MemoryBandwidth;
        public IntPtr MaxPoolUsage;
        public IntPtr MaxSectionSize;
        public IntPtr MaxViewSize;
        public IntPtr MaxTotalSectionSize;
        public AlpcHandleObjectType DupObjectTypes;
        public int Reserved;

        public static AlpcPortAttributes CreateDefault()
        {
            return new AlpcPortAttributes()
            {
                Flags = AlpcPortAttributeFlags.None,
                SecurityQos = new SecurityQualityOfServiceStruct(SecurityImpersonationLevel.Impersonation,
                                                            SecurityContextTrackingMode.Static, false),
                MaxMessageLength = new IntPtr(short.MaxValue),
                DupObjectTypes = AlpcHandleObjectType.AllObjects
            };
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct AlpcHandle
    {
        private IntPtr _value;

        public long Value
        {
            get => _value.ToInt64();
            set => _value = new IntPtr(value);
        }

        public AlpcHandle(long value)
        {
            _value = new IntPtr(value);
        }
        
        public static implicit operator AlpcHandle(long value)
        {
            return new AlpcHandle(value);
        }
    }

    [Flags]
    public enum AlpcMessageAttributeFlags : uint
    {
        None = 0,
        WorkOnBehalfOf = 0x2000000,
        Direct = 0x4000000,
        Token = 0x8000000,
        Handle = 0x10000000,
        Context = 0x20000000,
        View = 0x40000000,
        Security = 0x80000000,
        AllAttributes = WorkOnBehalfOf | Direct | Token | Handle | Context | View | Security
    }

    [Flags]
    public enum AlpcSecurityAttrFlags
    {
        None = 0,
        ReleaseHandle = 0x10000,
        CreateHandle = 0x20000
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct AlpcSecurityAttr
    {
        public AlpcSecurityAttrFlags Flags;
        public IntPtr QoS; // struct _SECURITY_QUALITY_OF_SERVICE
        public AlpcHandle ContextHandle;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct AlpcContextAttr
    {
        public IntPtr PortContext;
        public IntPtr MessageContext;
        public int Sequence;
        public int MessageId;
        public int CallbackId;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct AlpcDirectAttr
    {
        public IntPtr Event;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct AlpcWorkOnBehalfAttr
    {
        public int ThreadId;
        public int ThreadCreationTimeLow;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct AlpcTokenAttr
    {
        public Luid TokenId;
        public Luid AuthenticationId;
        public Luid ModifiedId;
    }

    [Flags]
    public enum AlpcHandleAttrFlags
    {
        None = 0,
        SameAccess = 0x10000,
        SameAttributes = 0x20000,
        Indirect = 0x40000,
        Inherit = 0x80000
    }

    [Flags]
    public enum AlpcHandleObjectType
    {
        None = 0,
        File = 0x0001,
        Invalid0002 = 0x0002,
        Thread = 0x0004,
        Semaphore = 0x0008,
        Event = 0x0010,
        Process = 0x0020,
        Mutex = 0x0040,
        Section = 0x0080,
        RegKey = 0x0100,
        Token = 0x0200,
        Composition = 0x0400,
        Job = 0x0800,
        AllObjects = File | Thread | Semaphore | Event
            | Process | Mutex | Section | RegKey | Token
            | Composition | Job
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct AlpcHandleAttr
    {
        public AlpcHandleAttrFlags Flags;
        public IntPtr Handle;
        public AlpcHandleObjectType ObjectType;
        public AccessMask DesiredAccess;
    }

    public struct AlpcHandleAttrIndirect
    {
        public AlpcHandleAttrFlags Flags;
        public IntPtr HandleAttrArray;
        public int HandleCount;
        public AccessMask GrantedAccess;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct AlpcHandleAttr32
    {
        public AlpcHandleAttrFlags Flags;
        public int Handle;
        public AlpcHandleObjectType ObjectType;
        public AccessMask DesiredAccess;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct AlpcMessageHandleInformation
    {
        public int Index;
        public AlpcHandleAttrFlags Flags;
        public int Handle;
        public AlpcHandleObjectType ObjectType;
        public AccessMask GrantedAccess;
    }

    [Flags]
    public enum AlpcDataViewAttrFlags
    {
        None = 0,
        /// <summary>
        /// Use in a reply to release the view.
        /// </summary>
        ReleaseView = 0x10000,
        /// <summary>
        /// Automatically release the view once it's passed to the receiver. 
        /// </summary>
        AutoRelease = 0x20000,
        /// <summary>
        /// Make the data view secure.
        /// </summary>
        Secure = 0x40000
    }

    [Flags]
    public enum AlpcCreatePortSectionFlags
    {
        None = 0,
        Secure = 0x40000
    }

    [Flags]
    public enum AlpcDeletePortSectionFlags
    {
        None = 0
    }

    [Flags]
    public enum AlpcCreateSectionViewFlags
    {
        None = 0,
    }

    [Flags]
    public enum AlpcCreateResourceReserveFlags
    {
        None = 0,
    }

    [Flags]
    public enum AlpcDeleteResourceReserveFlags
    {
        None = 0,
    }

    [Flags]
    public enum AlpcDeleteSectionViewFlags
    {
        None = 0,
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct AlpcDataViewAttr
    {
        public AlpcDataViewAttrFlags Flags;
        public AlpcHandle SectionHandle;
        public IntPtr ViewBase;
        public IntPtr ViewSize;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct AlpcMessageAttributes
    {
        public AlpcMessageAttributeFlags AllocatedAttributes;
        public AlpcMessageAttributeFlags ValidAttributes;
    }

    [Flags]
    public enum AlpcDisconnectPortFlags
    {
        None = 0,
        NoFlushOnClose = 1,
    }

    [Flags]
    public enum AlpcMessageFlags : uint
    {
        None = 0,
        ReplyMessage = 0x1,
        LpcMode = 0x2,
        ReleaseMessage = 0x10000,
        SyncRequest = 0x20000,
        TrackPortReferences = 0x40000,
        WaitUserMode = 0x100000,
        WaitAlertable = 0x200000,
        WaitChargePolicy = 0x400000,
        Unknown1000000 = 0x1000000,
        /// <summary>
        /// When used all structures passed to kernel need to be 64 bit versions.
        /// </summary>
        Wow64Call = 0x40000000,
    }

    [Flags]
    public enum AlpcCancelMessageFlags
    {
        None = 0,
        TryCancel = 1,
        Unknown2 = 2,
        Unknown4 = 4,
        NoContextCheck = 8
    }

    [Flags]
    public enum AlpcImpersonationClientOfPortFlags
    {
        None = 0,
        AnonymousFallback = 1,
        RequireImpersonationLevel = 2,
        // From bit 2 on it's the impersonation level required.
    }

    [Flags]
    public enum AlpcImpersonateClientContainerOfPortFlags
    {
        None = 0,
    }

    [Flags]
    public enum AlpcCreateSecurityContextFlags
    {
        None = 0,
    }

    [Flags]
    public enum AlpcDeleteSecurityContextFlags
    {
        None = 0,
    }

    [Flags]
    public enum AlpcRevokeSecurityContextFlags
    {
        None = 0,
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct AlpcBasicInformation
    {
        public AlpcPortAttributeFlags Flags;
        public int SequenceNo;
        public IntPtr PortContext;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct AlpcPortAssociateCompletionPort
    {
        public IntPtr CompletionKey;
        public IntPtr CompletionPort;
    }

    // Output structure for server information. You need to specif
    public struct AlpcServerInformationOut
    {
        public byte ThreadBlocked;
        public IntPtr ConnectedProcessId;
        public UnicodeStringOut ConnectionPortName;
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct AlpcServerInformation
    {
        [FieldOffset(0)]
        public IntPtr ThreadHandle;
        [FieldOffset(0)]
        public AlpcServerInformationOut Out;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct AlpcPortMessageZoneInformation
    {
        public IntPtr Buffer;
        public int Size;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct AlpcPortCompletionListInformation
    {
        public IntPtr Buffer; // PALPC_COMPLETION_LIST_HEADER
        public int Size;
        public int ConcurrencyCount;
        public int AttributeFlags;
    }

    [Flags]
    public enum AlpcOpenSenderProcessFlags
    {
        None = 0,
    }

    [Flags]
    public enum AlpcOpenSenderThreadFlags
    {
        None = 0,
    }

    public static class NtAlpcNativeMethods
    {
        [DllImport("ntdll.dll")]
        public static extern int AlpcMaxAllowedMessageLength();

        [DllImport("ntdll.dll")]
        public static extern int AlpcGetHeaderSize(AlpcMessageAttributeFlags Flags);

        [DllImport("ntdll.dll")]
        public static extern NtStatus AlpcInitializeMessageAttribute(
            AlpcMessageAttributeFlags AttributeFlags,
            SafeAlpcMessageAttributesBuffer Buffer,
            int BufferSize,
            out int RequiredBufferSize
        );

        [DllImport("ntdll.dll")]
        public static extern IntPtr AlpcGetMessageAttribute(
            SafeAlpcMessageAttributesBuffer Buffer,
            AlpcMessageAttributeFlags AttributeFlag
        );
    }

    public static partial class NtSystemCalls
    {
        [DllImport("ntdll.dll")]
        public static extern NtStatus NtAlpcCreatePort(
            out SafeKernelObjectHandle PortHandle,
            [In] ObjectAttributes ObjectAttributes,
            [In] AlpcPortAttributes PortAttributes
        );

        [DllImport("ntdll.dll")]
        public static extern NtStatus NtAlpcDisconnectPort(
            [In] SafeKernelObjectHandle PortHandle,
            AlpcDisconnectPortFlags Flags
        );

        [DllImport("ntdll.dll")]
        public static extern NtStatus NtAlpcQueryInformation(
            SafeKernelObjectHandle PortHandle,
            AlpcPortInformationClass PortInformationClass,
            SafeBuffer PortInformation,
            int Length,
            out int ReturnLength
        );

        [DllImport("ntdll.dll")]
        public static extern NtStatus NtAlpcSetInformation(
            [In] SafeKernelObjectHandle PortHandle,
            AlpcPortInformationClass PortInformationClass,
            SafeBuffer PortInformation,
            int Length);

        [DllImport("ntdll.dll")]
        public static extern NtStatus NtAlpcConnectPort(
            out SafeKernelObjectHandle PortHandle,
            [In] UnicodeString PortName,
            [In] ObjectAttributes ObjectAttributes,
            [In] AlpcPortAttributes PortAttributes,
            AlpcMessageFlags Flags,
            [In] SafeSidBufferHandle RequiredServerSid,
            [In, Out] SafeAlpcPortMessageBuffer ConnectionMessage,
            [In, Out] OptionalLength BufferLength,
            [In, Out] SafeAlpcMessageAttributesBuffer OutMessageAttributes,
            [In, Out] SafeAlpcMessageAttributesBuffer InMessageAttributes,
            [In] LargeInteger Timeout
        );

        [DllImport("ntdll.dll")]
        [SupportedVersion(SupportedVersion.Windows8)]
        public static extern NtStatus NtAlpcConnectPortEx(
            out SafeKernelObjectHandle PortHandle,
            [In] ObjectAttributes ConnectionPortObjectAttributes,
            [In] ObjectAttributes ClientPortObjectAttributes,
            [In] AlpcPortAttributes PortAttributes,
            AlpcMessageFlags Flags,
            [In] SafeBuffer ServerSecurityRequirements, // SECURITY_DESCRIPTOR
            [In, Out] SafeAlpcPortMessageBuffer ConnectionMessage,
            [In, Out] OptionalLength BufferLength,
            [In, Out] SafeAlpcMessageAttributesBuffer OutMessageAttributes,
            [In, Out] SafeAlpcMessageAttributesBuffer InMessageAttributes,
            [In] LargeInteger Timeout);

        [DllImport("ntdll.dll")]
        public static extern NtStatus NtAlpcSendWaitReceivePort(
            [In] SafeKernelObjectHandle PortHandle,
            AlpcMessageFlags Flags,
            [In] SafeAlpcPortMessageBuffer SendMessage,
            [In, Out] SafeAlpcMessageAttributesBuffer SendMessageAttributes,
            [Out] SafeAlpcPortMessageBuffer ReceiveMessage,
            [In, Out] OptionalLength BufferLength,
            [In, Out] SafeAlpcMessageAttributesBuffer ReceiveMessageAttributes,
            [In] LargeInteger Timeout);

        [DllImport("ntdll.dll")]
        public static extern NtStatus NtAlpcImpersonateClientOfPort(
                [In] SafeKernelObjectHandle PortHandle,
                [In] AlpcPortMessage PortMessage,
                AlpcImpersonationClientOfPortFlags Flags
        );

        [DllImport("ntdll.dll")]
        [SupportedVersion(SupportedVersion.Windows10_TH2)]
        public static extern NtStatus NtAlpcImpersonateClientContainerOfPort(
            [In] SafeKernelObjectHandle PortHandle,
            [In] AlpcPortMessage PortMessage,
            AlpcImpersonateClientContainerOfPortFlags Flags
        );

        [DllImport("ntdll.dll")]
        public static extern NtStatus NtAlpcCreateSecurityContext(
            SafeKernelObjectHandle PortHandle,
            AlpcCreateSecurityContextFlags Flags,
            ref AlpcSecurityAttr SecurityAttribute);

        [DllImport("ntdll.dll")]
        public static extern NtStatus NtAlpcDeleteSecurityContext(
            SafeKernelObjectHandle PortHandle,
            AlpcDeleteSecurityContextFlags Flags,
            AlpcHandle ContextHandle
        );

        [DllImport("ntdll.dll")]
        public static extern NtStatus NtAlpcRevokeSecurityContext(
            SafeKernelObjectHandle PortHandle,
            AlpcRevokeSecurityContextFlags Flags,
            AlpcHandle ContextHandle
        );

        [DllImport("ntdll.dll")]
        public static extern NtStatus NtAlpcQueryInformationMessage(
            SafeKernelObjectHandle PortHandle,
            AlpcPortMessage PortMessage,
            AlpcMessageInformationClass MessageInformationClass,
            SafeBuffer MessageInformation,
            int Length,
            out int ReturnLength
        );

        // Version to support AlpcMessageDirectStatusInformation which needs ReturnLength == NULL.
        [DllImport("ntdll.dll")]
        public static extern NtStatus NtAlpcQueryInformationMessage(
            SafeKernelObjectHandle PortHandle,
            AlpcPortMessage PortMessage,
            AlpcMessageInformationClass MessageInformationClass,
            IntPtr MessageInformation,
            int Length,
            IntPtr ReturnLength
        );

        [DllImport("ntdll.dll")]
        public static extern NtStatus NtAlpcCreatePortSection(
            SafeKernelObjectHandle PortHandle,
            AlpcCreatePortSectionFlags Flags,
            SafeKernelObjectHandle SectionHandle,
            IntPtr SectionSize,
            out AlpcHandle AlpcSectionHandle,
            out IntPtr ActualSectionSize
        );

        [DllImport("ntdll.dll")]
        public static extern NtStatus NtAlpcDeletePortSection(
            SafeKernelObjectHandle PortHandle,
            AlpcDeletePortSectionFlags Flags,
            AlpcHandle SectionHandle
        );

        [DllImport("ntdll.dll")]
        public static extern NtStatus NtAlpcCreateResourceReserve(
            SafeKernelObjectHandle PortHandle,
            AlpcCreateResourceReserveFlags Flags,
            IntPtr MessageSize,
            out AlpcHandle ResourceId
        );

        [DllImport("ntdll.dll")]
        public static extern NtStatus NtAlpcDeleteResourceReserve(
            SafeKernelObjectHandle PortHandle,
            AlpcDeleteResourceReserveFlags Flags,
            AlpcHandle ResourceId
        );

        [DllImport("ntdll.dll")]
        public static extern NtStatus NtAlpcCreateSectionView(
            SafeKernelObjectHandle PortHandle,
            AlpcCreateSectionViewFlags Flags,
            ref AlpcDataViewAttr ViewAttributes
        );

        [DllImport("ntdll.dll")]
        public static extern NtStatus NtAlpcDeleteSectionView(
            SafeKernelObjectHandle PortHandle,
            AlpcDeleteSectionViewFlags Flags,
            IntPtr ViewBase
        );

        [DllImport("ntdll.dll")]
        public static extern NtStatus NtAlpcAcceptConnectPort(
            out SafeKernelObjectHandle PortHandle,
            SafeKernelObjectHandle ConnectionPortHandle,
            AlpcMessageFlags Flags,
            ObjectAttributes ObjectAttributes,
            AlpcPortAttributes PortAttributes,
            IntPtr PortContext,
            SafeAlpcPortMessageBuffer ConnectionRequest,
            SafeAlpcMessageAttributesBuffer ConnectionMessageAttributes,
            bool AcceptConnection
        );

        [DllImport("ntdll.dll")]
        public static extern NtStatus NtAlpcOpenSenderProcess(
            out SafeKernelObjectHandle ProcessHandle,
            SafeKernelObjectHandle PortHandle,
            AlpcPortMessage PortMessage,
            AlpcOpenSenderProcessFlags Flags,
            ProcessAccessRights DesiredAccess,
            ObjectAttributes ObjectAttributes
        );

        [DllImport("ntdll.dll")]
        public static extern NtStatus NtAlpcOpenSenderThread(
            out SafeKernelObjectHandle ThreadHandle,
            SafeKernelObjectHandle PortHandle,
            AlpcPortMessage PortMessage,
            AlpcOpenSenderThreadFlags Flags,
            ThreadAccessRights DesiredAccess,
            ObjectAttributes ObjectAttributes
        );

        [DllImport("ntdll.dll")]
        public static extern NtStatus NtAlpcCancelMessage(
            SafeKernelObjectHandle PortHandle,
            AlpcCancelMessageFlags Flags,
            ref AlpcContextAttr MessageContext
        );
    }
#pragma warning restore 1591
}
