testipp.c   [plain text]


/*
 * "$Id: testipp.c 6649 2007-07-11 21:46:42Z mike $"
 *
 *   IPP test program for the Common UNIX Printing System (CUPS).
 *
 *   Copyright 2007-2010 by Apple Inc.
 *   Copyright 1997-2005 by Easy Software Products.
 *
 *   These coded instructions, statements, and computer programs are the
 *   property of Apple Inc. and are protected by Federal copyright
 *   law.  Distribution and use rights are outlined in the file "LICENSE.txt"
 *   which should have been included with this file.  If this file is
 *   file is missing or damaged, see the license at "http://www.cups.org/".
 *
 *   This file is subject to the Apple OS-Developed Software exception.
 *
 * Contents:
 *
 *   main()             - Main entry.
 *   hex_dump()         - Produce a hex dump of a buffer.
 *   print_attributes() - Print the attributes in a request...
 *   read_cb()          - Read data from a buffer.
 *   write_cb()         - Write data into a buffer.
 */

/*
 * Include necessary headers...
 */

#include <stdio.h>
#include <stdlib.h>
#include <cups/file.h>
#include <cups/string.h>
#include <errno.h>
#include "ipp-private.h"
#ifdef WIN32
#  include <io.h>
#else
#  include <unistd.h>
#  include <fcntl.h>
#endif /* WIN32 */


/*
 * Local globals...
 */

