File: channels\ipc\ipcport.cs
Project: ndp\clr\src\managedlibraries\remoting\System.Runtime.Remoting.csproj (System.Runtime.Remoting)
// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
//===========================================================================
//  File:       IpcPort.cs
//  Author:   Microsoft@Microsoft.Com
//  Summary:    Implements an abstraction over Named pipes
//
//==========================================================================
 
 
using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Threading;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Runtime.InteropServices;
using System.Globalization;
 
namespace System.Runtime.Remoting.Channels.Ipc
{
    internal class IpcPort : IDisposable
    {
        private PipeHandle _handle;
        private string _portName;
        private bool _cacheable;
        private const string prefix = @"\\.\pipe\";  // To make sure we are on the same machine
        private const string networkSidSddlForm = @"S-1-5-2";  // This is the wellknown sid for network sid
        private const string authenticatedUserSidSddlForm = @"S-1-5-11";  // This is the wellknown sid for authenticated user sid
        private static CommonSecurityDescriptor s_securityDescriptor = CreateSecurityDescriptor(null);
        
        private IpcPort(string portName, PipeHandle handle)
        {
            _portName = portName;
            _handle = handle;
            _cacheable = true;
#pragma warning disable 618
            // Bind the current handle to the threadpool for IOCompletion
            ThreadPool.BindHandle(_handle.Handle);
#pragma warning restore 618
        }
 
        internal string Name { get { return _portName; } }
 
        internal bool Cacheable{ get { return _cacheable;} set { _cacheable = value;} }
 
        internal static CommonSecurityDescriptor CreateSecurityDescriptor(SecurityIdentifier userSid)
        {
            SecurityIdentifier sid = new SecurityIdentifier(networkSidSddlForm);
            DiscretionaryAcl dacl = new DiscretionaryAcl(false, false, 1);
            // Deny all access to NetworkSid
            dacl.AddAccess(AccessControlType.Deny, sid, -1, InheritanceFlags.None, PropagationFlags.None);
            if (userSid != null)
                dacl.AddAccess(AccessControlType.Allow, userSid, -1, InheritanceFlags.None, PropagationFlags.None);
            // Add access to the current user creating the pipe
            dacl.AddAccess(AccessControlType.Allow, WindowsIdentity.GetCurrent().User, -1, InheritanceFlags.None, PropagationFlags.None);
            // Initialize and return the CommonSecurityDescriptor
            return new CommonSecurityDescriptor(false, false, ControlFlags.OwnerDefaulted | ControlFlags.GroupDefaulted | ControlFlags.DiscretionaryAclPresent, null, null, null, dacl);;
        }
            
        internal static IpcPort Create(String portName, CommonSecurityDescriptor securityDescriptor, bool exclusive)
        {
            if (Environment.OSVersion.Platform != PlatformID.Win32NT) {
                throw new NotSupportedException(CoreChannel.GetResourceString("Remoting_Ipc_Win9x"));
            }
            PipeHandle handle = null;
            // Add the prefix to the portName
            string pipeName = prefix + portName;
            SECURITY_ATTRIBUTES attr = new SECURITY_ATTRIBUTES();
            attr.nLength = (int)Marshal.SizeOf(attr);
            byte[] sd = null;
            // If no securityDescriptor was set by the user use the default
            if (securityDescriptor == null)
            {
                securityDescriptor = s_securityDescriptor;
            }
 
            sd = new byte[securityDescriptor.BinaryLength];
            // Get the binary form of the descriptor
            securityDescriptor.GetBinaryForm(sd, 0);
               
            GCHandle pinningHandle = GCHandle.Alloc(sd, GCHandleType.Pinned);
            // get the address of the security descriptor
            attr.lpSecurityDescriptor = Marshal.UnsafeAddrOfPinnedArrayElement(sd, 0);
 
            // Create the named pipe with the appropriate name
            handle = NativePipe.CreateNamedPipe(pipeName, 
                                      NativePipe.PIPE_ACCESS_DUPLEX  | NativePipe.FILE_FLAG_OVERLAPPED 
                                            | (exclusive ? NativePipe.FILE_FLAG_FIRST_PIPE_INSTANCE : 0x0), // Or exclusive flag 
                                      NativePipe.PIPE_TYPE_BYTE | NativePipe.PIPE_READMODE_BYTE | NativePipe.PIPE_WAIT,
                                      NativePipe.PIPE_UNLIMITED_INSTANCES,
                                      8192,
                                      8192,
                                      NativePipe.NMPWAIT_WAIT_FOREVER,
                                      attr); 
 
            pinningHandle.Free();
            if (handle.Handle.ToInt32() == NativePipe.INVALID_HANDLE_VALUE){
                int error = Marshal.GetLastWin32Error();
                throw new RemotingException(String.Format(CultureInfo.CurrentCulture, CoreChannel.GetResourceString("Remoting_Ipc_CreateIpcFailed"), GetMessage(error)));
            }
 
             return new IpcPort(portName, handle);   
        }
 
