/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%    DDDD    IIIII   SSSSS  TTTTT  RRRR   IIIII  BBBB   U   U  TTTTT  EEEEE   %
%    D   D     I     SS       T    R   R    I    B   B  U   U    T    E       %
%    D   D     I      SSS     T    RRRR     I    BBBB   U   U    T    EEE     %
%    D   D     I        SS    T    R R      I    B   B  U   U    T    E       %
%    DDDDA   IIIII   SSSSS    T    R  R   IIIII  BBBB    UUU     T    EEEEE   %
%                                                                             %
%                      CCCC   AAA    CCCC  H   H  EEEEE                       %
%                     C      A   A  C      H   H  E                           %
%                     C      AAAAA  C      HHHHH  EEE                         %
%                     C      A   A  C      H   H  E                           %
%                      CCCC  A   A   CCCC  H   H  EEEEE                       %
%                                                                             %
%                                                                             %
%                 MagickCore Distributed Pixel Cache Methods                  %
%                                                                             %
%                              Software Design                                %
%                                   Cristy                                    %
%                                January 2013                                 %
%                                                                             %
%                                                                             %
%  Copyright @ 1999 ImageMagick Studio LLC, a non-profit organization         %
%  dedicated to making software imaging solutions freely available.           %
%                                                                             %
%  You may not use this file except in compliance with the License.  You may  %
%  obtain a copy of the License at                                            %
%                                                                             %
%    https://imagemagick.org/license/                                         %
%                                                                             %
%  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.                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% A distributed pixel cache is an extension of the traditional pixel cache
% available on a single host.  The distributed pixel cache may span multiple
% servers so that it can grow in size and transactional capacity to support
% very large images.  Start up the pixel cache server on one or more machines.
% When you read or operate on an image and the local pixel cache resources are
% exhausted, ImageMagick contacts one or more of these remote pixel servers to
% store or retrieve pixels.
%
*/

/*
  Include declarations.
*/
#include "MagickCore/studio.h"
#include "MagickCore/cache.h"
#include "MagickCore/cache-private.h"
#include "MagickCore/distribute-cache.h"
#include "MagickCore/distribute-cache-private.h"
#include "MagickCore/exception.h"
#include "MagickCore/exception-private.h"
#include "MagickCore/geometry.h"
#include "MagickCore/image.h"
#include "MagickCore/image-private.h"
#include "MagickCore/list.h"
#include "MagickCore/locale_.h"
#include "MagickCore/memory_.h"
#include "MagickCore/nt-base-private.h"
#include "MagickCore/pixel.h"
#include "MagickCore/policy.h"
#include "MagickCore/random_.h"
#include "MagickCore/registry.h"
#include "MagickCore/splay-tree.h"
#include "MagickCore/string_.h"
#include "MagickCore/string-private.h"
#include "MagickCore/utility-private.h"
#include "MagickCore/version.h"
#include "MagickCore/version-private.h"
#define SOCKET_TYPE int
#undef MAGICKCORE_HAVE_DISTRIBUTE_CACHE
#if defined(MAGICKCORE_DPC_SUPPORT)
#if defined(MAGICKCORE_HAVE_SOCKET) && defined(MAGICKCORE_THREAD_SUPPORT)
#include <netinet/in.h>
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define CLOSE_SOCKET(socket) (void) close_utf8(socket)
#define HANDLER_RETURN_TYPE void *
#define HANDLER_RETURN_VALUE (void *) NULL
#define SOCKET_TYPE int
#define LENGTH_TYPE size_t
#define MAGICKCORE_HAVE_DISTRIBUTE_CACHE 1
#elif defined(_MSC_VER)
#define CLOSE_SOCKET(socket) (void) closesocket(socket)
#define HANDLER_RETURN_TYPE DWORD WINAPI
#define HANDLER_RETURN_VALUE 0
#define LENGTH_TYPE int
#define MAGICKCORE_HAVE_DISTRIBUTE_CACHE 1
#define MAGICKCORE_HAVE_WINSOCK2 1
#endif
#endif

/*
  Define declarations.
*/
#define DPCHostname  "127.0.0.1"
#define DPCPendingConnections  10
#define DPCPort  6668
#define DPCSessionKeyLength  16
#ifndef MSG_NOSIGNAL
#  define MSG_NOSIGNAL 0
#endif

/*
  Static declarations.
*/
#ifdef MAGICKCORE_HAVE_WINSOCK2
static SemaphoreInfo
  *winsock2_semaphore = (SemaphoreInfo *) NULL;

static WSADATA
  *wsaData = (WSADATA*) NULL;
#endif

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
+   A c q u i r e D i s t r i b u t e C a c h e I n f o                       %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  AcquireDistributeCacheInfo() allocates the DistributeCacheInfo structure.
%
%  The format of the AcquireDistributeCacheInfo method is:
%
%      DistributeCacheInfo *AcquireDistributeCacheInfo(ExceptionInfo *exception)
%
%  A description of each parameter follows:
%
%    o exception: return any errors or warnings in this structure.
%
*/

#if !defined(MAGICKCORE_HAVE_DISTRIBUTE_CACHE)
static inline MagickOffsetType dpc_read(SOCKET_TYPE magick_unused(file),
  const MagickSizeType magick_unused(length),
  unsigned char *magick_restrict magick_unused(message))
{
  magick_unreferenced(file);
  magick_unreferenced(length);
  magick_unreferenced(message);
  return(-1);
}
#else
static inline MagickOffsetType dpc_read(SOCKET_TYPE file,
  const MagickSizeType length,unsigned char *magick_restrict message)
{
  MagickOffsetType
    i;

  ssize_t
    count;

  count=0;
  for (i=0; i < (MagickOffsetType) length; i+=count)
  {
    count=recv(file,(char *) message+i,(LENGTH_TYPE) MagickMin(length-
      (MagickSizeType) i,(MagickSizeType) MagickMaxBufferExtent),0);
    if (count <= 0)
      {
        count=0;
        if (errno != EINTR)
          break;
      }
  }
  return(i);
}
#endif

#if defined(MAGICKCORE_HAVE_WINSOCK2)
static void InitializeWinsock2(MagickBooleanType use_lock)
{
  if (use_lock != MagickFalse)
    {
      if (winsock2_semaphore == (SemaphoreInfo *) NULL)
        ActivateSemaphoreInfo(&winsock2_semaphore);
      LockSemaphoreInfo(winsock2_semaphore);
    }
  if (wsaData == (WSADATA *) NULL)
    {
      wsaData=(WSADATA *) AcquireMagickMemory(sizeof(WSADATA));
      if (WSAStartup(MAKEWORD(2,2),wsaData) != 0)
        ThrowFatalException(CacheFatalError,"WSAStartup failed");
    }
  if (use_lock != MagickFalse)
    UnlockSemaphoreInfo(winsock2_semaphore);
}
#endif

#if !defined(MAGICKCORE_HAVE_DISTRIBUTE_CACHE)
static int ConnectPixelCacheServer(const char *magick_unused(hostname),
  const int magick_unused(port),uint64_t *magick_unused(session_key),
  ExceptionInfo *exception)
{
  magick_unreferenced(hostname);
  magick_unreferenced(port);
  magick_unreferenced(session_key);
  (void) ThrowMagickException(exception,GetMagickModule(),MissingDelegateError,
    "DelegateLibrarySupportNotBuiltIn","distributed pixel cache");
  return(MagickFalse);
}
#else
static inline uint64_t ROTL(uint64_t x,int b)
{
  return((x << b) | (x >> (64-b)));
}

