/*
   nslcd-prot.h - helper macros for reading and writing in protocol streams

   Copyright (C) 2006 West Consulting
   Copyright (C) 2006-2014 Arthur de Jong

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

#ifndef COMMON__NSLCD_PROT_H
#define COMMON__NSLCD_PROT_H 1

#include <arpa/inet.h>
#include <netinet/in.h>

#include "tio.h"

/* If you use these macros you should define the following macros to
   handle error conditions (these marcos should clean up and return from the
   function):
     ERROR_OUT_WRITEERROR(fp)
     ERROR_OUT_READERROR(fp)
     ERROR_OUT_BUFERROR(fp)
     ERROR_OUT_NOSUCCESS(fp) */


/* Debugging marcos that can be used to enable detailed protocol logging,
   pass -DDEBUG_PROT to do overall protocol debugging, and -DDEBUG_PROT_DUMP
   to dump the actual bytestream. */

#ifdef DEBUG_PROT
/* define a debugging macro to output logging */
#include <string.h>
#include <errno.h>
#define DEBUG_PRINT(fmt, arg)                                               \
  fprintf(stderr, "%s:%d:%s: " fmt "\n", __FILE__, __LINE__,                \
          __PRETTY_FUNCTION__, arg);
#else /* DEBUG_PROT */
/* define an empty debug macro to disable logging */
#define DEBUG_PRINT(fmt, arg)
#endif /* not DEBUG_PROT */

#ifdef DEBUG_PROT_DUMP
/* define a debugging macro to output detailed logging */
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif /* HAVE_STDINT_H */
static void debug_dump(const void *ptr, size_t size)
{
  int i;
  for (i = 0; i < size; i++)
    fprintf(stderr, " %02x", ((const uint8_t *)ptr)[i]);
  fprintf(stderr, "\n");
}
#define DEBUG_DUMP(ptr, size)                                               \
  fprintf(stderr, "%s:%d:%s:", __FILE__, __LINE__, __PRETTY_FUNCTION__);    \
  debug_dump(ptr, size);
#else /* DEBUG_PROT_DUMP */
/* define an empty debug macro to disable logging */
#define DEBUG_DUMP(ptr, size)
#endif /* not DEBUG_PROT_DUMP */


/* WRITE marcos, used for writing data, on write error they will
   call the ERROR_OUT_WRITEERROR macro
   these macros may require the availability of the following
   variables:
   int32_t tmpint32; - temporary variable
   */

#define WRITE(fp, ptr, size)                                                \
  DEBUG_PRINT("WRITE       : var="__STRING(ptr)" size=%d", (int)size);      \
  DEBUG_DUMP(ptr, size);                                                    \
  if (tio_write(fp, ptr, (size_t)size))                                     \
  {                                                                         \
    DEBUG_PRINT("WRITE       : var="__STRING(ptr)" error: %s",              \
                strerror(errno));                                           \
    ERROR_OUT_WRITEERROR(fp);                                               \
  }

#define WRITE_INT32(fp, i)                                                  \
  DEBUG_PRINT("WRITE_INT32 : var="__STRING(i)" int32=%08x", (int)i);        \
  tmpint32 = htonl((int32_t)(i));                                           \
  WRITE(fp, &tmpint32, sizeof(int32_t))

#define WRITE_STRING(fp, str)                                               \
  DEBUG_PRINT("WRITE_STRING: var="__STRING(str)" string=\"%s\"", (str));    \
  if ((str) == NULL)                                                        \
  {                                                                         \
    WRITE_INT32(fp, 0);                                                     \
  }                                                                         \
  else                                                                      \
  {                                                                         \
    WRITE_INT32(fp, strlen(str));                                           \
    tmpint32 = ntohl(tmpint32);                                             \
    if (tmpint32 > 0)                                                       \
    {                                                                       \
      WRITE(fp, (str), tmpint32);                                           \
    }                                                                       \
  }