int		rpos;				/* Current position in buffer */
ipp_uchar_t	wbuffer[8192];			/* Write buffer */
int		wused;				/* Number of bytes in buffer */
ipp_uchar_t	collection[] =			/* Collection buffer */
		{
		  0x01, 0x01,			/* IPP version */
		  0x00, 0x02,			/* Print-Job operation */
		  0x00, 0x00, 0x00, 0x01,	/* Request ID */

		  IPP_TAG_OPERATION,

		  IPP_TAG_CHARSET,
		  0x00, 0x12,			/* Name length + name */
		  'a','t','t','r','i','b','u','t','e','s','-',
		  'c','h','a','r','s','e','t',
		  0x00, 0x05,			/* Value length + value */
		  'u','t','f','-','8',

		  IPP_TAG_LANGUAGE,
		  0x00, 0x1b,			/* Name length + name */
		  'a','t','t','r','i','b','u','t','e','s','-',
		  'n','a','t','u','r','a','l','-','l','a','n',
		  'g','u','a','g','e',
		  0x00, 0x02,			/* Value length + value */
		  'e','n',

		  IPP_TAG_URI,
		  0x00, 0x0b,			/* Name length + name */
		  'p','r','i','n','t','e','r','-','u','r','i',
		  0x00, 0x1c,			/* Value length + value */
		  'i','p','p',':','/','/','l','o','c','a','l',
		  'h','o','s','t','/','p','r','i','n','t','e',
		  'r','s','/','f','o','o',

		  IPP_TAG_JOB,			/* job group tag */

		  IPP_TAG_BEGIN_COLLECTION,	/* begCollection tag */
		  0x00, 0x09,			/* Name length + name */
		  'm', 'e', 'd', 'i', 'a', '-', 'c', 'o', 'l',
		  0x00, 0x00,			/* No value */
		    IPP_TAG_MEMBERNAME,		/* memberAttrName tag */
		    0x00, 0x00,			/* No name */
		    0x00, 0x0a,			/* Value length + value */
		    'm', 'e', 'd', 'i', 'a', '-', 's', 'i', 'z', 'e',
		    IPP_TAG_BEGIN_COLLECTION,	/* begCollection tag */
		    0x00, 0x00,			/* Name length + name */
		    0x00, 0x00,			/* No value */
		      IPP_TAG_MEMBERNAME,	/* memberAttrName tag */
		      0x00, 0x00,		/* No name */
		      0x00, 0x0b,		/* Value length + value */
		      'x', '-', 'd', 'i', 'm', 'e', 'n', 's', 'i', 'o', 'n',
		      IPP_TAG_INTEGER,		/* integer tag */
		      0x00, 0x00,		/* No name */
		      0x00, 0x04,		/* Value length + value */
		      0x00, 0x00, 0x54, 0x56,
		      IPP_TAG_MEMBERNAME,	/* memberAttrName tag */
		      0x00, 0x00,		/* No name */
		      0x00, 0x0b,		/* Value length + value */
		      'y', '-', 'd', 'i', 'm', 'e', 'n', 's', 'i', 'o', 'n',
		      IPP_TAG_INTEGER,		/* integer tag */
		      0x00, 0x00,		/* No name */
		      0x00, 0x04,		/* Value length + value */
		      0x00, 0x00, 0x6d, 0x24,
		    IPP_TAG_END_COLLECTION,	/* endCollection tag */
		    0x00, 0x00,			/* No name */
		    0x00, 0x00,			/* No value */
		    IPP_TAG_MEMBERNAME,		/* memberAttrName tag */
		    0x00, 0x00,			/* No name */
		    0x00, 0x0b,			/* Value length + value */
		    'm', 'e', 'd', 'i', 'a', '-', 'c', 'o', 'l', 'o', 'r',
		    IPP_TAG_KEYWORD,		/* keyword tag */
		    0x00, 0x00,			/* No name */
		    0x00, 0x04,			/* Value length + value */
		    'b', 'l', 'u', 'e',

		    IPP_TAG_MEMBERNAME,		/* memberAttrName tag */
		    0x00, 0x00,			/* No name */
		    0x00, 0x0a,			/* Value length + value */
		    'm', 'e', 'd', 'i', 'a', '-', 't', 'y', 'p', 'e',
		    IPP_TAG_KEYWORD,		/* keyword tag */
		    0x00, 0x00,			/* No name */
		    0x00, 0x05,			/* Value length + value */
		    'p', 'l', 'a', 'i', 'n',
		  IPP_TAG_END_COLLECTION,	/* endCollection tag */
		  0x00, 0x00,			/* No name */
		  0x00, 0x00,			/* No value */

		  IPP_TAG_BEGIN_COLLECTION,	/* begCollection tag */
		  0x00, 0x00,			/* No name */
		  0x00, 0x00,			/* No value */
		    IPP_TAG_MEMBERNAME,		/* memberAttrName tag */
		    0x00, 0x00,			/* No name */
		    0x00, 0x0a,			/* Value length + value */
		    'm', 'e', 'd', 'i', 'a', '-', 's', 'i', 'z', 'e',
		    IPP_TAG_BEGIN_COLLECTION,	/* begCollection tag */
		    0x00, 0x00,			/* Name length + name */
		    0x00, 0x00,			/* No value */
		      IPP_TAG_MEMBERNAME,	/* memberAttrName tag */
		      0x00, 0x00,		/* No name */
		      0x00, 0x0b,		/* Value length + value */
		      'x', '-', 'd', 'i', 'm', 'e', 'n', 's', 'i', 'o', 'n',
		      IPP_TAG_INTEGER,		/* integer tag */
		      0x00, 0x00,		/* No name */
		      0x00, 0x04,		/* Value length + value */
		      0x00, 0x00, 0x52, 0x08,
		      IPP_TAG_MEMBERNAME,	/* memberAttrName tag */
		      0x00, 0x00,		/* No name */
		      0x00, 0x0b,		/* Value length + value */
		      'y', '-', 'd', 'i', 'm', 'e', 'n', 's', 'i', 'o', 'n',
		      IPP_TAG_INTEGER,		/* integer tag */
		      0x00, 0x00,		/* No name */
		      0x00, 0x04,		/* Value length + value */
		      0x00, 0x00, 0x74, 0x04,
		    IPP_TAG_END_COLLECTION,	/* endCollection tag */
		    0x00, 0x00,			/* No name */
		    0x00, 0x00,			/* No value */
		    IPP_TAG_MEMBERNAME,		/* memberAttrName tag */
		    0x00, 0x00,			/* No name */
		    0x00, 0x0b,			/* Value length + value */
		    'm', 'e', 'd', 'i', 'a', '-', 'c', 'o', 'l', 'o', 'r',
		    IPP_TAG_KEYWORD,		/* keyword tag */
		    0x00, 0x00,			/* No name */
		    0x00, 0x05,			/* Value length + value */
		    'p', 'l', 'a', 'i', 'd',

		    IPP_TAG_MEMBERNAME,		/* memberAttrName tag */
		    0x00, 0x00,			/* No name */
		    0x00, 0x0a,			/* Value length + value */
		    'm', 'e', 'd', 'i', 'a', '-', 't', 'y', 'p', 'e',
		    IPP_TAG_KEYWORD,		/* keyword tag */
		    0x00, 0x00,			/* No name */
		    0x00, 0x06,			/* Value length + value */
		    'g', 'l', 'o', 's', 's', 'y',
		  IPP_TAG_END_COLLECTION,	/* endCollection tag */
		  0x00, 0x00,			/* No name */
		  0x00, 0x00,			/* No value */

		  IPP_TAG_END			/* end tag */
		};