static inline uint64_t U8TO64_LE(const uint8_t *p)
{
  return(((uint64_t) p[0] << 0)  | ((uint64_t) p[1] << 8)  |
         ((uint64_t) p[2] << 16) | ((uint64_t) p[3] << 24) |
         ((uint64_t) p[4] << 32) | ((uint64_t) p[5] << 40) |
         ((uint64_t) p[6] << 48) | ((uint64_t) p[7] << 56));
}

static inline uint64_t SIPHash24(const uint8_t key[16],const uint8_t *message,
  const size_t length)
{
  const uint8_t
    *end = message+length-(length % 8);

  size_t
    i;

  uint64_t
    b = ((uint64_t) length) << 56,
    k0 = U8TO64_LE(key),
    k1 = U8TO64_LE(key+8),
    m,
    v0 = 0x736f6d6570736575ULL^k0,
    v1 = 0x646f72616e646f6dULL^k1,
    v2 = 0x6c7967656e657261ULL^k0,
    v3 = 0x7465646279746573ULL^k1;

  for ( ; message != end; message+=8)
  {
    m=U8TO64_LE(message);
    v3^=m;
    for (i=0; i < 2; i++)
    {
      v0+=v1; v1=ROTL(v1,13); v1^=v0; v0=ROTL(v0,32);
      v2+=v3; v3=ROTL(v3,16); v3^=v2;
      v0+=v3; v3=ROTL(v3,21); v3^=v0;
      v2+=v1; v1=ROTL(v1,17); v1^=v2; v2=ROTL(v2,32);
    }
    v0^=m;
  }
  switch (length & 0x07)
  {
    case 7: b|=((uint64_t) message[6]) << 48; magick_fallthrough;
    case 6: b|=((uint64_t) message[5]) << 40; magick_fallthrough;
    case 5: b|=((uint64_t) message[4]) << 32; magick_fallthrough;
    case 4: b|=((uint64_t) message[3]) << 24; magick_fallthrough;
    case 3: b|=((uint64_t) message[2]) << 16; magick_fallthrough;
    case 2: b|=((uint64_t) message[1]) << 8; magick_fallthrough;
    case 1: b|=((uint64_t) message[0]); magick_fallthrough;
    default: break;
  }
  v3^=b;
  for (i=0; i < 2; i++)
  {
    v0+=v1; v1=ROTL(v1,13); v1^=v0; v0=ROTL(v0,32);
    v2+=v3; v3=ROTL(v3,16); v3^=v2;
    v0+=v3; v3=ROTL(v3,21); v3^=v0;
    v2+=v1; v1=ROTL(v1,17); v1^=v2; v2=ROTL(v2,32);
  }
  v0^=b;
  v2^=0xff;
  for (i=0; i < 4; i++)
  {
    v0+=v1; v1=ROTL(v1,13); v1^=v0; v0=ROTL(v0,32);
    v2+=v3; v3=ROTL(v3,16); v3^=v2;
    v0+=v3; v3=ROTL(v3,21); v3^=v0;
    v2+=v1; v1=ROTL(v1,17); v1^=v2; v2=ROTL(v2,32);
  }
  return(v0^v1^v2^v3);
}

static inline void DeriveSipKeyFromSecret(const char *shared_secret,
  uint8_t key[16])
{
  size_t
    i,
    length;

  uint64_t
    k0 = 0x0706050403020100ULL,
    k1 = 0x0f0e0d0c0b0a0908ULL;
  
  length=strlen(shared_secret);
  for (i=0; i < length; i++)
  {
    uint8_t 
      b = shared_secret[i];
    
    k0^=b;
    k0*=0x100000001b3ULL;   
    k1^=(uint64_t) b << ((i & 7)*8);
    k1=(k1 << 5) | (k1 >> (64-5));
  }
  (void) memcpy(key,&k0,8);
  (void) memcpy(key+8,&k1,8);
}

static inline uint64_t GenerateSessionKey(const char *shared_secret,
  const unsigned char *nonce,size_t length)
{
  uint8_t
    key[16];

  DeriveSipKeyFromSecret(shared_secret,key);
  return(SIPHash24(key,nonce,length));
}

static int ConnectPixelCacheServer(const char *hostname,const int port,
  uint64_t *session_key,ExceptionInfo *exception)
{
#if defined(MAGICKCORE_HAVE_DISTRIBUTE_CACHE)
  char
    service[MagickPathExtent],
    *shared_secret;

  int
    status;

  SOCKET_TYPE
    client_socket;

  ssize_t
    count;

  struct addrinfo
    hints,
    *result;

  unsigned char
    nonce[DPCSessionKeyLength];

  /*
    Connect to distributed pixel cache server and get session key.
  */
  *session_key=0;
#if defined(MAGICKCORE_HAVE_WINSOCK2)
  InitializeWinsock2(MagickTrue);
#endif
  (void) memset(&hints,0,sizeof(hints));
  hints.ai_family=AF_INET;
  hints.ai_socktype=SOCK_STREAM;
  hints.ai_flags=AI_PASSIVE;
  (void) FormatLocaleString(service,MagickPathExtent,"%d",port);
  status=getaddrinfo(hostname,service,&hints,&result);
  if (status != 0)
    {
      (void) ThrowMagickException(exception,GetMagickModule(),CacheError,
        "DistributedPixelCache","'%s': %s",hostname,gai_strerror(status));
      return(-1);
    }
  client_socket=(SOCKET_TYPE) socket(result->ai_family,result->ai_socktype,
    result->ai_protocol);
  if (client_socket == -1)
    {
      freeaddrinfo(result);
      (void) ThrowMagickException(exception,GetMagickModule(),CacheError,
        "DistributedPixelCache","'%s': %s",hostname,GetExceptionMessage(errno));
      return(-1);
    }
  status=connect(client_socket,result->ai_addr,(socklen_t) result->ai_addrlen);
  freeaddrinfo(result);
  if (status == -1)
    {
      CLOSE_SOCKET(client_socket);
      (void) ThrowMagickException(exception,GetMagickModule(),CacheError,
        "DistributedPixelCache","'%s': %s",hostname,GetExceptionMessage(errno));
      return(-1);
    }
  /*
    Receive server nonce.
  */
  count=recv(client_socket,(char *) nonce,sizeof(nonce),0);
  if (count != (ssize_t) sizeof(nonce))
    {
      CLOSE_SOCKET(client_socket);
      (void) ThrowMagickException(exception,GetMagickModule(),CacheError,
        "DistributedPixelCache","'%s': %s",hostname,GetExceptionMessage(errno));
      return(-1);
    }
  /*
    Compute keyed hash(shared_secret,nonce).
  */
  shared_secret=GetPolicyValue("cache:shared-secret");
  if (shared_secret == (char*) NULL)
    {
      CLOSE_SOCKET(client_socket);
      (void) ThrowMagickException(exception,GetMagickModule(),CacheError,
        "DistributedPixelCache","'%s': shared secret required",hostname);
      return(-1);
    }
  *session_key=GenerateSessionKey(shared_secret,nonce,sizeof(nonce));
  shared_secret=DestroyString(shared_secret);
  /*
    Send keyed hash back to client.
  */
  count=send(client_socket,(char *) session_key,sizeof(*session_key),
    MSG_NOSIGNAL);
  if (count != (ssize_t) sizeof(*session_key))
    {
      CLOSE_SOCKET(client_socket);
      (void) ThrowMagickException(exception,GetMagickModule(),CacheError,
        "DistributedPixelCache","'%s': authentication failed",hostname);
      return(-1);
    }
  return((int) client_socket);
#else
  (void) ThrowMagickException(exception,GetMagickModule(),MissingDelegateError,
    "DelegateLibrarySupportNotBuiltIn","distributed pixel cache");
  return(-1);
#endif
}
#endif