        public bool WaitForConnect()
        {
            // Wait for clients to connect
            bool status = NativePipe.ConnectNamedPipe(_handle, null);
            
            return status ? true : (Marshal.GetLastWin32Error() == NativePipe.ERROR_PIPE_CONNECTED);
        }
 
        internal static IpcPort Connect(String portName, bool secure, TokenImpersonationLevel impersonationLevel, int timeout)
        {
            string pipeName = prefix + portName;
            uint impersonation = NativePipe.SECURITY_SQOS_PRESENT;
 
            // convert the impersonation Level to the correct flag
            if (secure) {
                switch (impersonationLevel)
                {
                    case TokenImpersonationLevel.None:
                            impersonation = NativePipe.SECURITY_SQOS_PRESENT;
                            break;
                    case TokenImpersonationLevel.Identification:
                            impersonation = NativePipe.SECURITY_SQOS_PRESENT | NativePipe.SECURITY_IDENTIFICATION;
                            break;
                    case TokenImpersonationLevel.Impersonation:
                            impersonation = NativePipe.SECURITY_SQOS_PRESENT | NativePipe.SECURITY_IMPERSONATION;
                            break;
                    case TokenImpersonationLevel.Delegation:
                            impersonation = NativePipe.SECURITY_SQOS_PRESENT | NativePipe.SECURITY_DELEGATION;
                            break;
                }
            }
 
            while (true)
            {
                // Invoke CreateFile with the pipeName to open a client side connection                
                PipeHandle handle = NativePipe.CreateFile(pipeName, 
                                     NativePipe.GENERIC_READ | NativePipe.GENERIC_WRITE , 
                                     NativePipe.FILE_SHARE_READ | 
                                     NativePipe.FILE_SHARE_WRITE,
                                     IntPtr.Zero, 
                                     NativePipe.OPEN_EXISTING, 
                                     NativePipe.FILE_ATTRIBUTE_NORMAL | 
                                     NativePipe.FILE_FLAG_OVERLAPPED |
                                     impersonation, 
                                     IntPtr.Zero);
 
                if(handle.Handle.ToInt32() != NativePipe.INVALID_HANDLE_VALUE)
                    return new IpcPort(portName, handle);
 
                int error = Marshal.GetLastWin32Error();
                if(error != NativePipe.ERROR_PIPE_BUSY)
                {
                    throw new RemotingException(String.Format(CultureInfo.CurrentCulture, CoreChannel.GetResourceString("Remoting_Ipc_ConnectIpcFailed"), GetMessage(error)));
                }
 
                if(!NativePipe.WaitNamedPipe(pipeName, timeout))
                {
                    throw new RemotingException(String.Format(CultureInfo.CurrentCulture, CoreChannel.GetResourceString("Remoting_Ipc_Busy"), GetMessage(error)));
                }
            }            
 
        }
 