/*
 * Local functions...
 */

void	hex_dump(const char *title, ipp_uchar_t *buffer, int bytes);
void	print_attributes(ipp_t *ipp, int indent);
ssize_t	read_cb(void *data, ipp_uchar_t *buffer, size_t bytes);
ssize_t	write_cb(void *data, ipp_uchar_t *buffer, size_t bytes);


/*
 * 'main()' - Main entry.
 */

int				/* O - Exit status */
main(int  argc,			/* I - Number of command-line arguments */
     char *argv[])		/* I - Command-line arguments */
{
  ipp_t		*cols[2],	/* Collections */
		*size;		/* media-size collection */
  ipp_t		*request;	/* Request */
  ipp_attribute_t *media_col,	/* media-col attribute */
		*media_size,	/* media-size attribute */
		*attr;		/* Other attribute */
  ipp_state_t	state;		/* State */
  int		length;		/* Length of data */
  cups_file_t	*fp;		/* File pointer */
  int		i;		/* Looping var */
  int		status;		/* Status of tests (0 = success, 1 = fail) */


  status = 0;

  if (argc == 1)
  {
   /*
    * Test request generation code...
    */

    printf("Create Sample Request: ");

    request = ippNew();
    request->request.op.version[0]   = 0x01;
    request->request.op.version[1]   = 0x01;
    request->request.op.operation_id = IPP_PRINT_JOB;
    request->request.op.request_id   = 1;

    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
        	 "attributes-charset", NULL, "utf-8");
    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
        	 "attributes-natural-language", NULL, "en");
    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
        	 "printer-uri", NULL, "ipp://localhost/printers/foo");

    cols[0] = ippNew();
    size    = ippNew();
    ippAddInteger(size, IPP_TAG_ZERO, IPP_TAG_INTEGER, "x-dimension", 21590);
    ippAddInteger(size, IPP_TAG_ZERO, IPP_TAG_INTEGER, "y-dimension", 27940);
    ippAddCollection(cols[0], IPP_TAG_JOB, "media-size", size);
    ippDelete(size);
    ippAddString(cols[0], IPP_TAG_JOB, IPP_TAG_KEYWORD, "media-color", NULL,
                 "blue");
    ippAddString(cols[0], IPP_TAG_JOB, IPP_TAG_KEYWORD, "media-type", NULL,
                 "plain");

    cols[1] = ippNew();
    size    = ippNew();
    ippAddInteger(size, IPP_TAG_ZERO, IPP_TAG_INTEGER, "x-dimension", 21000);
    ippAddInteger(size, IPP_TAG_ZERO, IPP_TAG_INTEGER, "y-dimension", 29700);
    ippAddCollection(cols[1], IPP_TAG_JOB, "media-size", size);
    ippDelete(size);
    ippAddString(cols[1], IPP_TAG_JOB, IPP_TAG_KEYWORD, "media-color", NULL,
                 "plaid");
    ippAddString(cols[1], IPP_TAG_JOB, IPP_TAG_KEYWORD, "media-type", NULL,
		 "glossy");

    ippAddCollections(request, IPP_TAG_JOB, "media-col", 2,
                      (const ipp_t **)cols);
    ippDelete(cols[0]);
    ippDelete(cols[1]);

    length = ippLength(request);
    if (length != sizeof(collection))
    {
      printf("FAIL - wrong ippLength(), %d instead of %d bytes!\n",
             length, (int)sizeof(collection));
      status = 1;
    }
    else
      puts("PASS");

   /*
    * Write test #1...
    */

    printf("Write Sample to Memory: ");

    wused = 0;
    while ((state = ippWriteIO(wbuffer, write_cb, 1, NULL,
                               request)) != IPP_DATA)
      if (state == IPP_ERROR)
	break;

    if (state != IPP_DATA)
    {
      printf("FAIL - %d bytes written.\n", wused);
      status = 1;
    }
    else if (wused != sizeof(collection))
    {
      printf("FAIL - wrote %d bytes, expected %d bytes!\n", wused,
             (int)sizeof(collection));
      hex_dump("Bytes Written", wbuffer, wused);
      hex_dump("Baseline", collection, sizeof(collection));
      status = 1;
    }
    else if (memcmp(wbuffer, collection, wused))
    {
      for (i = 0; i < wused; i ++)
        if (wbuffer[i] != collection[i])
	  break;

      printf("FAIL - output does not match baseline at 0x%04x!\n", i);
      hex_dump("Bytes Written", wbuffer, wused);
      hex_dump("Baseline", collection, sizeof(collection));
      status = 1;
    }
    else
      puts("PASS");

    ippDelete(request);

   /*
    * Read the data back in and confirm...
    */

    printf("Read Sample from Memory: ");

    request = ippNew();
    rpos    = 0;

    while ((state = ippReadIO(wbuffer, read_cb, 1, NULL, request)) != IPP_DATA)
      if (state == IPP_ERROR)
	break;

    length = ippLength(request);

    if (state != IPP_DATA)
    {
      printf("FAIL - %d bytes read.\n", rpos);
      status = 1;
    }
    else if (rpos != wused)
    {
      printf("FAIL - read %d bytes, expected %d bytes!\n", rpos, wused);
      print_attributes(request, 8);
      status = 1;
    }
    else if (length != sizeof(collection))
    {
      printf("FAIL - wrong ippLength(), %d instead of %d bytes!\n",
             length, (int)sizeof(collection));
      print_attributes(request, 8);
      status = 1;
    }
    else
      puts("PASS");

    fputs("ippFindAttribute(media-col): ", stdout);
    if ((media_col = ippFindAttribute(request, "media-col",
                                      IPP_TAG_BEGIN_COLLECTION)) == NULL)
    {
      if ((media_col = ippFindAttribute(request, "media-col",
                                        IPP_TAG_ZERO)) == NULL)
        puts("FAIL (not found)");
      else
        printf("FAIL (wrong type - %s)\n", ippTagString(media_col->value_tag));

      status = 1;
    }
    else if (media_col->num_values != 2)
    {
      printf("FAIL (wrong count - %d)\n", media_col->num_values);
      status = 1;
    }
    else
      puts("PASS");

    if (media_col)
    {
      fputs("ippFindAttribute(media-size 1): ", stdout);
      if ((media_size = ippFindAttribute(media_col->values[0].collection,
					 "media-size",
					 IPP_TAG_BEGIN_COLLECTION)) == NULL)
      {
	if ((media_size = ippFindAttribute(media_col->values[0].collection,
					   "media-col",
					   IPP_TAG_ZERO)) == NULL)
	  puts("FAIL (not found)");
	else
	  printf("FAIL (wrong type - %s)\n",
	         ippTagString(media_size->value_tag));

	status = 1;
      }
      else
      {
	if ((attr = ippFindAttribute(media_size->values[0].collection,
				     "x-dimension", IPP_TAG_INTEGER)) == NULL)
	{
	  if ((attr = ippFindAttribute(media_size->values[0].collection,
				       "x-dimension", IPP_TAG_ZERO)) == NULL)
	    puts("FAIL (missing x-dimension)");
	  else
	    printf("FAIL (wrong type for x-dimension - %s)\n",
		   ippTagString(attr->value_tag));

	  status = 1;
	}
	else if (attr->values[0].integer != 21590)
	{
	  printf("FAIL (wrong value for x-dimension - %d)\n",
		 attr->values[0].integer);
	  status = 1;
	}
	else if ((attr = ippFindAttribute(media_size->values[0].collection,
					  "y-dimension",
					  IPP_TAG_INTEGER)) == NULL)
	{
	  if ((attr = ippFindAttribute(media_size->values[0].collection,
				       "y-dimension", IPP_TAG_ZERO)) == NULL)
	    puts("FAIL (missing y-dimension)");
	  else
	    printf("FAIL (wrong type for y-dimension - %s)\n",
		   ippTagString(attr->value_tag));

	  status = 1;
	}
	else if (attr->values[0].integer != 27940)
	{
	  printf("FAIL (wrong value for y-dimension - %d)\n",
		 attr->values[0].integer);
	  status = 1;
	}
	else
	  puts("PASS");
      }

      fputs("ippFindAttribute(media-size 2): ", stdout);
      if ((media_size = ippFindAttribute(media_col->values[1].collection,
					 "media-size",
					 IPP_TAG_BEGIN_COLLECTION)) == NULL)
      {
	if ((media_size = ippFindAttribute(media_col->values[1].collection,
					   "media-col",
					   IPP_TAG_ZERO)) == NULL)
	  puts("FAIL (not found)");
	else
	  printf("FAIL (wrong type - %s)\n",
	         ippTagString(media_size->value_tag));

	status = 1;
      }
      else
      {
	if ((attr = ippFindAttribute(media_size->values[0].collection,
				     "x-dimension",
				     IPP_TAG_INTEGER)) == NULL)
	{
	  if ((attr = ippFindAttribute(media_size->values[0].collection,
				       "x-dimension", IPP_TAG_ZERO)) == NULL)
	    puts("FAIL (missing x-dimension)");
	  else
	    printf("FAIL (wrong type for x-dimension - %s)\n",
		   ippTagString(attr->value_tag));

	  status = 1;
	}
	else if (attr->values[0].integer != 21000)
	{
	  printf("FAIL (wrong value for x-dimension - %d)\n",
		 attr->values[0].integer);
	  status = 1;
	}
	else if ((attr = ippFindAttribute(media_size->values[0].collection,
					  "y-dimension",
					  IPP_TAG_INTEGER)) == NULL)
	{
	  if ((attr = ippFindAttribute(media_size->values[0].collection,
				       "y-dimension", IPP_TAG_ZERO)) == NULL)
	    puts("FAIL (missing y-dimension)");
	  else
	    printf("FAIL (wrong type for y-dimension - %s)\n",
		   ippTagString(attr->value_tag));

	  status = 1;
	}
	else if (attr->values[0].integer != 29700)
	{
	  printf("FAIL (wrong value for y-dimension - %d)\n",
		 attr->values[0].integer);
	  status = 1;
	}
	else
	  puts("PASS");
      }
    }

    ippDelete(request);

   /*
    * Test _ippFindOption() private API...
    */

    fputs("_ippFindOption(printer-type): ", stdout);
    if (_ippFindOption("printer-type"))
      puts("PASS");
    else
    {
      puts("FAIL");
      status = 1;
    }

   /*
    * Summarize...
    */

    putchar('\n');

    if (status)
      puts("Core IPP tests failed.");
    else
      puts("Core IPP tests passed.");
  }
  else
  {
   /*
    * Read IPP files...
    */

    for (i = 1; i < argc; i ++)
    {
      if ((fp = cupsFileOpen(argv[i], "r")) == NULL)
      {
	printf("Unable to open \"%s\" - %s\n", argv[i], strerror(errno));
	status = 1;
	continue;
      }

      request = ippNew();
      while ((state = ippReadIO(fp, (ipp_iocb_t)cupsFileRead, 1, NULL,
                                request)) == IPP_ATTRIBUTE);

      if (state != IPP_DATA)
      {
	printf("Error reading IPP message from \"%s\"!\n", argv[i]);
	status = 1;
      }
      else
      {
	printf("\n%s:\n", argv[i]);
	print_attributes(request, 4);
      }

      ippDelete(request);
      cupsFileClose(fp);
    }
  }

  return (status);
}


