/*
 * Example code for encoding and decoding large amounts of data in a PPD file.
 * This would typically be used in a driver to save configuration/state
 * information that could be used by an application.
 *
 * Copyright © 2020-2024 by OpenPrinting.
 * Copyright 2012 by Apple Inc.
 *
 * Licensed under Apache License v2.0.  See the file "LICENSE" for more information.
 */

/*
 * Include necessary headers...
 */

#include "ppdx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zlib.h>			/* For compression of the data */


/*
 * Constants...
 */

#define PPDX_MAX_VALUE	(PPD_MAX_LINE - PPD_MAX_NAME - 4)
					/* Max value length with delimiters + nul */
#define PPDX_MAX_CHUNK	(PPDX_MAX_VALUE * 3 / 4)
					/* Max length of each chunk when Base64-encoded */


/*
 * 'ppdxReadData()' - Read encoded data from a ppd_file_t *.
 *
 * Reads chunked data in the PPD file "ppd" using the prefix "name".  Returns
 * an allocated pointer to the data (which is nul-terminated for convenience)
 * along with the length of the data in the variable pointed to by "datasize",
 * which can be NULL to indicate the caller doesn't need the length.
 *
 * Returns NULL if no data is present in the PPD with the prefix.
 */

void *					/* O - Data or NULL */
ppdxReadData(ppd_file_t *ppd,		/* I - PPD file */
             const char *name,		/* I - Keyword prefix */
             size_t     *datasize)	/* O - Size of data or NULL for don't care */
{
  char		keyword[PPD_MAX_NAME],	/* Keyword name */
		decoded[PPDX_MAX_CHUNK + 1];
					/* Decoded string */
  unsigned	chunk = 0;		/* Current chunk number */
  int		len;			/* Length of current chunk */
  ppd_attr_t	*attr;			/* Keyword/value from PPD file */
  Bytef		*data;			/* Pointer to data */
  size_t	alloc_size;		/* Allocated size of data buffer */
  z_stream	decomp;			/* Decompressor stream */
  int		error;			/* Error/status from inflate() */


 /*
  * Range check input...
  */

  if (datasize)
    *datasize = 0;

  if (!ppd || !name)
    return (NULL);

 /*
  * First see if there are any instances of the named keyword in the PPD...
  */

  snprintf(keyword, sizeof(keyword), "%s%04x", name, chunk);
  if ((attr = ppdFindAttr(ppd, keyword, NULL)) == NULL)
    return (NULL);

 /*
  * Allocate some memory and start decoding...
  */

  data       = malloc(257);
  alloc_size = 256;

  memset(&decomp, 0, sizeof(decomp));
  decomp.next_out  = data;
  decomp.avail_out = 256;

  inflateInit(&decomp);

  do
  {
   /*
    * Grab the data from the current attribute and decode it...
    */

    len = sizeof(decoded);
    if (!httpDecode64_2(decoded, &len, attr->value) || len == 0)
      break;

//    printf("chunk %04x has length %d\n", chunk, len);

   /*
    * Decompress this chunk...
    */

    decomp.next_in  = decoded;
    decomp.avail_in = len;

    do
    {
      Bytef	*temp;			/* Temporary pointer */
      size_t	temp_size;		/* Temporary allocation size */

//      printf("Before inflate: avail_in=%d, avail_out=%d\n", decomp.avail_in,
//             decomp.avail_out);

      if ((error = inflate(&decomp, Z_NO_FLUSH)) < Z_OK)
      {
        fprintf(stderr, "ERROR: inflate returned %d (%s)\n", error, decomp.msg);
        break;
      }

//      printf("After inflate: avail_in=%d, avail_out=%d, error=%d\n",
//             decomp.avail_in, decomp.avail_out, error);

      if (decomp.avail_out == 0)
      {
	if (alloc_size < 2048)
	  temp_size = alloc_size * 2;
	else if (alloc_size < PPDX_MAX_DATA)
	  temp_size = alloc_size + 2048;
	else
	  break;

	if ((temp = realloc(data, temp_size + 1)) == NULL)
	{
	  free(data);
	  return (NULL);
	}

	decomp.next_out  = temp + (decomp.next_out - data);
	decomp.avail_out = temp_size - alloc_size;
	data             = temp;
	alloc_size       = temp_size;
      }
    }
    while (decomp.avail_in > 0);

    chunk ++;
    snprintf(keyword, sizeof(keyword), "%s%04x", name, chunk);
  }
  while ((attr = ppdFindAttr(ppd, keyword, NULL)) != NULL);

  inflateEnd(&decomp);

 /*
  * Nul-terminate the data (usually a string)...
  */

  *(decomp.next_out) = '\0';

  if (datasize)
    *datasize = decomp.next_out - data;

  return (data);
}