        // Gets an error message for a Win32 error code.
        internal static String GetMessage(int errorCode) {
            StringBuilder sb = new StringBuilder(512);
            int result = NativePipe.FormatMessage(NativePipe.FORMAT_MESSAGE_IGNORE_INSERTS |
                NativePipe.FORMAT_MESSAGE_FROM_SYSTEM | NativePipe.FORMAT_MESSAGE_ARGUMENT_ARRAY,
                NativePipe.NULL, errorCode, 0, sb, sb.Capacity, NativePipe.NULL);
            if (result != 0) {
                // result is the # of characters copied to the StringBuilder on NT,
                // but on Win9x, it appears to be the number of MBCS bytes.
                // Just give up and return the String as-is...
                String s = sb.ToString();
                return s;
            }
            else {
                return String.Format(CultureInfo.CurrentCulture, CoreChannel.GetResourceString("Remoting_UnknownError_Num"), errorCode.ToString(CultureInfo.InvariantCulture));
            }
        }
 
        internal void ImpersonateClient()
        {
            bool status = NativePipe.ImpersonateNamedPipeClient(_handle);
 
            if (!status)
            {
                int error = Marshal.GetLastWin32Error();
                throw new RemotingException(String.Format(CultureInfo.CurrentCulture, CoreChannel.GetResourceString("Remoting_Ipc_ImpersonationFailed"), GetMessage(error)));
            }
        }
 
        internal unsafe int Read(byte[] data, int offset, int length)
        {
            bool status = false;
            int numBytesRead = 0;
 
            fixed(byte* p = data) {
                    status = NativePipe.ReadFile(_handle, p + offset, length, ref numBytesRead, IntPtr.Zero);
            }
            if (!status) {
                int error = Marshal.GetLastWin32Error();
                throw new RemotingException(String.Format(CultureInfo.CurrentCulture, CoreChannel.GetResourceString("Remoting_Ipc_ReadFailure"), GetMessage(error)));
            }
            else
                return numBytesRead;
        }
 
        internal unsafe IAsyncResult BeginRead(byte[] data, int offset, int size, AsyncCallback callback, object state)
        {
            PipeAsyncResult asyncResult = new PipeAsyncResult(callback);
            // Create a managed overlapped class
            // We will set the file offsets later
            Overlapped overlapped = new Overlapped(0, 0, IntPtr.Zero, asyncResult);
 
            // Pack the Overlapped class, and store it in the async result
            NativeOverlapped* intOverlapped;
            intOverlapped = overlapped.UnsafePack(IOCallback, data);
            asyncResult._overlapped = intOverlapped;
            bool status;
 
            // pin the buffer and read data with overlapped
            fixed(byte* p = data) {
                    status = NativePipe.ReadFile(_handle, p + offset, size, IntPtr.Zero, intOverlapped);
            }
            if (!status)
            {
                int error = Marshal.GetLastWin32Error();
                // For pipes, when they hit EOF, they will come here.
                if (error == NativePipe.ERROR_BROKEN_PIPE) {
                    // Not an error, but EOF.  AsyncFSCallback will NOT be 
                    // called.  Call the user callback here.
                    asyncResult.CallUserCallback();                 
                    // EndRead will free the Overlapped struct correctly.
                }
                else if (error != NativePipe.ERROR_IO_PENDING)
                    throw new RemotingException(String.Format(CultureInfo.CurrentCulture, CoreChannel.GetResourceString("Remoting_Ipc_ReadFailure"), GetMessage(error)));
            }
            return asyncResult;
        }
 