/*
 * 'hex_dump()' - Produce a hex dump of a buffer.
 */

void
hex_dump(const char  *title,		/* I - Title */
         ipp_uchar_t *buffer,		/* I - Buffer to dump */
         int         bytes)		/* I - Number of bytes */
{
  int	i, j;				/* Looping vars */
  int	ch;				/* Current ASCII char */


 /*
  * Show lines of 16 bytes at a time...
  */

  printf("    %s:\n", title);

  for (i = 0; i < bytes; i += 16)
  {
   /*
    * Show the offset...
    */

    printf("    %04x ", i);

   /*
    * Then up to 16 bytes in hex...
    */

    for (j = 0; j < 16; j ++)
      if ((i + j) < bytes)
        printf(" %02x", buffer[i + j]);
      else
        printf("   ");

   /*
    * Then the ASCII representation of the bytes...
    */

    putchar(' ');
    putchar(' ');

    for (j = 0; j < 16 && (i + j) < bytes; j ++)
    {
      ch = buffer[i + j] & 127;

      if (ch < ' ' || ch == 127)
        putchar('.');
      else
        putchar(ch);
    }

    putchar('\n');
  }
}


/*
 * 'print_attributes()' - Print the attributes in a request...
 */

void
print_attributes(ipp_t *ipp,		/* I - IPP request */
                 int   indent)		/* I - Indentation */
{
  int			i;		/* Looping var */
  ipp_tag_t		group;		/* Current group */
  ipp_attribute_t	*attr;		/* Current attribute */
  ipp_value_t		*val;		/* Current value */
  static const char * const tags[] =	/* Value/group tag strings */
			{
			  "reserved-00",
			  "operation-attributes-tag",
			  "job-attributes-tag",
			  "end-of-attributes-tag",
			  "printer-attributes-tag",
			  "unsupported-attributes-tag",
			  "subscription-attributes-tag",
			  "event-attributes-tag",
			  "reserved-08",
			  "reserved-09",
			  "reserved-0A",
			  "reserved-0B",
			  "reserved-0C",
			  "reserved-0D",
			  "reserved-0E",
			  "reserved-0F",
			  "unsupported",
			  "default",
			  "unknown",
			  "no-value",
			  "reserved-14",
			  "not-settable",
			  "delete-attr",
			  "admin-define",
			  "reserved-18",
			  "reserved-19",
			  "reserved-1A",
			  "reserved-1B",
			  "reserved-1C",
			  "reserved-1D",
			  "reserved-1E",
			  "reserved-1F",
			  "reserved-20",
			  "integer",
			  "boolean",
			  "enum",
			  "reserved-24",
			  "reserved-25",
			  "reserved-26",
			  "reserved-27",
			  "reserved-28",
			  "reserved-29",
			  "reserved-2a",
			  "reserved-2b",
			  "reserved-2c",
			  "reserved-2d",
			  "reserved-2e",
			  "reserved-2f",
			  "octetString",
			  "dateTime",
			  "resolution",
			  "rangeOfInteger",
			  "begCollection",
			  "textWithLanguage",
			  "nameWithLanguage",
			  "endCollection",
			  "reserved-38",
			  "reserved-39",
			  "reserved-3a",
			  "reserved-3b",
			  "reserved-3c",
			  "reserved-3d",
			  "reserved-3e",
			  "reserved-3f",
			  "reserved-40",
			  "textWithoutLanguage",
			  "nameWithoutLanguage",
			  "reserved-43",
			  "keyword",
			  "uri",
			  "uriScheme",
			  "charset",
			  "naturalLanguage",
			  "mimeMediaType",
			  "memberName"
			};


  for (group = IPP_TAG_ZERO, attr = ipp->attrs; attr; attr = attr->next)
  {
    if (!attr->name && indent == 4)
    {
      group = IPP_TAG_ZERO;
      putchar('\n');
      continue;
    }

    if (group != attr->group_tag)
    {
      group = attr->group_tag;

      printf("\n%*s%s:\n\n", indent - 4, "", tags[group]);
    }

    printf("%*s%s (", indent, "", attr->name ? attr->name : "(null)");
    if (attr->num_values > 1)
      printf("1setOf ");
    printf("%s):", tags[attr->value_tag]);

    switch (attr->value_tag)
    {
      case IPP_TAG_ENUM :
      case IPP_TAG_INTEGER :
          for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
	    printf(" %d", val->integer);
          putchar('\n');
          break;

      case IPP_TAG_BOOLEAN :
          for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
	    printf(" %s", val->boolean ? "true" : "false");
          putchar('\n');
          break;

      case IPP_TAG_RANGE :
          for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
	    printf(" %d-%d", val->range.lower, val->range.upper);
          putchar('\n');
          break;

      case IPP_TAG_DATE :
          {
	    time_t	vtime;		/* Date/Time value */
	    struct tm	*vdate;		/* Date info */
	    char	vstring[256];	/* Formatted time */

	    for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
	    {
	      vtime = ippDateToTime(val->date);
	      vdate = localtime(&vtime);
	      strftime(vstring, sizeof(vstring), "%c", vdate);
	      printf(" (%s)", vstring);
	    }
          }
          putchar('\n');
          break;

      case IPP_TAG_RESOLUTION :
          for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
	    printf(" %dx%d%s", val->resolution.xres, val->resolution.yres,
	           val->resolution.units == IPP_RES_PER_INCH ? "dpi" : "dpc");
          putchar('\n');
          break;

      case IPP_TAG_STRING :
      case IPP_TAG_TEXTLANG :
      case IPP_TAG_NAMELANG :
      case IPP_TAG_TEXT :
      case IPP_TAG_NAME :
      case IPP_TAG_KEYWORD :
      case IPP_TAG_URI :
      case IPP_TAG_URISCHEME :
      case IPP_TAG_CHARSET :
      case IPP_TAG_LANGUAGE :
      case IPP_TAG_MIMETYPE :
          for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
	    printf(" \"%s\"", val->string.text);
          putchar('\n');
          break;

      case IPP_TAG_BEGIN_COLLECTION :
          putchar('\n');

          for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
	  {
	    if (i)
	      putchar('\n');
	    print_attributes(val->collection, indent + 4);
	  }
          break;

      default :
          printf("UNKNOWN (%d values)\n", attr->num_values);
          break;
    }
  }
}


