/* Implementation of message port subclass of NSPortNameServer

   Copyright (C) 2005 Free Software Foundation, Inc.

   This file is part of the GNUstep Base Library.

   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; either
   version 3 of the License, or (at your option) any later version.

   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 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, write to the
   Free Software Foundation,
   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA.

   <title>NSMessagePortNameServer class reference</title>
   $Date: 2008-01-10 14:05:38 -0600 (Thu, 10 Jan 2008) $ $Revision: 25931 $
   */

#include "Foundation/NSPortNameServer.h"

#include "Foundation/NSAutoreleasePool.h"
#include "Foundation/NSDebug.h"
#include "Foundation/NSError.h"
#include "Foundation/NSException.h"
#include "Foundation/NSLock.h"
#include "Foundation/NSMapTable.h"
#include "Foundation/NSPathUtilities.h"
#include "Foundation/NSPort.h"
#include "Foundation/NSFileManager.h"
#include "Foundation/NSValue.h"
#include "Foundation/NSThread.h"
#include "Foundation/NSUserDefaults.h"

#include "GNUstepBase/GSMime.h"

#include "../GSPrivate.h"
#include "../GSPortPrivate.h"

#define	UNISTR(X) \
((const unichar*)[(X) cStringUsingEncoding: NSUnicodeStringEncoding])

extern int	errno;

static NSRecursiveLock *serverLock = nil;
static NSMessagePortNameServer *defaultServer = nil;
static NSMapTable portToNamesMap;
static NSString	*registry;
static HKEY	key;

static SECURITY_ATTRIBUTES	security;

@interface NSMessagePortNameServer (private)
+ (NSString *) _query: (NSString *)name;
+ (NSString *) _translate: (NSString *)name;
@end


static void clean_up_names(void)
{
  NSMapEnumerator mEnum;
  NSMessagePort	*port;
  NSString	*name;
  BOOL	unknownThread = GSRegisterCurrentThread();
  CREATE_AUTORELEASE_POOL(arp);

  mEnum = NSEnumerateMapTable(portToNamesMap);
  while (NSNextMapEnumeratorPair(&mEnum, (void *)&port, (void *)&name))
    {
      [defaultServer removePort: port];
    }
  NSEndMapTableEnumeration(&mEnum);
  DESTROY(arp);
  RegCloseKey(key);
  if (unknownThread == YES)
    {
      GSUnregisterCurrentThread();
    }
}

/**
 * Subclass of [NSPortNameServer] taking/returning instances of [NSMessagePort].
 * Port removal functionality is not supported; if you want to cancel a service,
 * you have to destroy the port (invalidate the [NSMessagePort] given to
 * [NSPortNameServer-registerPort:forName:]).
 */
@implementation NSMessagePortNameServer

+ (void) initialize
{
  if (self == [NSMessagePortNameServer class])
    {
      int	rc;

      serverLock = [NSRecursiveLock new];
      portToNamesMap = NSCreateMapTable(NSNonRetainedObjectMapKeyCallBacks,
	NSObjectMapValueCallBacks, 0);
      atexit(clean_up_names);

      security.nLength = sizeof(SECURITY_ATTRIBUTES);
      security.lpSecurityDescriptor = 0;	// Default
      security.bInheritHandle = TRUE;

      registry = @"Software\\GNUstepNSMessagePort";
      rc = RegCreateKeyExW(
	HKEY_CURRENT_USER,
	UNISTR(registry),
	0,
	L"",
	REG_OPTION_VOLATILE,
	STANDARD_RIGHTS_WRITE|STANDARD_RIGHTS_READ|KEY_SET_VALUE
	|KEY_QUERY_VALUE|KEY_NOTIFY,
	&security,
	&key,
	NULL);
      if (rc == ERROR_SUCCESS)
	{
	  rc = RegFlushKey(key);
	  if (rc != ERROR_SUCCESS)
	    {
	      NSLog(@"Failed to flush registry HKEY_CURRENT_USER\\%@ (%x)",
		registry, rc);
	    }
	}
      else
	{
	  NSLog(@"Failed to create registry HKEY_CURRENT_USER\\%@ (%x)",
	    registry, rc);
	}
    }
}