static char *GetHostname(int *port,ExceptionInfo *exception)
{
  char
    *host,
    *hosts,
    **hostlist;

  int
    argc;

  ssize_t
    i;

  static size_t
    id = 0;

  /*
    Parse host list (e.g. 192.168.100.1:6668,192.168.100.2:6668).
  */
  hosts=(char *) GetImageRegistry(StringRegistryType,"cache:hosts",exception);
  if (hosts == (char *) NULL)
    {
      *port=DPCPort;
      return(AcquireString(DPCHostname));
    }
  (void) SubstituteString(&hosts,","," ");
  hostlist=StringToArgv(hosts,&argc);
  hosts=DestroyString(hosts);
  if (hostlist == (char **) NULL)
    {
      *port=DPCPort;
      return(AcquireString(DPCHostname));
    }
  {
    size_t host_count = (size_t) argc-1;
    size_t index = (id++ % host_count)+1;
    hosts=AcquireString(hostlist[index]);
  }
  for (i=0; i < (ssize_t) argc; i++)
    hostlist[i]=DestroyString(hostlist[i]);
  hostlist=(char **) RelinquishMagickMemory(hostlist);
  (void) SubstituteString(&hosts,":"," ");
  hostlist=StringToArgv(hosts,&argc);
  if (hostlist == (char **) NULL)
    {
      *port=DPCPort;
      return(AcquireString(DPCHostname));
    }
  host=AcquireString(hostlist[1]);
  if (hostlist[2] == (char *) NULL)
    *port=DPCPort;
  else
    *port=StringToLong(hostlist[2]);
  for (i=0; i < (ssize_t) argc; i++)
    hostlist[i]=DestroyString(hostlist[i]);
  hostlist=(char **) RelinquishMagickMemory(hostlist);
  return(host);
}

