/***********************************************************************
*
* Copyright (C) 2005-2006 Novell, Inc. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, Novell, Inc.
*
* To contact Novell about this file by physical or electronic mail,
* you may find current contact information at www.novell.com.
*
***********************************************************************/
using System;
using System.Runtime.InteropServices;
using AppModule.InterProcessComm;
namespace AppModule.NamedPipes {
#region Comments
///
/// A utility class that exposes named pipes operations.
///
///
/// This class uses the exposed exposed kernel32.dll methods by the
/// NamedPipeNative class
/// to provided controlled named pipe functionality.
///
#endregion
public sealed class NamedPipeWrapper {
#region Comments
///
/// The number of retries when creating a pipe or connecting to a pipe.
///
#endregion
private const int ATTEMPTS = 2;
#region Comments
///
/// Wait time for the
/// NamedPipeNative.WaitNamedPipe
/// operation.
///
#endregion
private const int WAIT_TIME = 5000;
#region Comments
///
/// Reads a string from a named pipe using the UTF8 encoding.
///
/// The pipe handle.
/// The maximum bytes to read.
/// A UTF8 string.
/// This function uses
/// AppModule.NamedPipes.ReadBytes
/// to read the bytes from the pipe and then converts them to string.
/// The first four bytes of the pipe data are expected to contain
/// the data length of the message. This method first reads those four
/// bytes and converts them to integer. It then continues to read from the pipe using
/// the extracted data length.
///
#endregion
public static string Read(PipeHandle handle, int maxBytes) {
string returnVal = "";
byte[] bytes = ReadBytes(handle, maxBytes);
if (bytes != null) {
returnVal = System.Text.Encoding.UTF8.GetString(bytes);
}
return returnVal;
}
#region Comments
///
/// Reads the bytes from a named pipe.
///
/// The pipe handle.
/// The maximum bytes to read.
/// An array of bytes.
/// This method expects that the first four bytes in the pipe define
/// the length of the data to read. If the data length is greater than
/// maxBytes the method returns null.
/// The first four bytes of the pipe data are expected to contain
/// the data length of the message. This method first reads those four
/// bytes and converts them to integer. It then continues to read from the pipe using
/// the extracted data length.
///
#endregion
public static byte[] ReadBytes(PipeHandle handle, int maxBytes) {
byte[] numReadWritten = new byte[4];
byte[] intBytes = new byte[4];
byte[] msgBytes = null;
int len;
// Set the Handle state to Reading
handle.State = InterProcessConnectionState.Reading;
// Read the first four bytes and convert them to integer
if (NamedPipeNative.ReadFile(handle.Handle, intBytes, 4, numReadWritten, 0)) {
len = BitConverter.ToInt32(intBytes, 0);
msgBytes = new byte[len];
// Read the rest of the data
if (!NamedPipeNative.ReadFile(handle.Handle, msgBytes, (uint)len, numReadWritten, 0)) {
handle.State = InterProcessConnectionState.Error;
throw new NamedPipeIOException("Error reading from pipe. Internal error: " + NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError());
}
}
else {
handle.State = InterProcessConnectionState.Error;
throw new NamedPipeIOException("Error reading from pipe. Internal error: " + NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError());
}
handle.State = InterProcessConnectionState.ReadData;
if (len > maxBytes) {
return null;
}
return msgBytes;
}
#region Comments
///
/// Writes a string to a named pipe.
///
/// The pipe handle.
/// The text to write to the pipe.
/// This method converts the text into an array of bytes, using the
/// UTF8 encoding and the uses
/// AppModule.NamedPipes.WriteBytes
/// to write to the pipe.
/// When writing to a pipe the method first writes four bytes that define the data length.
/// It then writes the whole message.
#endregion
public static void Write(PipeHandle handle, string text) {
WriteBytes(handle, System.Text.Encoding.UTF8.GetBytes(text));
}
#region Comments
///
/// Writes an array of bytes to a named pipe.
///
/// The pipe handle.
/// The bytes to write.
/// If we try bytes array we attempt to write is empty then this method write a space character to the pipe. This is necessary because the other end of the pipe uses a blocking Read operation so we must write someting.
/// The bytes length is restricted by the maxBytes parameter, which is done primarily for security reasons.
/// When writing to a pipe the method first writes four bytes that define the data length.
/// It then writes the whole message.
#endregion
public static void WriteBytes(PipeHandle handle, byte[] bytes) {
byte[] numReadWritten = new byte[4];
uint len;
if (bytes == null) {
bytes = new byte[0];
}
if (bytes.Length == 0) {
bytes = new byte[1];
bytes = System.Text.Encoding.UTF8.GetBytes(" ");
}
// Get the message length
len = (uint)bytes.Length;
handle.State = InterProcessConnectionState.Writing;
// Write four bytes that define the message length
if (NamedPipeNative.WriteFile(handle.Handle, BitConverter.GetBytes(len), 4, numReadWritten, 0)) {
// Write the whole message
if (!NamedPipeNative.WriteFile(handle.Handle, bytes, len, numReadWritten, 0)) {
handle.State = InterProcessConnectionState.Error;
throw new NamedPipeIOException("Error writing to pipe. Internal error: " + NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError());
}
}
else {
handle.State = InterProcessConnectionState.Error;
throw new NamedPipeIOException("Error writing to pipe. Internal error: " + NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError());
}
handle.State = InterProcessConnectionState.Flushing;
Flush(handle);
handle.State = InterProcessConnectionState.FlushedData;
}
#region Comments
///
/// Tries to connect to a named pipe on the same machine.
///
/// The name of the pipe.
/// The resulting pipe handle.
/// Return true if the attempt succeeds.
/// This method is used mainly when stopping the pipe server. It unblocks the existing pipes, which wait for client connection.
#endregion
public static bool TryConnectToPipe(string pipeName, out PipeHandle handle) {
return TryConnectToPipe(pipeName, ".", out handle);
}
#region Comments
///
/// Tries to connect to a named pipe.
///
/// The name of the pipe.
/// The name of the server.
/// The resulting pipe handle.
/// Return true if the attempt succeeds.
/// This method is used mainly when stopping the pipe server. It unblocks the existing pipes, which wait for client connection.
#endregion
public static bool TryConnectToPipe(string pipeName, string serverName, out PipeHandle handle) {
handle = new PipeHandle();
// Build the pipe name string
string name = @"\\" + serverName + @"\pipe\" + pipeName;
handle.State = InterProcessConnectionState.ConnectingToServer;
// Try to connect to a server pipe
handle.Handle = NamedPipeNative.CreateFile(name, NamedPipeNative.GENERIC_READ | NamedPipeNative.GENERIC_WRITE, 0, null, NamedPipeNative.OPEN_EXISTING, 0, 0);
if (handle.Handle.ToInt32() != NamedPipeNative.INVALID_HANDLE_VALUE) {
handle.State = InterProcessConnectionState.ConnectedToServer;
return true;
} else {
handle.State = InterProcessConnectionState.Error;
return false;
}
}
#region Comments
///
/// Connects to a server named pipe on the same machine.
///
/// The pipe name.
/// The pipe handle, which also contains the pipe state.
/// This method is used by clients to establish a pipe connection with a server pipe.
#endregion
public static PipeHandle ConnectToPipe(string pipeName) {
return ConnectToPipe(pipeName, ".");
}
#region Comments
///
/// Connects to a server named pipe.
///
/// The pipe name.
/// The server name.
/// The pipe handle, which also contains the pipe state.
/// This method is used by clients to establish a pipe connection with a server pipe.
#endregion
public static PipeHandle ConnectToPipe(string pipeName, string serverName) {
PipeHandle handle = new PipeHandle();
// Build the name of the pipe.
string name = @"\\" + serverName + @"\pipe\" + pipeName;
for (int i = 1; i<=ATTEMPTS; i++) {
handle.State = InterProcessConnectionState.ConnectingToServer;
// Try to connect to the server
handle.Handle = NamedPipeNative.CreateFile(name, NamedPipeNative.GENERIC_READ | NamedPipeNative.GENERIC_WRITE, 0, null, NamedPipeNative.OPEN_EXISTING, 0, 0);
if (handle.Handle.ToInt32() != NamedPipeNative.INVALID_HANDLE_VALUE) {
// The client managed to connect to the server pipe
handle.State = InterProcessConnectionState.ConnectedToServer;
// Set the read mode of the pipe channel
uint mode = NamedPipeNative.PIPE_READMODE_MESSAGE;
if (NamedPipeNative.SetNamedPipeHandleState(handle.Handle, ref mode, IntPtr.Zero, IntPtr.Zero)) {
break;
}
if (i >= ATTEMPTS) {
handle.State = InterProcessConnectionState.Error;
throw new NamedPipeIOException("Error setting read mode on pipe " + name + " . Internal error: " + NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError());
}
}
if (i >= ATTEMPTS) {
if (NamedPipeNative.GetLastError() != NamedPipeNative.ERROR_PIPE_BUSY) {
handle.State = InterProcessConnectionState.Error;
// After a certain number of unsuccessful attempt raise an exception
throw new NamedPipeIOException("Error connecting to pipe " + name + " . Internal error: " + NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError());
} else {
handle.State = InterProcessConnectionState.Error;
throw new NamedPipeIOException("Pipe " + name + " is too busy. Internal error: " + NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError());
}
} else {
// The pipe is busy so lets wait for some time and try again
if (NamedPipeNative.GetLastError() == NamedPipeNative.ERROR_PIPE_BUSY)
NamedPipeNative.WaitNamedPipe(name, WAIT_TIME);
}
}
return handle;
}
#region Comments
///
/// Creates a server named pipe.
///
/// The name of the pipe.
/// The size of the outbound buffer.
/// The size of the inbound buffer.
/// The pipe handle.
#endregion
public static PipeHandle Create(string name, uint outBuffer, uint inBuffer) {
return Create(name, outBuffer, inBuffer, true);
}
#region Comments
///
/// Creates a server named pipe.
///
/// The name of the pipe.
/// The size of the outbound buffer.
/// The size of the inbound buffer.
/// Specifies whether to make the pipe secure.
/// The pipe handle.
/// You can specify a security descriptor for a named pipe when you call the CreateNamedPipe function. The security descriptor controls access to both client and server ends of the named pipe. If NULL is specified, the named pipe gets a default security descriptor. The ACLs in the default security descriptor for a named pipe grant full control to the LocalSystem account, administrators, and the creator owner. They also grant read access to members of the Everyone group and the anonymous account.
///
/// If the secure parameter is false the method creates a security descriptor that grants full access to Everyone.
///
#endregion
public static PipeHandle Create(string name, uint outBuffer, uint inBuffer, bool secure) {
if ((name.IndexOf("pipe") < 0) && (name.IndexOf("PIPE") < 0))
name = @"\\.\pipe\" + name;
PipeHandle handle = new PipeHandle();
IntPtr secAttr = IntPtr.Zero;
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
if (!secure) {
SECURITY_DESCRIPTOR sd;
GetNullDaclSecurityDescriptor(out sd);
sa.lpSecurityDescriptor = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SECURITY_DESCRIPTOR)));
Marshal.StructureToPtr(sd, sa.lpSecurityDescriptor, false);
sa.bInheritHandle = false;
sa.nLength = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES));
secAttr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES)));
Marshal.StructureToPtr(sa, secAttr, false);
}
try {
for (int i = 1; i<=ATTEMPTS; i++) {
handle.State = InterProcessConnectionState.Creating;
handle.Handle = NamedPipeNative.CreateNamedPipe(
name,
NamedPipeNative.PIPE_ACCESS_DUPLEX,
NamedPipeNative.PIPE_TYPE_MESSAGE | NamedPipeNative.PIPE_READMODE_MESSAGE | NamedPipeNative.PIPE_WAIT,
NamedPipeNative.PIPE_UNLIMITED_INSTANCES,
outBuffer,
inBuffer,
NamedPipeNative.NMPWAIT_WAIT_FOREVER,
secAttr);
if (handle.Handle.ToInt32() != NamedPipeNative.INVALID_HANDLE_VALUE) {
handle.State = InterProcessConnectionState.Created;
break;
}
if (i >= ATTEMPTS) {
handle.State = InterProcessConnectionState.Error;
throw new NamedPipeIOException("Error creating named pipe " + name + " . Internal error: " + NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError());
}
}
}
finally {
if (!secure) {
Marshal.FreeHGlobal(sa.lpSecurityDescriptor);
Marshal.FreeHGlobal(secAttr);
}
}
return handle;
}
#region Comments
///
/// Creates a SECURITY_DESCRIPTOR with DACL = null, which allows full access to Everyone.
///
/// The SECURITY_DESCRIPTOR structure.
#endregion
public static void GetNullDaclSecurityDescriptor(out SECURITY_DESCRIPTOR sd) {
if(NamedPipeNative.InitializeSecurityDescriptor(out sd, 1)) {
if(!NamedPipeNative.SetSecurityDescriptorDacl(ref sd, true, IntPtr.Zero, false)) {
throw new NamedPipeIOException("Error setting SECURITY_DESCRIPTOR attributes. Internal error: " + NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError());
}
}
else {
throw new NamedPipeIOException("Error setting SECURITY_DESCRIPTOR attributes. Internal error: " + NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError());
}
}
#region Comments
///
/// Starts waiting for client connections.
///
///
/// Blocks the current execution until a client pipe attempts to establish a connection.
///
/// The pipe handle.
#endregion
public static void Connect(PipeHandle handle) {
handle.State = InterProcessConnectionState.WaitingForClient;
bool connected = NamedPipeNative.ConnectNamedPipe(handle.Handle, null);
handle.State = InterProcessConnectionState.ConnectedToClient;
if (!connected && NamedPipeNative.GetLastError() != NamedPipeNative.ERROR_PIPE_CONNECTED) {
handle.State = InterProcessConnectionState.Error;
throw new NamedPipeIOException("Error connecting pipe. Internal error: " + NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError());
}
}
#region Comments
///
/// Returns the number of instances of a named pipe.
///
/// The pipe handle.
/// The number of instances.
#endregion
public static uint NumberPipeInstances(PipeHandle handle) {
uint curInstances = 0;
if (NamedPipeNative.GetNamedPipeHandleState(handle.Handle, IntPtr.Zero, ref curInstances, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero)) {
return curInstances;
}
else {
throw new NamedPipeIOException("Error getting the pipe state. Internal error: " + NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError());
}
}
#region Comments
///
/// Closes a named pipe and releases the native handle.
///
/// The pipe handle.
#endregion
public static void Close(PipeHandle handle) {
handle.State = InterProcessConnectionState.Closing;
NamedPipeNative.CloseHandle(handle.Handle);
handle.Handle = IntPtr.Zero;
handle.State = InterProcessConnectionState.Closed;
}
#region Comments
///
/// Flushes all the data in a named pipe.
///
/// The pipe handle.
#endregion
public static void Flush(PipeHandle handle) {
handle.State = InterProcessConnectionState.Flushing;
NamedPipeNative.FlushFileBuffers(handle.Handle);
handle.State = InterProcessConnectionState.FlushedData;
}
#region Comments
///
/// Disconnects a server named pipe from the client.
///
///
/// Server pipes can be reused by first disconnecting them from the client and then
/// calling the Connect
/// method to start listening. This improves the performance as it is not necessary
/// to create new pipe handles.
///
/// The pipe handle.
#endregion
public static void Disconnect(PipeHandle handle) {
handle.State = InterProcessConnectionState.Disconnecting;
NamedPipeNative.DisconnectNamedPipe(handle.Handle);
handle.State = InterProcessConnectionState.Disconnected;
}
#region Comments
///
/// Private constructor.
///
#endregion
private NamedPipeWrapper() {}
}
}