/**
 *  Obtain single instance for this host.
 */
+ (id) sharedInstance
{
  if (defaultServer == nil)
    {
      [serverLock lock];
      if (defaultServer == nil)
	{
	  defaultServer = (NSMessagePortNameServer *)NSAllocateObject(self,
	    0, NSDefaultMallocZone());
	}
      [serverLock unlock];
    }
  return defaultServer;
}


+ (NSString *) _query: (NSString *)name
{
  NSString	*mailslotName;
  NSString	*translatedName;
  NSString	*mailslotPath;
  unsigned char	buf[1024];
  unsigned char	*ptr = buf;
  DWORD		max = 1024;
  DWORD		len = 1024;
  DWORD		type;
  HANDLE	h;
  int		rc;

  translatedName = [[self class] _translate: name];

#if 1
/* FIXME ... wierd hack.
 * It appears that RegQueryValueExW does not always read from the registry,
 * but will in fact return cached results (even if you close and re-open the
 * registry key between the calls to RegQueryValueExW).  This is a problem
 * if we look up a server which is not running, and then try to look it up
 * again when it is running, or if we have one address recorded but the server
 * has been restarted and is using a new address.
 * I couldn't find any mention of this behavior ... but accidentally discovered
 * that a call to OutputDebugStringW stops it ... presumably something in the
 * debug system invalidates whatever registry caching is being done.
 * Anyway, on my XP SP2 system, this next line is needed to fix things.
 *
 * You can test this by running a GNUstep application without starting
 * gdnc beforehand.  If the bug is occurring, the app will try to start gdnc
 * then poll to connect to it, and after 5 seconds will abort because it
 * hasn't seen the gdnc port registered even though gdnc did start.
 * If the hack has fixed the bug, the app will just pause briefly during
 * startup (as it starts gdnc) and then continue when it finds the server
 * port.
 */
OutputDebugStringW(L"");
#endif

  rc = RegQueryValueExW(
    key,
    UNISTR(translatedName),
    (LPDWORD)0,
    &type,
    (LPBYTE)ptr,
    &len);
  while (rc == ERROR_MORE_DATA)
    {
      if (ptr != buf)
        {
	  objc_free(ptr);
	}
      max += 1024;
      ptr = objc_malloc(max);
      len = max;
      rc = RegQueryValueExW(
	key,
	UNISTR(translatedName),
	(LPDWORD)0,
	&type,
	(LPBYTE)ptr,
	&len);
    }
  if (rc != ERROR_SUCCESS)
    {
      if (ptr != buf)
        {
	  objc_free(ptr);
	}
      return nil;
    }

  mailslotName = [NSString stringWithUTF8String: ptr];
  if (ptr != buf)
    {
      objc_free(ptr);
    }

  /*
   * See if we can open the port mailslot ... if not, the query returned
   * an old name, and we can remove it.
   */
  mailslotPath = [NSString stringWithFormat:
    @"\\\\.\\mailslot\\GNUstep\\NSMessagePort\\%@", mailslotName];
  h = CreateFileW(
    UNISTR(mailslotPath),
    GENERIC_WRITE,
    FILE_SHARE_READ|FILE_SHARE_WRITE,
    &security,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL,
    (HANDLE)0);
  if (h == INVALID_HANDLE_VALUE)
    {
      NSDebugLLog(@"NSMessagePortNameServer",
        @"Failed to open mailslot (%@) for write on port named %@ - %@",
	mailslotPath, name, [NSError _last]);
      //RegDeleteValueW(key, UNISTR(n));
      return nil;
    }
  else
    {
      CloseHandle(h);	// OK
      return mailslotName;
    }
}

+ (NSString *) _translate: (NSString *)name
{
  NSData		*data;

  /*
   * Make sure name is representable in the registry ...
   * assume base64 encoded strings are valid.
   */
  data = [name dataUsingEncoding: NSUTF8StringEncoding];
  data = [GSMimeDocument encodeBase64: data];
  name = [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding];
  AUTORELEASE(name);
  return name;
}

- (NSPort*) portForName: (NSString *)name
{
  return [self portForName: name onHost: @""];
}