#define WRITE_STRINGLIST(fp, arr)                                           \
  if ((arr) == NULL)                                                        \
  {                                                                         \
    DEBUG_PRINT("WRITE_STRLST: var="__STRING(arr)" num=%d", 0);             \
    WRITE_INT32(fp, 0);                                                     \
  }                                                                         \
  else                                                                      \
  {                                                                         \
    /* first determine length of array */                                   \
    for (tmp3int32 = 0; (arr)[tmp3int32] != NULL; tmp3int32++)              \
      /* noting */ ;                                                        \
    /* write number of strings */                                           \
    DEBUG_PRINT("WRITE_STRLST: var="__STRING(arr)" num=%d", (int)tmp3int32); \
    WRITE_INT32(fp, tmp3int32);                                             \
    /* write strings */                                                     \
    for (tmp2int32 = 0; tmp2int32 < tmp3int32; tmp2int32++)                 \
    {                                                                       \
      WRITE_STRING(fp, (arr)[tmp2int32]);                                   \
    }                                                                       \
  }

#define WRITE_STRINGLIST_EXCEPT(fp, arr, not)                               \
  /* first determine length of array */                                     \
  tmp3int32 = 0;                                                            \
  for (tmp2int32 = 0; (arr)[tmp2int32] != NULL; tmp2int32++)                \
    if (strcmp((arr)[tmp2int32], (not)) != 0)                               \
      tmp3int32++;                                                          \
  /* write number of strings (mius one because we intend to skip one) */    \
  DEBUG_PRINT("WRITE_STRLST: var="__STRING(arr)" num=%d", (int)tmp3int32);  \
  WRITE_INT32(fp, tmp3int32);                                               \
  /* write strings */                                                       \
  for (tmp2int32 = 0; (arr)[tmp2int32] != NULL; tmp2int32++)                \
  {                                                                         \
    if (strcmp((arr)[tmp2int32], (not)) != 0)                               \
    {                                                                       \
      WRITE_STRING(fp, (arr)[tmp2int32]);                                   \
    }                                                                       \
  }

/* READ macros, used for reading data, on read error they will
   call the ERROR_OUT_READERROR or ERROR_OUT_BUFERROR macro
   these macros may require the availability of the following
   variables:
   int32_t tmpint32; - temporary variable
   */

#define READ(fp, ptr, size)                                                 \
  if (tio_read(fp, ptr, (size_t)size))                                      \
  {                                                                         \
    DEBUG_PRINT("READ       : var="__STRING(ptr)" error: %s",               \
                strerror(errno));                                           \
    ERROR_OUT_READERROR(fp);                                                \
  }                                                                         \
  DEBUG_PRINT("READ       : var="__STRING(ptr)" size=%d", (int)(size));     \
  DEBUG_DUMP(ptr, size);

#define READ_INT32(fp, i)                                                   \
  READ(fp, &tmpint32, sizeof(int32_t));                                     \
  (i) = (int32_t)ntohl(tmpint32);                                           \
  DEBUG_PRINT("READ_INT32 : var="__STRING(i)" int32==%08x", (int)(i));

/* read a string in a fixed-size "normal" buffer */
#define READ_STRING(fp, buffer)                                             \
  /* read the size of the string */                                         \
  READ(fp, &tmpint32, sizeof(int32_t));                                     \
  tmpint32 = ntohl(tmpint32);                                               \
  DEBUG_PRINT("READ_STRING: var="__STRING(buffer)" strlen=%d", tmpint32);   \
  /* check if read would fit */                                             \
  if (((size_t)tmpint32) >= sizeof(buffer))                                 \
  {                                                                         \
    /* will not fit */                                                      \
    tmpint32 = (tmpint32 - sizeof(buffer)) + 1;                             \
    DEBUG_PRINT("READ       : buffer %d bytes too small", tmpint32);        \
    ERROR_OUT_BUFERROR(fp);                                                 \
  }                                                                         \
  /* read string from the stream */                                         \
  if (tmpint32 > 0)                                                         \
  {                                                                         \
    READ(fp, buffer, (size_t)tmpint32);                                     \
  }                                                                         \
  /* null-terminate string in buffer */                                     \
  buffer[tmpint32] = '\0';                                                  \
  DEBUG_PRINT("READ_STRING: var="__STRING(buffer)" string=\"%s\"", buffer);