        private unsafe static readonly IOCompletionCallback IOCallback = new IOCompletionCallback(IpcPort.AsyncFSCallback);
        unsafe private static void AsyncFSCallback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped)
        {
            // Unpack overlapped
            Overlapped overlapped = Overlapped.Unpack(pOverlapped);
            // Free the overlapped struct in EndRead/EndWrite.
            
            // Extract async result from overlapped 
            PipeAsyncResult asyncResult = 
                (PipeAsyncResult)overlapped.AsyncResult;
            asyncResult._numBytes = (int)numBytes;
            
            // Handle reading from & writing to closed pipes.  While I'm not sure
            // this is entirely necessary anymore, maybe it's possible for 
            // an async read on a pipe to be issued and then the pipe is closed, 
            // returning this error.  This may very well be necessary.
            if (errorCode == NativePipe.ERROR_BROKEN_PIPE)
                errorCode = 0;
            
            asyncResult._errorCode = (int)errorCode;
            // Call the user-provided callback.  It can and often should
            // call EndRead or EndWrite.  There's no reason to use an async 
            // delegate here - we're already on a threadpool thread.  
            // IAsyncResult's completedSynchronously property must return
            // false here, saying the user callback was called on another thread.
            AsyncCallback userCallback = asyncResult._userCallback;
            userCallback(asyncResult);
        }
 
        internal unsafe int EndRead(IAsyncResult iar)
        {
            PipeAsyncResult ar = iar as PipeAsyncResult;
            // Free memory & GC handles.
            NativeOverlapped* overlappedPtr = ar._overlapped;
            if (overlappedPtr != null)
                Overlapped.Free(overlappedPtr);
 
            // Now check for any error during the read.
            if (ar._errorCode != 0)
                throw new RemotingException(String.Format(CultureInfo.CurrentCulture, CoreChannel.GetResourceString("Remoting_Ipc_ReadFailure"), GetMessage(ar._errorCode)));
 
            return ar._numBytes;
        }
 
        internal unsafe void Write(byte[] data, int offset, int size)
        {
            int numBytesWritten = 0;
            bool status = false;
 
            // pin the buffer and write data
            fixed(byte* p = data) {
                status = NativePipe.WriteFile(_handle, p + offset, size, ref numBytesWritten, IntPtr.Zero);
            }
 
            if (!status) {
                int error = Marshal.GetLastWin32Error();
                throw new RemotingException(String.Format(CultureInfo.CurrentCulture, CoreChannel.GetResourceString("Remoting_Ipc_WriteFailure"), GetMessage(error)));
            }
        }
 
        private bool isDisposed = false;
 
        ~IpcPort(){
            Dispose();
        }
        
        public void Dispose()
        {
            InternalRemotingServices.RemotingAssert(_handle.Handle != IntPtr.Zero, "Handle should be valid");
            if (!isDisposed){
                _handle.Close();
                isDisposed = true;
                GC.SuppressFinalize(this);
            }
        }
 
        public bool IsDisposed {
            get { return isDisposed; }
        }
    }
 
    internal unsafe class PipeAsyncResult: IAsyncResult
    {
        internal NativeOverlapped* _overlapped;
        internal AsyncCallback _userCallback;
        internal int _numBytes;
        internal int _errorCode;
 
        internal PipeAsyncResult(AsyncCallback callback)
        {
            _userCallback = callback;
        }
 
        public bool IsCompleted { get { throw new NotSupportedException();} }
        public WaitHandle AsyncWaitHandle { get { throw new NotSupportedException();} }
        public Object     AsyncState      { get { throw new NotSupportedException();} }
        public bool       CompletedSynchronously { get { return false;} }
 
        
        internal void CallUserCallback()
        {
            // Call user's callback on a threadpool thread.  
            // Set completedSynchronously to false, since it's on another 
            // thread, not the main thread.
            ThreadPool.QueueUserWorkItem(new WaitCallback(CallUserCallbackWorker));
        }
 
        private void CallUserCallbackWorker(Object callbackState)
        {
            // This needs to call the user callback, then set _isComplete to
            // true and set the event if it exists.  This is similar to the
            // logic in AsyncFSCallback.
            _userCallback(this);
        }
    }
    
}