MagickPrivate DistributeCacheInfo *AcquireDistributeCacheInfo(
  ExceptionInfo *exception)
{
  char
    *hostname;

  DistributeCacheInfo
    *server_info;

  uint64_t
    session_key;

  /*
    Connect to the distributed pixel cache server.
  */
  server_info=(DistributeCacheInfo *) AcquireCriticalMemory(
    sizeof(*server_info));
  (void) memset(server_info,0,sizeof(*server_info));
  server_info->signature=MagickCoreSignature;
  server_info->port=0;
  hostname=GetHostname(&server_info->port,exception);
  session_key=0;
  server_info->file=ConnectPixelCacheServer(hostname,server_info->port,
    &session_key,exception);
  if (server_info->file == -1)
    server_info=DestroyDistributeCacheInfo(server_info);
  else
    {
      server_info->session_key=session_key;
      (void) CopyMagickString(server_info->hostname,hostname,MagickPathExtent);
      server_info->debug=(GetLogEventMask() & CacheEvent) != 0 ? MagickTrue :
        MagickFalse;
    }
  hostname=DestroyString(hostname);
  return(server_info);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
+   D e s t r o y D i s t r i b u t e C a c h e I n f o                       %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  DestroyDistributeCacheInfo() deallocates memory associated with an
%  DistributeCacheInfo structure.
%
%  The format of the DestroyDistributeCacheInfo method is:
%
%      DistributeCacheInfo *DestroyDistributeCacheInfo(
%        DistributeCacheInfo *server_info)
%
%  A description of each parameter follows:
%
%    o server_info: the distributed cache info.
%
*/
MagickPrivate DistributeCacheInfo *DestroyDistributeCacheInfo(
  DistributeCacheInfo *server_info)
{
  assert(server_info != (DistributeCacheInfo *) NULL);
  assert(server_info->signature == MagickCoreSignature);
#if defined(MAGICKCORE_HAVE_DISTRIBUTE_CACHE)
  if (server_info->file >= 0)
    CLOSE_SOCKET(server_info->file);
#endif
  server_info->signature=(~MagickCoreSignature);
  server_info=(DistributeCacheInfo *) RelinquishMagickMemory(server_info);
  return(server_info);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
+   D i s t r i b u t e P i x e l C a c h e S e r v e r                       %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  DistributePixelCacheServer() waits on the specified port for commands to
%  create, read, update, or destroy a pixel cache.
%
%  The format of the DistributePixelCacheServer() method is:
%
%      void DistributePixelCacheServer(const int port)
%
%  A description of each parameter follows:
%
%    o port: connect the distributed pixel cache at this port.
%
%    o exception: return any errors or warnings in this structure.
%
*/

#if !defined(MAGICKCORE_HAVE_DISTRIBUTE_CACHE)
static inline MagickOffsetType dpc_send(SOCKET_TYPE magick_unused(file),
  const MagickSizeType magick_unused(length),
  const void *magick_restrict magick_unused(message))
{
  magick_unreferenced(file);
  magick_unreferenced(length);
  magick_unreferenced(message);
  return(-1);
}
#else
static inline MagickOffsetType dpc_send(SOCKET_TYPE file,
  const MagickSizeType length,const void *magick_restrict message)
{
  MagickOffsetType
    i;

  ssize_t
    count;

  /*
    Ensure a complete message is sent.
  */
  count=0;
  for (i=0; i < (MagickOffsetType) length; i+=count)
  {
    count=(ssize_t) send(file,(char *) message+i,(LENGTH_TYPE) MagickMin(
      length-(MagickSizeType) i,(MagickSizeType) MagickMaxBufferExtent),
      MSG_NOSIGNAL);
    if (count <= 0)
      {
        count=0;
        if (errno != EINTR)
          break;
      }
  }
  return(i);
}
#endif

#if !defined(MAGICKCORE_HAVE_DISTRIBUTE_CACHE)
MagickExport void DistributePixelCacheServer(const int magick_unused(port),
  ExceptionInfo *magick_unused(exception))
{
  magick_unreferenced(port);
  magick_unreferenced(exception);
  ThrowFatalException(MissingDelegateError,"DelegateLibrarySupportNotBuiltIn");
}
#else
static MagickBooleanType DestroyDistributeCache(SplayTreeInfo *registry,
  const uint64_t session_key)
{
  MagickAddressType
    key = (MagickAddressType) session_key;

  /*
    Destroy distributed pixel cache.
  */
  return(DeleteNodeFromSplayTree(registry,(const void *) key));
}

static MagickBooleanType OpenDistributeCache(SplayTreeInfo *registry,
  SOCKET_TYPE file,const uint64_t session_key,ExceptionInfo *exception)
{
  Image
    *image;

  MagickAddressType
    key = (MagickAddressType) session_key;

  MagickBooleanType
    status;

  MagickOffsetType
    count;

  MagickSizeType
    length;

  unsigned char
    message[MagickPathExtent],
    *p;

  /*
    Open distributed pixel cache.
  */
  image=AcquireImage((ImageInfo *) NULL,exception);
  if (image == (Image *) NULL)
    return(MagickFalse);
  length=sizeof(image->storage_class)+sizeof(image->colorspace)+
    sizeof(image->alpha_trait)+sizeof(image->channels)+sizeof(image->columns)+
    sizeof(image->rows)+sizeof(image->number_channels)+MaxPixelChannels*
    sizeof(*image->channel_map)+sizeof(image->metacontent_extent);
  count=dpc_read(file,length,message);
  if (count != (MagickOffsetType) length)
    {
      image=DestroyImage(image);
      return(MagickFalse);
    }
  /*
    Deserialize the image attributes.
  */
  p=message;
  (void) memcpy(&image->storage_class,p,sizeof(image->storage_class));
  p+=(ptrdiff_t) sizeof(image->storage_class);
  (void) memcpy(&image->colorspace,p,sizeof(image->colorspace));
  p+=(ptrdiff_t) sizeof(image->colorspace);
  (void) memcpy(&image->alpha_trait,p,sizeof(image->alpha_trait));
  p+=(ptrdiff_t) sizeof(image->alpha_trait);
  (void) memcpy(&image->channels,p,sizeof(image->channels));
  p+=(ptrdiff_t) sizeof(image->channels);
  (void) memcpy(&image->columns,p,sizeof(image->columns));
  p+=(ptrdiff_t) sizeof(image->columns);
  (void) memcpy(&image->rows,p,sizeof(image->rows));
  p+=(ptrdiff_t) sizeof(image->rows);
  (void) memcpy(&image->number_channels,p,sizeof(image->number_channels));
  p+=(ptrdiff_t) sizeof(image->number_channels);
  (void) memcpy(image->channel_map,p,MaxPixelChannels*
    sizeof(*image->channel_map));
  p+=(ptrdiff_t) MaxPixelChannels*sizeof(*image->channel_map);
  (void) memcpy(&image->metacontent_extent,p,sizeof(image->metacontent_extent));
  p+=(ptrdiff_t) sizeof(image->metacontent_extent);
  if (SyncImagePixelCache(image,exception) == MagickFalse)
    {
      image=DestroyImage(image);
      return(MagickFalse);
    }
  status=AddValueToSplayTree(registry,(const void *) key,image);
  if (status == MagickFalse)
    {
      image=DestroyImage(image);
      return(MagickFalse);
    }
  return(status);
}

static inline MagickBooleanType ValidateDistributedPixelCache(
  const RectangleInfo *region,const size_t per_pixel,
  const MagickSizeType length)
{
  size_t
    extent = 0,
    pixels = 0;

  if (HeapOverflowSanityCheckGetSize(region->width,region->height,&pixels) != MagickFalse)
    return(MagickFalse);
  if (HeapOverflowSanityCheckGetSize(pixels,per_pixel,&extent) != MagickFalse)
    return(MagickFalse);
  if (length > (MagickSizeType) extent)
    return(MagickFalse);
  return(MagickTrue);
}

static MagickBooleanType ReadDistributeCacheMetacontent(SplayTreeInfo *registry,
  SOCKET_TYPE file,const uint64_t session_key,ExceptionInfo *exception)
{
  const Quantum
    *p;

  const unsigned char
    *metacontent;

  Image
    *image;

  MagickAddressType
    key = (MagickAddressType) session_key;

  MagickOffsetType
    count;

  MagickSizeType
    length;

  RectangleInfo
    region;

  size_t
    per_pixel;

  unsigned char
    message[MagickPathExtent],
    *q;

  /*
    Read distributed pixel cache metacontent.
  */
  image=(Image *) GetValueFromSplayTree(registry,(const void *) key);
  if (image == (Image *) NULL)
    return(MagickFalse);
  length=sizeof(region.width)+sizeof(region.height)+sizeof(region.x)+
    sizeof(region.y)+sizeof(length);
  count=dpc_read(file,length,message);
  if (count != (MagickOffsetType) length)
    return(MagickFalse);
  q=message;
  (void) memcpy(&region.width,q,sizeof(region.width));
  q+=(ptrdiff_t) sizeof(region.width);
  (void) memcpy(&region.height,q,sizeof(region.height));
  q+=(ptrdiff_t) sizeof(region.height);
  (void) memcpy(&region.x,q,sizeof(region.x));
  q+=(ptrdiff_t) sizeof(region.x);
  (void) memcpy(&region.y,q,sizeof(region.y));
  q+=(ptrdiff_t) sizeof(region.y);
  (void) memcpy(&length,q,sizeof(length));
  q+=(ptrdiff_t) sizeof(length);
  per_pixel=image->number_meta_channels*sizeof(Quantum);
  if (ValidateDistributedPixelCache(&region,per_pixel,length) == MagickFalse)
    return(MagickFalse);
  p=GetVirtualPixels(image,region.x,region.y,region.width,region.height,
    exception);
  if (p == (const Quantum *) NULL)
    return(MagickFalse);
  metacontent=(const unsigned char *) GetVirtualMetacontent(image);
  count=dpc_send(file,length,metacontent);
  if (count != (MagickOffsetType) length)
    return(MagickFalse);
  return(MagickTrue);
}

static MagickBooleanType ReadDistributeCachePixels(SplayTreeInfo *registry,
  SOCKET_TYPE file,const uint64_t session_key,ExceptionInfo *exception)
{
  const Quantum
    *p;

  Image
    *image;

  MagickAddressType
    key = (MagickAddressType) session_key;

  MagickOffsetType
    count;

  MagickSizeType
    length;

  RectangleInfo
    region;

  size_t
    per_pixel;

  unsigned char
    message[MagickPathExtent],
    *q;

  /*
    Read distributed pixel cache pixels.
  */
  image=(Image *) GetValueFromSplayTree(registry,(const void *) key);
  if (image == (Image *) NULL)
    return(MagickFalse);
  length=sizeof(region.width)+sizeof(region.height)+sizeof(region.x)+
    sizeof(region.y)+sizeof(length);
  count=dpc_read(file,length,message);
  if (count != (MagickOffsetType) length)
    return(MagickFalse);
  q=message;
  (void) memcpy(&region.width,q,sizeof(region.width));
  q+=(ptrdiff_t) sizeof(region.width);
  (void) memcpy(&region.height,q,sizeof(region.height));
  q+=(ptrdiff_t) sizeof(region.height);
  (void) memcpy(&region.x,q,sizeof(region.x));
  q+=(ptrdiff_t) sizeof(region.x);
  (void) memcpy(&region.y,q,sizeof(region.y));
  q+=(ptrdiff_t) sizeof(region.y);
  (void) memcpy(&length,q,sizeof(length));
  per_pixel=image->number_channels*sizeof(Quantum);
  if (ValidateDistributedPixelCache(&region,per_pixel,length) == MagickFalse)
    return(MagickFalse);
  q+=(ptrdiff_t) sizeof(length);
  p=GetVirtualPixels(image,region.x,region.y,region.width,region.height,
    exception);
  if (p == (const Quantum *) NULL)
    return(MagickFalse);
  count=dpc_send(file,length,p);
  if (count != (MagickOffsetType) length)
    return(MagickFalse);
  return(MagickTrue);
}

static void *RelinquishImageRegistry(void *image)
{
  return((void *) DestroyImageList((Image *) image));
}

static MagickBooleanType WriteDistributeCacheMetacontent(
  SplayTreeInfo *registry,SOCKET_TYPE file,const uint64_t session_key,
  ExceptionInfo *exception)
{
  Image
    *image;

  MagickAddressType
    key = (MagickAddressType) session_key;

  MagickOffsetType
    count;

  MagickSizeType
    length;

  Quantum
    *q;

  RectangleInfo
    region;

  size_t
    per_pixel;

  unsigned char
    message[MagickPathExtent],
    *metacontent,
    *p;

  /*
    Write distributed pixel cache metacontent.
  */
  image=(Image *) GetValueFromSplayTree(registry,(const void *) key);
  if (image == (Image *) NULL)
    return(MagickFalse);
  length=sizeof(region.width)+sizeof(region.height)+sizeof(region.x)+
    sizeof(region.y)+sizeof(length);
  count=dpc_read(file,length,message);
  if (count != (MagickOffsetType) length)
    return(MagickFalse);
  p=message;
  (void) memcpy(&region.width,p,sizeof(region.width));
  p+=(ptrdiff_t) sizeof(region.width);
  (void) memcpy(&region.height,p,sizeof(region.height));
  p+=(ptrdiff_t) sizeof(region.height);
  (void) memcpy(&region.x,p,sizeof(region.x));
  p+=(ptrdiff_t) sizeof(region.x);
  (void) memcpy(&region.y,p,sizeof(region.y));
  p+=(ptrdiff_t) sizeof(region.y);
  (void) memcpy(&length,p,sizeof(length));
  per_pixel=image->number_meta_channels*sizeof(Quantum);
  if (ValidateDistributedPixelCache(&region,per_pixel,length) == MagickFalse)
    return(MagickFalse);
  p+=(ptrdiff_t) sizeof(length);
  q=GetAuthenticPixels(image,region.x,region.y,region.width,region.height,
    exception);
  if (q == (Quantum *) NULL)
    return(MagickFalse);
  metacontent=(unsigned char *) GetAuthenticMetacontent(image);
  count=dpc_read(file,length,metacontent);
  if (count != (MagickOffsetType) length)
    return(MagickFalse);
  return(SyncAuthenticPixels(image,exception));
}

static MagickBooleanType WriteDistributeCachePixels(SplayTreeInfo *registry,
  SOCKET_TYPE file,const uint64_t session_key,ExceptionInfo *exception)
{
  Image
    *image;

  MagickAddressType
    key = (MagickAddressType) session_key;

  MagickOffsetType
    count;

  MagickSizeType
    length;

  Quantum
    *q;

  RectangleInfo
    region;

  size_t
    per_pixel;

  unsigned char
    message[MagickPathExtent],
    *p;

  /*
    Write distributed pixel cache pixels.
  */
  image=(Image *) GetValueFromSplayTree(registry,(const void *) key);
  if (image == (Image *) NULL)
    return(MagickFalse);
  length=sizeof(region.width)+sizeof(region.height)+sizeof(region.x)+
    sizeof(region.y)+sizeof(length);
  count=dpc_read(file,length,message);
  if (count != (MagickOffsetType) length)
    return(MagickFalse);
  p=message;
  (void) memcpy(&region.width,p,sizeof(region.width));
  p+=(ptrdiff_t) sizeof(region.width);
  (void) memcpy(&region.height,p,sizeof(region.height));
  p+=(ptrdiff_t) sizeof(region.height);
  (void) memcpy(&region.x,p,sizeof(region.x));
  p+=(ptrdiff_t) sizeof(region.x);
  (void) memcpy(&region.y,p,sizeof(region.y));
  p+=(ptrdiff_t) sizeof(region.y);
  (void) memcpy(&length,p,sizeof(length));
  per_pixel=image->number_channels*sizeof(Quantum);
  if (ValidateDistributedPixelCache(&region,per_pixel,length) == MagickFalse)
    return(MagickFalse);
  p+=(ptrdiff_t) sizeof(length);
  q=GetAuthenticPixels(image,region.x,region.y,region.width,region.height,
    exception);
  if (q == (Quantum *) NULL)
    return(MagickFalse);
  count=dpc_read(file,length,(unsigned char *) q);
  if (count != (MagickOffsetType) length)
    return(MagickFalse);
  return(SyncAuthenticPixels(image,exception));
}

static HANDLER_RETURN_TYPE DistributePixelCacheClient(void *socket_arg)
{
  char
    *shared_secret;

  ExceptionInfo
    *exception;

  MagickBooleanType
    status = MagickFalse;

  MagickOffsetType
    count;

  RandomInfo
    *random_info;

  SOCKET_TYPE
    client_socket,
    *client_socket_ptr = (SOCKET_TYPE *) socket_arg;

  SplayTreeInfo
    *registry;

  StringInfo
    *entropy;

  uint64_t
    key,
    session_key;

  unsigned char
    command,
    nonce[DPCSessionKeyLength];

  /*
    Load shared secret.
  */
  client_socket=(*client_socket_ptr);
  client_socket_ptr=(SOCKET_TYPE *) RelinquishMagickMemory(client_socket_ptr);
  shared_secret=GetPolicyValue("cache:shared-secret");
  if (shared_secret == NULL)
    ThrowFatalException(CacheFatalError,"shared secret required");
  /*
    Generate random nonce.
  */
  random_info=AcquireRandomInfo();
  entropy=GetRandomKey(random_info,sizeof(nonce));
  (void) memcpy(nonce,GetStringInfoDatum(entropy),sizeof(nonce));
  entropy=DestroyStringInfo(entropy);
  random_info=DestroyRandomInfo(random_info);
  /*
    Derive session key.
  */
  session_key=GenerateSessionKey(shared_secret,nonce,sizeof(nonce));
  shared_secret=DestroyString(shared_secret);
  /*
    Send nonce to client.
  */
  count=dpc_send(client_socket,sizeof(nonce),nonce);
  if (count != (MagickOffsetType) sizeof(nonce))
    {
      CLOSE_SOCKET(client_socket);
      return(HANDLER_RETURN_VALUE);
    }
  /*
    Receive client's keyed hash.
  */
  count=dpc_read(client_socket,sizeof(key),(unsigned char *) &key);
  if ((count != (MagickOffsetType) sizeof(key)) || (key != session_key))
    {
      CLOSE_SOCKET(client_socket);
      return(HANDLER_RETURN_VALUE);
    }
  exception=AcquireExceptionInfo();
  registry=NewSplayTree((int (*)(const void *,const void *)) NULL,
    (void *(*)(void *)) NULL,RelinquishImageRegistry);
  /*
    Command loop.
  */
  for (status=MagickFalse; ; )
  {
    /*
      Each command must echo the authenticated session key.
    */
    count=dpc_read(client_socket,1,(unsigned char *) &command);
    if (count <= 0)
      break;
    count=dpc_read(client_socket,sizeof(key),(unsigned char *) &key);
    if ((count != (MagickOffsetType) sizeof(key)) || (key != session_key))
      break;
    switch (command)
    {
      case 'o':
      {
        status=OpenDistributeCache(registry,client_socket,session_key,
          exception);
        dpc_send(client_socket,sizeof(status),&status);
        break;
      }
      case 'r':
      {
        status=ReadDistributeCachePixels(registry,client_socket,session_key,
          exception);
        break;
      }
      case 'R':
      {
        status=ReadDistributeCacheMetacontent(registry,client_socket,
          session_key,exception);
        break;
      }
      case 'w':
      {
        status=WriteDistributeCachePixels(registry,client_socket,session_key,
          exception);
        break;
      }
      case 'W':
      {
        status=WriteDistributeCacheMetacontent(registry,client_socket,
          session_key,exception);
        break;
      }
      case 'd':
      {
        status=DestroyDistributeCache(registry,session_key);
        break;
      }
      default:
        break;
    }
    if ((status == MagickFalse) || (command == 'd'))
      break;
  }
  count=dpc_send(client_socket,sizeof(status),&status);
  CLOSE_SOCKET(client_socket);
  exception=DestroyExceptionInfo(exception);
  registry=DestroySplayTree(registry);
  return(HANDLER_RETURN_VALUE);
}

MagickExport void DistributePixelCacheServer(const int port,
  ExceptionInfo *exception)
{
  char
    service[MagickPathExtent];

  int
    status;

#if defined(MAGICKCORE_THREAD_SUPPORT)
  pthread_attr_t
    attributes;

  pthread_t
    thread_id;
#elif defined(_MSC_VER)
  DWORD
    threadID;
#else
  Not implemented!
#endif

  SOCKET_TYPE
    server_socket;

  struct addrinfo
    *p;

  struct addrinfo
    hint,
    *result;

  struct sockaddr_in
    address;

  /*
    Launch distributed pixel cache server.
  */
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickCoreSignature);
  magick_unreferenced(exception);
#if defined(MAGICKCORE_HAVE_WINSOCK2)
  InitializeWinsock2(MagickFalse);
#endif
  (void) memset(&hint,0,sizeof(hint));
  hint.ai_family=AF_INET;
  hint.ai_socktype=SOCK_STREAM;
  hint.ai_flags=AI_PASSIVE;
  FormatLocaleString(service,MagickPathExtent,"%d",port);
  status=getaddrinfo(NULL,service,&hint,&result);
  if (status != 0)
    ThrowFatalException(CacheFatalError, "UnableToListen");
  server_socket=(SOCKET_TYPE) 0;
  for (p=result; p != NULL; p=p->ai_next)
  {
    int
      one = 1;

    server_socket=(SOCKET_TYPE) socket(p->ai_family,p->ai_socktype,
      p->ai_protocol);
    if (server_socket == -1)
      continue;
    status=setsockopt(server_socket,SOL_SOCKET,SO_REUSEADDR,(char *) &one,
      (socklen_t) sizeof(one));
    if (status != -1)
      {
#if defined(MAGICKCORE_HAVE_WINSOCK2)
        DWORD timeout = 5000; // ms
        status=setsockopt(server_socket,SOL_SOCKET,SO_RCVTIMEO,(const char *)
          &timeout,sizeof(timeout));
#else
        struct timeval tv;
        tv.tv_sec=5;
        tv.tv_usec=0;
        status=setsockopt(server_socket,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv));
#endif
      }
    if (status == -1)
      {
        CLOSE_SOCKET(server_socket);
        continue;
      }
    status=bind(server_socket,p->ai_addr,(socklen_t) p->ai_addrlen);
    if (status == -1)
      {
        CLOSE_SOCKET(server_socket);
        continue;
      }
    break;
  }
  if (p == (struct addrinfo *) NULL)
    ThrowFatalException(CacheFatalError,"UnableToBind");
  freeaddrinfo(result);
  status=listen(server_socket,DPCPendingConnections);
  if (status != 0)
    ThrowFatalException(CacheFatalError,"UnableToListen");
#if defined(MAGICKCORE_THREAD_SUPPORT)
  pthread_attr_init(&attributes);
  pthread_attr_setdetachstate(&attributes,PTHREAD_CREATE_DETACHED);
#endif
  for ( ; ; )
  {
    SOCKET_TYPE
      *client_socket_ptr;

    socklen_t
      length = (socklen_t) sizeof(address);

    client_socket_ptr=(SOCKET_TYPE *) AcquireMagickMemory(sizeof(SOCKET_TYPE));
    if (client_socket_ptr == NULL)
      continue;  /* skip connection */
    *client_socket_ptr=(SOCKET_TYPE) accept(server_socket,(struct sockaddr *)
      &address,&length);
    if (*client_socket_ptr == -1)
      {
        client_socket_ptr=(SOCKET_TYPE *) RelinquishMagickMemory(
          client_socket_ptr);
        continue;
      }
#if defined(MAGICKCORE_THREAD_SUPPORT)
    status=pthread_create(&thread_id,&attributes,DistributePixelCacheClient,
      (void *) client_socket_ptr);
    if (status != 0)
      {
        CLOSE_SOCKET(*client_socket_ptr);
        RelinquishMagickMemory(client_socket_ptr);
        ThrowFatalException(CacheFatalError,"UnableToCreateClientThread");
      }
#elif defined(_MSC_VER)
    if (CreateThread(0,0,DistributePixelCacheClient,(void*) client_socket_ptr,0,&threadID) == (HANDLE) NULL)
      {
        CLOSE_SOCKET(*client_socket_ptr);
        RelinquishMagickMemory(client_socket_ptr);
        ThrowFatalException(CacheFatalError,"UnableToCreateClientThread");
      }
#else
    Not implemented!
#endif
  }
}
#endif

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
+  D i s t r i b u t e C a c h e T e r m i n u s                              %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  DistributeCacheTerminus() destroys the Distributed Cache.
%
*/
MagickPrivate void DistributeCacheTerminus(void)
{
#ifdef MAGICKCORE_HAVE_WINSOCK2
  if (winsock2_semaphore == (SemaphoreInfo *) NULL)
    ActivateSemaphoreInfo(&winsock2_semaphore);
  LockSemaphoreInfo(winsock2_semaphore);
  if (wsaData != (WSADATA *) NULL)
    {
      WSACleanup();
      wsaData=(WSADATA *) RelinquishMagickMemory((void *) wsaData);
    }
  UnlockSemaphoreInfo(winsock2_semaphore);
  RelinquishSemaphoreInfo(&winsock2_semaphore);
#endif
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
+   G e t D i s t r i b u t e C a c h e F i l e                               %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  GetDistributeCacheFile() returns the file associated with this
%  DistributeCacheInfo structure.
%
%  The format of the GetDistributeCacheFile method is:
%
%      int GetDistributeCacheFile(const DistributeCacheInfo *server_info)
%
%  A description of each parameter follows:
%
%    o server_info: the distributed cache info.
%
*/
MagickPrivate int GetDistributeCacheFile(const DistributeCacheInfo *server_info)
{
  assert(server_info != (DistributeCacheInfo *) NULL);
  assert(server_info->signature == MagickCoreSignature);
  return(server_info->file);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
+   G e t D i s t r i b u t e C a c h e H o s t n a m e                       %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  GetDistributeCacheHostname() returns the hostname associated with this
%  DistributeCacheInfo structure.
%
%  The format of the GetDistributeCacheHostname method is:
%
%      const char *GetDistributeCacheHostname(
%        const DistributeCacheInfo *server_info)
%
%  A description of each parameter follows:
%
%    o server_info: the distributed cache info.
%
*/
MagickPrivate const char *GetDistributeCacheHostname(
  const DistributeCacheInfo *server_info)
{
  assert(server_info != (DistributeCacheInfo *) NULL);
  assert(server_info->signature == MagickCoreSignature);
  return(server_info->hostname);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
+   G e t D i s t r i b u t e C a c h e P o r t                               %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  GetDistributeCachePort() returns the port associated with this
%  DistributeCacheInfo structure.
%
%  The format of the GetDistributeCachePort method is:
%
%      int GetDistributeCachePort(const DistributeCacheInfo *server_info)
%
%  A description of each parameter follows:
%
%    o server_info: the distributed cache info.
%
*/
MagickPrivate int GetDistributeCachePort(const DistributeCacheInfo *server_info)
{
  assert(server_info != (DistributeCacheInfo *) NULL);
  assert(server_info->signature == MagickCoreSignature);
  return(server_info->port);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
+   O p e n D i s t r i b u t e P i x e l C a c h e                           %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  OpenDistributePixelCache() opens a pixel cache on a remote server.
%
%  The format of the OpenDistributePixelCache method is:
%
%      MagickBooleanType *OpenDistributePixelCache(
%        DistributeCacheInfo *server_info,Image *image)
%
%  A description of each parameter follows:
%
%    o server_info: the distributed cache info.
%
%    o image: the image.
%
*/
MagickPrivate MagickBooleanType OpenDistributePixelCache(
  DistributeCacheInfo *server_info,Image *image)
{
  MagickBooleanType
    status;

  MagickOffsetType
    count;

  unsigned char
    message[MagickPathExtent],
    *p;

  /*
    Open distributed pixel cache.
  */
  assert(server_info != (DistributeCacheInfo *) NULL);
  assert(server_info->signature == MagickCoreSignature);
  assert(image != (Image *) NULL);
  assert(image->signature == MagickCoreSignature);
  p=message;
  *p++='o';  /* open */
  /*
    Serialize image attributes (see ValidatePixelCacheMorphology()).
  */
  (void) memcpy(p,&server_info->session_key,sizeof(server_info->session_key));
  p+=(ptrdiff_t) sizeof(server_info->session_key);
  (void) memcpy(p,&image->storage_class,sizeof(image->storage_class));
  p+=(ptrdiff_t) sizeof(image->storage_class);
  (void) memcpy(p,&image->colorspace,sizeof(image->colorspace));
  p+=(ptrdiff_t) sizeof(image->colorspace);
  (void) memcpy(p,&image->alpha_trait,sizeof(image->alpha_trait));
  p+=(ptrdiff_t) sizeof(image->alpha_trait);
  (void) memcpy(p,&image->channels,sizeof(image->channels));
  p+=(ptrdiff_t) sizeof(image->channels);
  (void) memcpy(p,&image->columns,sizeof(image->columns));
  p+=(ptrdiff_t) sizeof(image->columns);
  (void) memcpy(p,&image->rows,sizeof(image->rows));
  p+=(ptrdiff_t) sizeof(image->rows);
  (void) memcpy(p,&image->number_channels,sizeof(image->number_channels));
  p+=(ptrdiff_t) sizeof(image->number_channels);
  (void) memcpy(p,image->channel_map,MaxPixelChannels*
    sizeof(*image->channel_map));
  p+=(ptrdiff_t) MaxPixelChannels*sizeof(*image->channel_map);
  (void) memcpy(p,&image->metacontent_extent,sizeof(image->metacontent_extent));
  p+=(ptrdiff_t) sizeof(image->metacontent_extent);
  count=dpc_send(server_info->file,(MagickSizeType) (p-message),message);
  if (count != (MagickOffsetType) (p-message))
    return(MagickFalse);
  status=MagickFalse;
  count=dpc_read(server_info->file,sizeof(status),(unsigned char *) &status);
  if (count != (MagickOffsetType) sizeof(status))
    return(MagickFalse);
  return(status);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
+   R e a d D i s t r i b u t e P i x e l C a c h e M e t a c o n t e n t     %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  ReadDistributePixelCacheMetacontents() reads metacontent from the specified
%  region of the distributed pixel cache.
%
%  The format of the ReadDistributePixelCacheMetacontents method is:
%
%      MagickOffsetType ReadDistributePixelCacheMetacontents(
%        DistributeCacheInfo *server_info,const RectangleInfo *region,
%        const MagickSizeType length,unsigned char *metacontent)
%
%  A description of each parameter follows:
%
%    o server_info: the distributed cache info.
%
%    o image: the image.
%
%    o region: read the metacontent from this region of the image.
%
%    o length: the length in bytes of the metacontent.
%
%    o metacontent: read these metacontent from the pixel cache.
%
*/
MagickPrivate MagickOffsetType ReadDistributePixelCacheMetacontent(
  DistributeCacheInfo *server_info,const RectangleInfo *region,
  const MagickSizeType length,unsigned char *metacontent)
{
  MagickOffsetType
    count;

  unsigned char
    message[MagickPathExtent],
    *p;

  /*
    Read distributed pixel cache metacontent.
  */
  assert(server_info != (DistributeCacheInfo *) NULL);
  assert(server_info->signature == MagickCoreSignature);
  assert(region != (RectangleInfo *) NULL);
  assert(metacontent != (unsigned char *) NULL);
  if (length > (MagickSizeType) MAGICK_SSIZE_MAX)
    return(-1);
  p=message;
  *p++='R';
  (void) memcpy(p,&server_info->session_key,sizeof(server_info->session_key));
  p+=(ptrdiff_t) sizeof(server_info->session_key);
  (void) memcpy(p,&region->width,sizeof(region->width));
  p+=(ptrdiff_t) sizeof(region->width);
  (void) memcpy(p,&region->height,sizeof(region->height));
  p+=(ptrdiff_t) sizeof(region->height);
  (void) memcpy(p,&region->x,sizeof(region->x));
  p+=(ptrdiff_t) sizeof(region->x);
  (void) memcpy(p,&region->y,sizeof(region->y));
  p+=(ptrdiff_t) sizeof(region->y);
  (void) memcpy(p,&length,sizeof(length));
  p+=(ptrdiff_t) sizeof(length);
  count=dpc_send(server_info->file,(MagickSizeType) (p-message),message);
  if (count != (MagickOffsetType) (p-message))
    return(-1);
  return(dpc_read(server_info->file,length,metacontent));
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
+   R e a d D i s t r i b u t e P i x e l C a c h e P i x e l s               %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  ReadDistributePixelCachePixels() reads pixels from the specified region of
%  the distributed pixel cache.
%
%  The format of the ReadDistributePixelCachePixels method is:
%
%      MagickOffsetType ReadDistributePixelCachePixels(
%        DistributeCacheInfo *server_info,const RectangleInfo *region,
%        const MagickSizeType length,unsigned char *magick_restrict pixels)
%
%  A description of each parameter follows:
%
%    o server_info: the distributed cache info.
%
%    o image: the image.
%
%    o region: read the pixels from this region of the image.
%
%    o length: the length in bytes of the pixels.
%
%    o pixels: read these pixels from the pixel cache.
%
*/
MagickPrivate MagickOffsetType ReadDistributePixelCachePixels(
  DistributeCacheInfo *server_info,const RectangleInfo *region,
  const MagickSizeType length,unsigned char *magick_restrict pixels)
{
  MagickOffsetType
    count;

  unsigned char
    message[MagickPathExtent],
    *p;

  /*
    Read distributed pixel cache pixels.
  */
  assert(server_info != (DistributeCacheInfo *) NULL);
  assert(server_info->signature == MagickCoreSignature);
  assert(region != (RectangleInfo *) NULL);
  assert(pixels != (unsigned char *) NULL);
  if (length > (MagickSizeType) MAGICK_SSIZE_MAX)
    return(-1);
  p=message;
  *p++='r';
  (void) memcpy(p,&server_info->session_key,sizeof(server_info->session_key));
  p+=(ptrdiff_t) sizeof(server_info->session_key);
  (void) memcpy(p,&region->width,sizeof(region->width));
  p+=(ptrdiff_t) sizeof(region->width);
  (void) memcpy(p,&region->height,sizeof(region->height));
  p+=(ptrdiff_t) sizeof(region->height);
  (void) memcpy(p,&region->x,sizeof(region->x));
  p+=(ptrdiff_t) sizeof(region->x);
  (void) memcpy(p,&region->y,sizeof(region->y));
  p+=(ptrdiff_t) sizeof(region->y);
  (void) memcpy(p,&length,sizeof(length));
  p+=(ptrdiff_t) sizeof(length);
  count=dpc_send(server_info->file,(MagickSizeType) (p-message),message);
  if (count != (MagickOffsetType) (p-message))
    return(-1);
  return(dpc_read(server_info->file,length,pixels));
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
+   R e l i n q u i s h D i s t r i b u t e P i x e l C a c h e               %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  RelinquishDistributePixelCache() frees resources acquired with
%  OpenDistributePixelCache().
%
%  The format of the RelinquishDistributePixelCache method is:
%
%      MagickBooleanType RelinquishDistributePixelCache(
%        DistributeCacheInfo *server_info)
%
%  A description of each parameter follows:
%
%    o server_info: the distributed cache info.
%
*/
MagickPrivate MagickBooleanType RelinquishDistributePixelCache(
  DistributeCacheInfo *server_info)
{
  MagickBooleanType
    status;

  MagickOffsetType
    count;

  unsigned char
    message[MagickPathExtent],
    *p;

  /*
    Delete distributed pixel cache.
  */
  assert(server_info != (DistributeCacheInfo *) NULL);
  assert(server_info->signature == MagickCoreSignature);
  p=message;
  *p++='d';
  (void) memcpy(p,&server_info->session_key,sizeof(server_info->session_key));
  p+=(ptrdiff_t) sizeof(server_info->session_key);
  count=dpc_send(server_info->file,(MagickSizeType) (p-message),message);
  if (count != (MagickOffsetType) (p-message))
    return(MagickFalse);
  status=MagickFalse;
  count=dpc_read(server_info->file,sizeof(status),(unsigned char *) &status);
  if (count != (MagickOffsetType) sizeof(status))
    return(MagickFalse);
  return(status);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
+   W r i t e D i s t r i b u t e P i x e l C a c h e M e t a c o n t e n t   %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  WriteDistributePixelCacheMetacontents() writes image metacontent to the
%  specified region of the distributed pixel cache.
%
%  The format of the WriteDistributePixelCacheMetacontents method is:
%
%      MagickOffsetType WriteDistributePixelCacheMetacontents(
%        DistributeCacheInfo *server_info,const RectangleInfo *region,
%        const MagickSizeType length,const unsigned char *metacontent)
%
%  A description of each parameter follows:
%
%    o server_info: the distributed cache info.
%
%    o image: the image.
%
%    o region: write the metacontent to this region of the image.
%
%    o length: the length in bytes of the metacontent.
%
%    o metacontent: write these metacontent to the pixel cache.
%
*/
MagickPrivate MagickOffsetType WriteDistributePixelCacheMetacontent(
  DistributeCacheInfo *server_info,const RectangleInfo *region,
  const MagickSizeType length,const unsigned char *metacontent)
{
  MagickOffsetType
    count;

  unsigned char
    message[MagickPathExtent],
    *p;

  /*
    Write distributed pixel cache metacontent.
  */
  assert(server_info != (DistributeCacheInfo *) NULL);
  assert(server_info->signature == MagickCoreSignature);
  assert(region != (RectangleInfo *) NULL);
  assert(metacontent != (unsigned char *) NULL);
  if (length > (MagickSizeType) MAGICK_SSIZE_MAX)
    return(-1);
  p=message;
  *p++='W';
  (void) memcpy(p,&server_info->session_key,sizeof(server_info->session_key));
  p+=(ptrdiff_t) sizeof(server_info->session_key);
  (void) memcpy(p,&region->width,sizeof(region->width));
  p+=(ptrdiff_t) sizeof(region->width);
  (void) memcpy(p,&region->height,sizeof(region->height));
  p+=(ptrdiff_t) sizeof(region->height);
  (void) memcpy(p,&region->x,sizeof(region->x));
  p+=(ptrdiff_t) sizeof(region->x);
  (void) memcpy(p,&region->y,sizeof(region->y));
  p+=(ptrdiff_t) sizeof(region->y);
  (void) memcpy(p,&length,sizeof(length));
  p+=(ptrdiff_t) sizeof(length);
  count=dpc_send(server_info->file,(MagickSizeType) (p-message),message);
  if (count != (MagickOffsetType) (p-message))
    return(-1);
  return(dpc_send(server_info->file,length,metacontent));
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
+   W r i t e D i s t r i b u t e P i x e l C a c h e P i x e l s             %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  WriteDistributePixelCachePixels() writes image pixels to the specified
%  region of the distributed pixel cache.
%
%  The format of the WriteDistributePixelCachePixels method is:
%
%      MagickBooleanType WriteDistributePixelCachePixels(
%        DistributeCacheInfo *server_info,const RectangleInfo *region,
%        const MagickSizeType length,
%        const unsigned char *magick_restrict pixels)
%
%  A description of each parameter follows:
%
%    o server_info: the distributed cache info.
%
%    o image: the image.
%
%    o region: write the pixels to this region of the image.
%
%    o length: the length in bytes of the pixels.
%
%    o pixels: write these pixels to the pixel cache.
%
*/
MagickPrivate MagickOffsetType WriteDistributePixelCachePixels(
  DistributeCacheInfo *server_info,const RectangleInfo *region,
  const MagickSizeType length,const unsigned char *magick_restrict pixels)
{
  MagickOffsetType
    count;

  unsigned char
    message[MagickPathExtent],
    *p;

  /*
    Write distributed pixel cache pixels.
  */
  assert(server_info != (DistributeCacheInfo *) NULL);
  assert(server_info->signature == MagickCoreSignature);
  assert(region != (RectangleInfo *) NULL);
  assert(pixels != (const unsigned char *) NULL);
  if (length > (MagickSizeType) MAGICK_SSIZE_MAX)
    return(-1);
  p=message;
  *p++='w';
  (void) memcpy(p,&server_info->session_key,sizeof(server_info->session_key));
  p+=(ptrdiff_t) sizeof(server_info->session_key);
  (void) memcpy(p,&region->width,sizeof(region->width));
  p+=(ptrdiff_t) sizeof(region->width);
  (void) memcpy(p,&region->height,sizeof(region->height));
  p+=(ptrdiff_t) sizeof(region->height);
  (void) memcpy(p,&region->x,sizeof(region->x));
  p+=(ptrdiff_t) sizeof(region->x);
  (void) memcpy(p,&region->y,sizeof(region->y));
  p+=(ptrdiff_t) sizeof(region->y);
  (void) memcpy(p,&length,sizeof(length));
  p+=(ptrdiff_t) sizeof(length);
  count=dpc_send(server_info->file,(MagickSizeType) (p-message),message);
  if (count != (MagickOffsetType) (p-message))
    return(-1);
  return(dpc_send(server_info->file,length,pixels));
}