/* READ BUF macros that read data into a pre-allocated buffer.
   these macros may require the availability of the following
   variables:
   int32_t tmpint32; - temporary variable
   char *buffer;     - pointer to a buffer for reading strings
   size_t buflen;    - the size of the buffer
   size_t bufptr;    - the current position in the buffer
   */

/* current position in the buffer */
#define BUF_CUR                                                             \
  (buffer + bufptr)

/* check that the buffer has sz bytes left in it */
#define BUF_CHECK(fp, sz)                                                   \
  if ((bufptr + (size_t)(sz)) > buflen)                                     \
  {                                                                         \
    /* will not fit */                                                      \
    tmpint32 = bufptr + (sz) - (buflen);                                    \
    DEBUG_PRINT("READ       : buffer %d bytes too small", tmpint32);        \
    ERROR_OUT_BUFERROR(fp);                                                 \
  }

/* move the buffer pointer */
#define BUF_SKIP(sz)                                                        \
  bufptr += (size_t)(sz);

/* move BUF_CUR foreward so that it is aligned to the specified
   type width */
#define BUF_ALIGN(fp, type)                                                 \
  /* figure out number of bytes to skip foreward */                         \
  tmp2int32 = (sizeof(type) - ((BUF_CUR - (char *)NULL) % sizeof(type)))    \
              % sizeof(type);                                               \
  /* check and skip */                                                      \
  BUF_CHECK(fp, tmp2int32);                                                 \
  BUF_SKIP(tmp2int32);

/* allocate a piece of the buffer to store an array in */
#define BUF_ALLOC(fp, ptr, type, num)                                       \
  /* align to the specified type width */                                   \
  BUF_ALIGN(fp, type);                                                      \
  /* check that we have enough room */                                      \
  BUF_CHECK(fp, (size_t)(num) * sizeof(type));                              \
  /* store the pointer */                                                   \
  (ptr) = (type *)BUF_CUR;                                                  \
  /* reserve the space */                                                   \
  BUF_SKIP((size_t)(num) * sizeof(type));

/* read a binary blob into the buffer */
#define READ_BUF(fp, ptr, sz)                                               \
  /* check that there is enough room and read */                            \
  BUF_CHECK(fp, sz);                                                        \
  READ(fp, BUF_CUR, (size_t)sz);                                            \
  /* store pointer and skip */                                              \
  (ptr) = BUF_CUR;                                                          \
  BUF_SKIP(sz);

/* read string in the buffer (using buffer, buflen and bufptr)
   and store the actual location of the string in field */
#define READ_BUF_STRING(fp, field)                                          \
  /* read the size of the string */                                         \
  READ(fp, &tmpint32, sizeof(int32_t));                                     \
  tmpint32 = ntohl(tmpint32);                                               \
  DEBUG_PRINT("READ_BUF_STRING: var="__STRING(field)" strlen=%d", tmpint32); \
  /* check if read would fit */                                             \
  BUF_CHECK(fp, tmpint32 + 1);                                              \
  /* read string from the stream */                                         \
  if (tmpint32 > 0)                                                         \
  {                                                                         \
    READ(fp, BUF_CUR, (size_t)tmpint32);                                    \
  }                                                                         \
  /* null-terminate string in buffer */                                     \
  BUF_CUR[tmpint32] = '\0';                                                 \
  DEBUG_PRINT("READ_BUF_STRING: var="__STRING(field)" string=\"%s\"", BUF_CUR); \
  /* prepare result */                                                      \
  (field) = BUF_CUR;                                                        \
  BUF_SKIP(tmpint32 + 1);

/* read an array from a stram and store it as a null-terminated
   array list (size for the array is allocated) */
#define READ_BUF_STRINGLIST(fp, arr)                                        \
  /* read the number of entries */                                          \
  READ(fp, &tmp3int32, sizeof(int32_t));                                    \
  tmp3int32 = ntohl(tmp3int32);                                             \
  DEBUG_PRINT("READ_STRLST: var="__STRING(arr)" num=%d", (int)tmp3int32);   \
  /* allocate room for *char[num + 1] */                                      \
  BUF_ALLOC(fp, arr, char *, tmp3int32 + 1);                                \
  /* read all entries */                                                    \
  for (tmp2int32 = 0; tmp2int32 < tmp3int32; tmp2int32++)                   \
  {                                                                         \
    READ_BUF_STRING(fp, (arr)[tmp2int32]);                                  \
  }                                                                         \
  /* set last entry to NULL */                                              \
  (arr)[tmp2int32] = NULL;