/*
 * 'read_cb()' - Read data from a buffer.
 */

ssize_t					/* O - Number of bytes read */
read_cb(void        *data,		/* I - Data */
        ipp_uchar_t *buffer,		/* O - Buffer to read */
	size_t      bytes)		/* I - Number of bytes to read */
{
  int	count;				/* Number of bytes */


 /*
  * Copy bytes from the data buffer to the read buffer...
  */

  for (count = bytes; count > 0 && rpos < wused; count --, rpos ++)
    *buffer++ = wbuffer[rpos];

 /*
  * Return the number of bytes read...
  */

  return (bytes - count);
}


/*
 * 'write_cb()' - Write data into a buffer.
 */

ssize_t					/* O - Number of bytes written */
write_cb(void        *data,		/* I - Data */
         ipp_uchar_t *buffer,		/* I - Buffer to write */
	 size_t      bytes)		/* I - Number of bytes to write */
{
  int	count;				/* Number of bytes */


 /*
  * Loop until all bytes are written...
  */

  for (count = bytes; count > 0 && wused < sizeof(wbuffer); count --, wused ++)
    wbuffer[wused] = *buffer++;

 /*
  * Return the number of bytes written...
  */

  return (bytes - count);
}


/*
 * End of "$Id: testipp.c 6649 2007-07-11 21:46:42Z mike $".
 */