- (NSPort*) portForName: (NSString *)name
		 onHost: (NSString *)host
{
  NSString	*n;

  NSDebugLLog(@"NSMessagePortNameServer",
    @"portForName: %@ host: %@", name, host);

  if ([host length] != 0)
    {
      [NSException raise: NSInvalidArgumentException
		  format: @"Attempt to contact a named host using a "
	@"message port name server.  This name server can only be used "
	@"to contact processes owned by the same user on the local host "
	@"(host name must be an empty string).  To contact processes "
	@"owned by other users or on other hosts you must use an instance "
	@"of the NSSocketPortNameServer class."];
    }

  n = [[self class] _query: name];
  if (n == nil)
    {
      NSDebugLLog(@"NSMessagePortNameServer", @"got no port for %@", name);
      return nil;
    }
  else
    {
      NSDebugLLog(@"NSMessagePortNameServer", @"got %@ for %@", n, name);
      return AUTORELEASE([NSMessagePort newWithName: n]);
    }
}

- (BOOL) registerPort: (NSPort *)port
	      forName: (NSString *)name
{
  NSMutableArray	*a;
  NSString		*n;
  int			rc;
  const unsigned char	*str;

  NSDebugLLog(@"NSMessagePortNameServer", @"register %@ as %@\n", port, name);
  if ([port isKindOfClass: [NSMessagePort class]] == NO)
    {
      [NSException raise: NSInvalidArgumentException
		  format: @"Attempted to register a non-NSMessagePort (%@)",
	port];
      return NO;
    }

  if ([[self class] _query: name] != nil)
    {
      NSDebugLLog(@"NSMessagePortNameServer", @"fail, is a live port");
      return NO;
    }

  n = [[self class] _translate: name];
  str = [[(NSMessagePort*)port name] UTF8String];

  rc = RegSetValueExW(
    key,
    UNISTR(n),
    0,
    REG_BINARY,
    str,
    strlen(str)+1);
  NSDebugLLog(@"NSMessagePortNameServer", @"Set port '%s' for %@", str, n);
  if (rc == ERROR_SUCCESS)
    {
      rc = RegFlushKey(key);
      if (rc != ERROR_SUCCESS)
	{
	  NSLog(@"Failed to flush registry HKEY_CURRENT_USER\\%@\\%@ (%x)",
	    registry, n, rc);
	}
    }
  else
    {
      NSLog(@"Failed to insert HKEY_CURRENT_USER\\%@\\%@ (%x) %@",
	registry, n, rc, [NSError _last]);
      return NO;
    }

  [serverLock lock];
  a = NSMapGet(portToNamesMap, port);
  if (a != nil)
    {
      a = [[NSMutableArray alloc] init];
      NSMapInsert(portToNamesMap, port, a);
      RELEASE(a);
    }
  [a addObject: [name copy]];
  [serverLock unlock];

  return YES;
}

- (BOOL) removePortForName: (NSString *)name
{
  NSString	*n;
  int		rc;

  NSDebugLLog(@"NSMessagePortNameServer", @"removePortForName: %@", name);
  n = [[self class] _translate: name];
  rc = RegDeleteValueW(key, UNISTR(n));

  return YES;
}

- (NSArray *) namesForPort: (NSPort *)port
{
  NSMutableArray	*a;

  [serverLock lock];
  a = NSMapGet(portToNamesMap, port);
  a = [a copy];
  [serverLock unlock];
  return AUTORELEASE(a);
}

- (BOOL) removePort: (NSPort *)port
{
  NSMutableArray *a;
  int		i;

  NSDebugLLog(@"NSMessagePortNameServer", @"removePort: %@", port);

  [serverLock lock];
  a = NSMapGet(portToNamesMap, port);

  for (i = 0; i < [a count]; i++)
    {
      [self removePort: port  forName: [a objectAtIndex: i]];
    }

  NSMapRemove(portToNamesMap, port);
  [serverLock unlock];

  return YES;
}

- (BOOL) removePort: (NSPort*)port forName: (NSString*)name
{
  NSDebugLLog(@"NSMessagePortNameServer",
    @"removePort: %@  forName: %@", port, name);

  if ([self portForName: name onHost: @""] == port)
    {
      return [self removePortForName: name];
    }
  return NO;
}

@end