/* SKIP macros for skipping over certain parts of the protocol stream. */

/* skip a number of bytes foreward */
#define SKIP(fp, sz)                                                        \
  DEBUG_PRINT("READ       : skip %d bytes", (int)(sz));                     \
  /* read (skip) the specified number of bytes */                           \
  if (tio_skip(fp, sz))                                                     \
  {                                                                         \
    DEBUG_PRINT("READ       : skip error: %s", strerror(errno));            \
    ERROR_OUT_READERROR(fp);                                                \
  }

/* read a string from the stream but don't do anything with the result */
#define SKIP_STRING(fp)                                                     \
  /* read the size of the string */                                         \
  READ(fp, &tmpint32, sizeof(int32_t));                                     \
  tmpint32 = ntohl(tmpint32);                                               \
  DEBUG_PRINT("READ_STRING: skip %d bytes", (int)tmpint32);                 \
  /* read (skip) the specified number of bytes */                           \
  SKIP(fp, tmpint32);

/* skip a list of strings */
#define SKIP_STRINGLIST(fp)                                                 \
  /* read the number of entries */                                          \
  READ(fp, &tmp3int32, sizeof(int32_t));                                    \
  tmp3int32 = ntohl(tmp3int32);                                             \
  DEBUG_PRINT("READ_STRLST: skip %d strings", (int)tmp3int32);              \
  /* read all entries */                                                    \
  for (tmp2int32 = 0; tmp2int32 < tmp3int32; tmp2int32++)                   \
  {                                                                         \
    SKIP_STRING(fp);                                                        \
  }


/* These are functions and macors for performing common operations in
   the nslcd request/response protocol. */

/* returns a socket to the server or NULL on error (see errno),
   socket should be closed with tio_close() */
TFILE *nslcd_client_open(void)
  MUST_USE;

/* generic request code */
#define NSLCD_REQUEST(fp, action, writefn)                                  \
  /* open a client socket */                                                \
  if ((fp = nslcd_client_open()) == NULL)                                   \
  {                                                                         \
    ERROR_OUT_OPENERROR;                                                    \
  }                                                                         \
  /* write a request header with a request code */                          \
  WRITE_INT32(fp, (int32_t)NSLCD_VERSION)                                   \
  WRITE_INT32(fp, (int32_t)action)                                          \
  /* write the request parameters (if any) */                               \
  writefn;                                                                  \
  /* flush the stream */                                                    \
  if (tio_flush(fp) < 0)                                                    \
  {                                                                         \
    DEBUG_PRINT("WRITE_FLUSH : error: %s", strerror(errno));                \
    ERROR_OUT_WRITEERROR(fp);                                               \
  }                                                                         \
  /* read and check response version number */                              \
  READ(fp, &tmpint32, sizeof(int32_t));                                     \
  tmpint32 = ntohl(tmpint32);                                               \
  if (tmpint32 != (int32_t)NSLCD_VERSION)                                   \
  {                                                                         \
    ERROR_OUT_READERROR(fp);                                                \
  }                                                                         \
  /* read and check response request number */                              \
  READ(fp, &tmpint32, sizeof(int32_t));                                     \
  tmpint32 = ntohl(tmpint32);                                               \
  if (tmpint32 != (int32_t)(action))                                        \
  {                                                                         \
    ERROR_OUT_READERROR(fp);                                                \
  }

/* Read the response code (the result code of the query) from
   the stream. */
#define READ_RESPONSE_CODE(fp)                                              \
  READ(fp, &tmpint32, sizeof(int32_t));                                     \
  tmpint32 = ntohl(tmpint32);                                               \
  if (tmpint32 != (int32_t)NSLCD_RESULT_BEGIN)                              \
  {                                                                         \
    ERROR_OUT_NOSUCCESS(fp);                                                \
  }

#endif /* not COMMON__NSLCD_PROT_H */