/*
 * 'ppdxWriteData()' - Writes encoded data to stderr using PPD: messages.
 *
 * Writes chunked data to the PPD file using PPD: messages sent to stderr for
 * cupsd.  "name" must be a valid PPD keyword string whose length is less than
 * 37 characters to allow for chunk numbering.  "data" provides a pointer to the
 * data to be written, and "datasize" provides the length.
 */

extern void
ppdxWriteData(const char *name,		/* I - Base name of keyword */
              const void *data,		/* I - Data to write */
              size_t     datasize)	/* I - Number of bytes in data */
{
  char		buffer[PPDX_MAX_CHUNK],	/* Chunk buffer */
		encoded[PPDX_MAX_VALUE + 1],
					/* Encoded data */
		pair[PPD_MAX_LINE],	/* name=value pair */
		line[PPDX_MAX_STATUS],	/* Line buffer */
		*lineptr,		/* Current position in line buffer */
		*lineend;		/* End of line buffer */
  unsigned	chunk = 0;		/* Current chunk number */
  int		len;			/* Length of current chunk */
  z_stream	comp;			/* Compressor stream */
  int		error;			/* Error/status from deflate() */


 /*
  * Range check input...
  */

  if (!name || (!data && datasize > 0) || datasize > PPDX_MAX_DATA)
    return;

  strlcpy(line, "PPD:", sizeof(line));
  lineptr = line + 4;
  lineend = line + sizeof(line) - 2;

  if (datasize > 0)
  {
   /*
    * Compress and encode output...
    */

    memset(&comp, 0, sizeof(comp));
    comp.next_in   = (Bytef *)data;
    comp.avail_in  = datasize;

    deflateInit(&comp, 9);

    do
    {
     /*
      * Compress a chunk...
      */

      comp.next_out  = buffer;
      comp.avail_out = sizeof(buffer);

      if ((error = deflate(&comp, Z_FINISH)) < Z_OK)
      {
        fprintf(stderr, "ERROR: deflate returned %d (%s)\n", error, comp.msg);
        break;
      }

     /*
      * Write a chunk...
      */

      len = sizeof(buffer) - comp.avail_out;
      httpEncode64_2(encoded, sizeof(encoded), buffer, len);

      len = (int)snprintf(pair, sizeof(pair), " %s%04x=%s", name, chunk,
			  encoded);
#ifdef DEBUG
      fprintf(stderr, "DEBUG: *%s%04x: \"%s\"\n", name, chunk, encoded);
#endif /* DEBUG */

      if ((lineptr + len) >= lineend)
      {
	*lineptr++ = '\n';
	*lineptr   = '\0';

	fputs(line, stderr);
	lineptr = line + 4;
      }

      strlcpy(lineptr, pair, lineend - lineptr);
      lineptr += len;

     /*
      * Setup for the next one...
      */

      chunk ++;
    }
    while (comp.avail_out == 0);
  }

  deflateEnd(&comp);

 /*
  * Write a trailing empty chunk to signal EOD...
  */

  len = (int)snprintf(pair, sizeof(pair), " %s%04x=\"\"", name, chunk);
#ifdef DEBUG
  fprintf(stderr, "DEBUG: *%s%04x: \"\"\n", name, chunk);
#endif /* DEBUG */

  if ((lineptr + len) >= lineend)
  {
    *lineptr++ = '\n';
    *lineptr   = '\0';

    fputs(line, stderr);
    lineptr = line + 4;
  }

  strlcpy(lineptr, pair, lineend - lineptr);
  lineptr += len;

  *lineptr++ = '\n';
  *lineptr   = '\0';

  fputs(line, stderr);
}
