Bump to cups 2.3.3
[platform/upstream/cups.git] / cups / testclient.c
1 /*
2  * Simulated client test program for CUPS.
3  *
4  * Copyright © 2017-2019 by Apple Inc.
5  *
6  * Licensed under Apache License v2.0.  See the file "LICENSE" for more
7  * information.
8  */
9
10 /*
11  * Include necessary headers...
12  */
13
14 #include <config.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <cups/cups.h>
18 #include <cups/raster.h>
19 #include <cups/string-private.h>
20 #include <cups/thread-private.h>
21
22
23 /*
24  * Constants...
25  */
26
27 #define MAX_CLIENTS     16              /* Maximum number of client threads */
28
29
30 /*
31  * Local types...
32  */
33
34 typedef struct _client_data_s
35 {
36   const char            *uri,           /* Printer URI */
37                         *hostname,      /* Hostname */
38                         *user,          /* Username */
39                         *resource;      /* Resource path */
40   int                   port;           /* Port number */
41   http_encryption_t     encryption;     /* Use encryption? */
42   const char            *docfile,       /* Document file */
43                         *docformat;     /* Document format */
44   int                   grayscale,      /* Force grayscale? */
45                         keepfile;       /* Keep temporary file? */
46   ipp_pstate_t          printer_state;  /* Current printer state */
47   char                  printer_state_reasons[1024];
48                                         /* Current printer-state-reasons */
49   int                   job_id;         /* Job ID for submitted job */
50   ipp_jstate_t          job_state;      /* Current job state */
51   char                  job_state_reasons[1024];
52                                         /* Current job-state-reasons */
53 } _client_data_t;
54
55
56 /*
57  * Local globals...
58  */
59
60 static int              client_count = 0;
61 static _cups_mutex_t    client_mutex = _CUPS_MUTEX_INITIALIZER;
62 static int              verbosity = 0;
63
64
65 /*
66  * Local functions...
67  */
68
69 static const char       *make_raster_file(ipp_t *response, int grayscale, char *tempname, size_t tempsize, const char **format);
70 static void             *monitor_printer(_client_data_t *data);
71 static void             *run_client(_client_data_t *data);
72 static void             show_attributes(const char *title, int request, ipp_t *ipp);
73 static void             show_capabilities(ipp_t *response);
74 static void             usage(void);
75
76
77 /*
78  * 'main()' - Main entry.
79  */
80
81 int                                     /* O - Exit status */
82 main(int  argc,                         /* I - Number of command-line arguments */
83      char *argv[])                      /* I - Command-line arguments */
84 {
85   int                   i;              /* Looping var */
86   const char            *opt;           /* Current option */
87   int                   num_clients = 0,/* Number of clients to simulate */
88                         clients_started = 0;
89                                         /* Number of clients that have been started */
90   char                  scheme[32],     /* URI scheme */
91                         userpass[256],  /* Username:password */
92                         hostname[256],  /* Hostname */
93                         resource[256];  /* Resource path */
94   _client_data_t        data;           /* Client data */
95
96
97  /*
98   * Parse command-line options...
99   */
100
101   if (argc == 1)
102     return (0);
103
104   memset(&data, 0, sizeof(data));
105
106   for (i = 1; i < argc; i ++)
107   {
108     if (argv[i][0] == '-')
109     {
110       for (opt = argv[i] + 1; *opt; opt ++)
111       {
112         switch (*opt)
113         {
114           case 'c' : /* -c num-clients */
115               if (num_clients)
116               {
117                 puts("Number of clients can only be specified once.");
118                 usage();
119                 return (1);
120               }
121
122               i ++;
123               if (i >= argc)
124               {
125                 puts("Expected client count after '-c'.");
126                 usage();
127                 return (1);
128               }
129
130               if ((num_clients = atoi(argv[i])) < 1)
131               {
132                 puts("Number of clients must be one or more.");
133                 usage();
134                 return (1);
135               }
136               break;
137
138           case 'd' : /* -d document-format */
139               if (data.docformat)
140               {
141                 puts("Document format can only be specified once.");
142                 usage();
143                 return (1);
144               }
145
146               i ++;
147               if (i >= argc)
148               {
149                 puts("Expected document format after '-d'.");
150                 usage();
151                 return (1);
152               }
153
154               data.docformat = argv[i];
155               break;
156
157           case 'f' : /* -f print-file */
158               if (data.docfile)
159               {
160                 puts("Print file can only be specified once.");
161                 usage();
162                 return (1);
163               }
164
165               i ++;
166               if (i >= argc)
167               {
168                 puts("Expected print file after '-f'.");
169                 usage();
170                 return (1);
171               }
172
173               data.docfile = argv[i];
174               break;
175
176           case 'g' :
177               data.grayscale = 1;
178               break;
179
180           case 'k' :
181               data.keepfile = 1;
182               break;
183
184           case 'v' :
185               verbosity ++;
186               break;
187
188           default :
189               printf("Unknown option '-%c'.\n", *opt);
190               usage();
191               return (1);
192         }
193       }
194     }
195     else if (data.uri || (strncmp(argv[i], "ipp://", 6) && strncmp(argv[i], "ipps://", 7)))
196     {
197       printf("Unknown command-line argument '%s'.\n", argv[i]);
198       usage();
199       return (1);
200     }
201     else
202       data.uri = argv[i];
203   }
204
205  /*
206   * Make sure we have everything we need.
207   */
208
209   if (!data.uri)
210   {
211     puts("Expected printer URI.");
212     usage();
213     return (1);
214   }
215
216   if (num_clients < 1)
217     num_clients = 1;
218
219  /*
220   * Connect to the printer...
221   */
222
223   if (httpSeparateURI(HTTP_URI_CODING_ALL, data.uri, scheme, sizeof(scheme), userpass, sizeof(userpass), hostname, sizeof(hostname), &data.port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK)
224   {
225     printf("Bad printer URI '%s'.\n", data.uri);
226     return (1);
227   }
228
229   if (!data.port)
230     data.port = IPP_PORT;
231
232   if (!strcmp(scheme, "https") || !strcmp(scheme, "ipps"))
233     data.encryption = HTTP_ENCRYPTION_ALWAYS;
234   else
235     data.encryption = HTTP_ENCRYPTION_IF_REQUESTED;
236
237  /*
238   * Start the client threads...
239   */
240
241   data.hostname = hostname;
242   data.resource = resource;
243
244   while (clients_started < num_clients)
245   {
246     _cupsMutexLock(&client_mutex);
247     if (client_count < MAX_CLIENTS)
248     {
249       _cups_thread_t    tid;            /* New thread */
250
251       client_count ++;
252       _cupsMutexUnlock(&client_mutex);
253       tid = _cupsThreadCreate((_cups_thread_func_t)run_client, &data);
254       _cupsThreadDetach(tid);
255     }
256     else
257     {
258       _cupsMutexUnlock(&client_mutex);
259       sleep(1);
260     }
261   }
262
263   while (client_count > 0)
264   {
265     _cupsMutexLock(&client_mutex);
266     printf("%d RUNNING CLIENTS\n", client_count);
267     _cupsMutexUnlock(&client_mutex);
268     sleep(1);
269   }
270
271   return (0);
272 }
273
274
275 /*
276  * 'make_raster_file()' - Create a temporary raster file.
277  */
278
279 static const char *                     /* O - Print filename */
280 make_raster_file(ipp_t      *response,  /* I - Printer attributes */
281                  int        grayscale,  /* I - Force grayscale? */
282                  char       *tempname,  /* I - Temporary filename buffer */
283                  size_t     tempsize,   /* I - Size of temp file buffer */
284                  const char **format)   /* O - Print format */
285 {
286   int                   i,              /* Looping var */
287                         count;          /* Number of values */
288   ipp_attribute_t       *attr;          /* Printer attribute */
289   const char            *type = NULL;   /* Raster type (colorspace + bits) */
290   pwg_media_t           *media = NULL;  /* Media size */
291   int                   xdpi = 0,       /* Horizontal resolution */
292                         ydpi = 0;       /* Vertical resolution */
293   int                   fd;             /* Temporary file */
294   cups_mode_t           mode;           /* Raster mode */
295   cups_raster_t         *ras;           /* Raster stream */
296   cups_page_header2_t   header;         /* Page header */
297   unsigned char         *line,          /* Line of raster data */
298                         *lineptr;       /* Pointer into line */
299   unsigned              y,              /* Current position on page */
300                         xcount, ycount, /* Current count for X and Y */
301                         xrep, yrep,     /* Repeat count for X and Y */
302                         xoff, yoff,     /* Offsets for X and Y */
303                         yend;           /* End Y value */
304   int                   temprow,        /* Row in template */
305                         tempcolor;      /* Template color */
306   const char            *template;      /* Pointer into template */
307   const unsigned char   *color;         /* Current color */
308   static const unsigned char colors[][3] =
309   {                                     /* Colors for test */
310     { 191, 191, 191 },
311     { 127, 127, 127 },
312     {  63,  63,  63 },
313     {   0,   0,   0 },
314     { 255,   0,   0 },
315     { 255, 127,   0 },
316     { 255, 255,   0 },
317     { 127, 255,   0 },
318     {   0, 255,   0 },
319     {   0, 255, 127 },
320     {   0, 255, 255 },
321     {   0, 127, 255 },
322     {   0,   0, 255 },
323     { 127,   0, 255 },
324     { 255,   0, 255 }
325   };
326   static const char * const templates[] =
327   {                                     /* Raster template */
328     " CCC   U   U  PPPP    SSS          TTTTT  EEEEE   SSS   TTTTT          000     1     222    333      4   55555   66    77777   888    999   ",
329     "C   C  U   U  P   P  S   S           T    E      S   S    T           0   0   11    2   2  3   3  4  4   5      6          7  8   8  9   9  ",
330     "C      U   U  P   P  S               T    E      S        T           0   0    1        2      3  4  4   5      6         7   8   8  9   9  ",
331     "C      U   U  PPPP    SSS   -----    T    EEEE    SSS     T           0 0 0    1      22    333   44444   555   6666      7    888    9999  ",
332     "C      U   U  P          S           T    E          S    T           0   0    1     2         3     4       5  6   6    7    8   8      9  ",
333     "C   C  U   U  P      S   S           T    E      S   S    T           0   0    1    2      3   3     4   5   5  6   6    7    8   8      9  ",
334     " CCC    UUU   P       SSS            T    EEEEE   SSS     T            000    111   22222   333      4    555    666     7     888     99   ",
335     "                                                                                                                                            "
336   };
337
338
339  /*
340   * Figure out the output format...
341   */
342
343   if ((attr = ippFindAttribute(response, "document-format-supported", IPP_TAG_MIMETYPE)) == NULL)
344   {
345     puts("No supported document formats, aborting.");
346     return (NULL);
347   }
348
349   if (*format)
350   {
351     if (!ippContainsString(attr, *format))
352     {
353       printf("Printer does not support document-format '%s'.\n", *format);
354       return (NULL);
355     }
356
357     if (!strcmp(*format, "image/urf"))
358       mode = CUPS_RASTER_WRITE_APPLE;
359     else if (!strcmp(*format, "image/pwg-raster"))
360       mode = CUPS_RASTER_WRITE_PWG;
361     else
362     {
363       printf("Unable to generate document-format '%s'.\n", *format);
364       return (NULL);
365     }
366   }
367   else if (ippContainsString(attr, "image/urf"))
368   {
369    /*
370     * Apple Raster format...
371     */
372
373     *format = "image/urf";
374     mode    = CUPS_RASTER_WRITE_APPLE;
375   }
376   else if (ippContainsString(attr, "image/pwg-raster"))
377   {
378    /*
379     * PWG Raster format...
380     */
381
382     *format = "image/pwg-raster";
383     mode    = CUPS_RASTER_WRITE_PWG;
384   }
385   else
386   {
387    /*
388     * No supported raster format...
389     */
390
391     puts("Printer does not support Apple or PWG raster files, aborting.");
392     return (NULL);
393   }
394
395  /*
396   * Figure out the the media, resolution, and color mode...
397   */
398
399   if ((attr = ippFindAttribute(response, "media-default", IPP_TAG_KEYWORD)) != NULL)
400   {
401    /*
402     * Use default media...
403     */
404
405     media = pwgMediaForPWG(ippGetString(attr, 0, NULL));
406   }
407   else if ((attr = ippFindAttribute(response, "media-ready", IPP_TAG_KEYWORD)) != NULL)
408   {
409    /*
410     * Use ready media...
411     */
412
413     if (ippContainsString(attr, "na_letter_8.5x11in"))
414       media = pwgMediaForPWG("na_letter_8.5x11in");
415     else if (ippContainsString(attr, "iso_a4_210x297mm"))
416       media = pwgMediaForPWG("iso_a4_210x297mm");
417     else
418       media = pwgMediaForPWG(ippGetString(attr, 0, NULL));
419   }
420   else
421   {
422     puts("No default or ready media reported by printer, aborting.");
423     return (NULL);
424   }
425
426   if (mode == CUPS_RASTER_WRITE_APPLE && (attr = ippFindAttribute(response, "urf-supported", IPP_TAG_KEYWORD)) != NULL)
427   {
428     for (i = 0, count = ippGetCount(attr); i < count; i ++)
429     {
430       const char *val = ippGetString(attr, i, NULL);
431
432       if (!strncmp(val, "RS", 2))
433         xdpi = ydpi = atoi(val + 2);
434       else if (!strncmp(val, "W8", 2) && !type)
435         type = "sgray_8";
436       else if (!strncmp(val, "SRGB24", 6) && !grayscale)
437         type = "srgb_8";
438     }
439   }
440   else if (mode == CUPS_RASTER_WRITE_PWG && (attr = ippFindAttribute(response, "pwg-raster-document-resolution-supported", IPP_TAG_RESOLUTION)) != NULL)
441   {
442     for (i = 0, count = ippGetCount(attr); i < count; i ++)
443     {
444       int tempxdpi, tempydpi;
445       ipp_res_t tempunits;
446
447       tempxdpi = ippGetResolution(attr, 0, &tempydpi, &tempunits);
448
449       if (i == 0 || tempxdpi < xdpi || tempydpi < ydpi)
450       {
451         xdpi = tempxdpi;
452         ydpi = tempydpi;
453       }
454     }
455
456     if ((attr = ippFindAttribute(response, "pwg-raster-document-type-supported", IPP_TAG_KEYWORD)) != NULL)
457     {
458       if (!grayscale && ippContainsString(attr, "srgb_8"))
459         type = "srgb_8";
460       else if (ippContainsString(attr, "sgray_8"))
461         type = "sgray_8";
462     }
463   }
464
465   if (xdpi < 72 || ydpi < 72)
466   {
467     puts("No supported raster resolutions, aborting.");
468     return (NULL);
469   }
470
471   if (!type)
472   {
473     puts("No supported color spaces or bit depths, aborting.");
474     return (NULL);
475   }
476
477  /*
478   * Make the raster context and details...
479   */
480
481   if (!cupsRasterInitPWGHeader(&header, media, type, xdpi, ydpi, "one-sided", NULL))
482   {
483     printf("Unable to initialize raster context: %s\n", cupsRasterErrorString());
484     return (NULL);
485   }
486
487   header.cupsInteger[CUPS_RASTER_PWG_TotalPageCount] = 1;
488
489   if (header.cupsWidth > (4 * header.HWResolution[0]))
490   {
491     xoff = header.HWResolution[0] / 2;
492     yoff = header.HWResolution[1] / 2;
493   }
494   else
495   {
496     xoff = 0;
497     yoff = 0;
498   }
499
500   xrep = (header.cupsWidth - 2 * xoff) / 140;
501   yrep = xrep * header.HWResolution[1] / header.HWResolution[0];
502   yend = header.cupsHeight - yoff;
503
504  /*
505   * Prepare the raster file...
506   */
507
508   if ((line = malloc(header.cupsBytesPerLine)) == NULL)
509   {
510     printf("Unable to allocate %u bytes for raster output: %s\n", header.cupsBytesPerLine, strerror(errno));
511     return (NULL);
512   }
513
514   if ((fd = cupsTempFd(tempname, (int)tempsize)) < 0)
515   {
516     printf("Unable to create temporary print file: %s\n", strerror(errno));
517     free(line);
518     return (NULL);
519   }
520
521   if ((ras = cupsRasterOpen(fd, mode)) == NULL)
522   {
523     printf("Unable to open raster stream: %s\n", cupsRasterErrorString());
524     close(fd);
525     free(line);
526     return (NULL);
527   }
528
529  /*
530   * Write a single page consisting of the template dots repeated over the page.
531   */
532
533   cupsRasterWriteHeader2(ras, &header);
534
535   memset(line, 0xff, header.cupsBytesPerLine);
536
537   for (y = 0; y < yoff; y ++)
538     cupsRasterWritePixels(ras, line, header.cupsBytesPerLine);
539
540   for (temprow = 0, tempcolor = 0; y < yend;)
541   {
542     template = templates[temprow];
543     color    = colors[tempcolor];
544
545     temprow ++;
546     if (temprow >= (int)(sizeof(templates) / sizeof(templates[0])))
547     {
548       temprow = 0;
549       tempcolor ++;
550       if (tempcolor >= (int)(sizeof(colors) / sizeof(colors[0])))
551         tempcolor = 0;
552       else if (tempcolor > 3 && header.cupsColorSpace == CUPS_CSPACE_SW)
553         tempcolor = 0;
554     }
555
556     memset(line, 0xff, header.cupsBytesPerLine);
557
558     if (header.cupsColorSpace == CUPS_CSPACE_SW)
559     {
560      /*
561       * Do grayscale output...
562       */
563
564       for (lineptr = line + xoff; *template; template ++)
565       {
566         if (*template != ' ')
567         {
568           for (xcount = xrep; xcount > 0; xcount --)
569             *lineptr++ = *color;
570         }
571         else
572         {
573           lineptr += xrep;
574         }
575       }
576     }
577     else
578     {
579      /*
580       * Do color output...
581       */
582
583       for (lineptr = line + 3 * xoff; *template; template ++)
584       {
585         if (*template != ' ')
586         {
587           for (xcount = xrep; xcount > 0; xcount --, lineptr += 3)
588             memcpy(lineptr, color, 3);
589         }
590         else
591         {
592           lineptr += 3 * xrep;
593         }
594       }
595     }
596
597     for (ycount = yrep; ycount > 0 && y < yend; ycount --, y ++)
598       cupsRasterWritePixels(ras, line, header.cupsBytesPerLine);
599   }
600
601   memset(line, 0xff, header.cupsBytesPerLine);
602
603   for (y = 0; y < header.cupsHeight; y ++)
604     cupsRasterWritePixels(ras, line, header.cupsBytesPerLine);
605
606   cupsRasterClose(ras);
607
608   close(fd);
609
610   printf("PRINT FILE: %s\n", tempname);
611
612   return (tempname);
613 }
614
615
616 /*
617  * 'monitor_printer()' - Monitor the job and printer states.
618  */
619
620 static void *                           /* O - Thread exit code */
621 monitor_printer(
622     _client_data_t *data)               /* I - Client data */
623 {
624   http_t        *http;                  /* Connection to printer */
625   ipp_t         *request,               /* IPP request */
626                 *response;              /* IPP response */
627   ipp_attribute_t *attr;                /* Attribute in response */
628   ipp_pstate_t  printer_state;          /* Printer state */
629   char          printer_state_reasons[1024];
630                                         /* Printer state reasons */
631   ipp_jstate_t  job_state;              /* Job state */
632   char          job_state_reasons[1024];/* Printer state reasons */
633   static const char * const jattrs[] =  /* Job attributes we want */
634   {
635     "job-state",
636     "job-state-reasons"
637   };
638   static const char * const pattrs[] =  /* Printer attributes we want */
639   {
640     "printer-state",
641     "printer-state-reasons"
642   };
643
644
645  /*
646   * Open a connection to the printer...
647   */
648
649   http = httpConnect2(data->hostname, data->port, NULL, AF_UNSPEC, data->encryption, 1, 0, NULL);
650
651  /*
652   * Loop until the job is canceled, aborted, or completed.
653   */
654
655   printer_state            = (ipp_pstate_t)0;
656   printer_state_reasons[0] = '\0';
657
658   job_state            = (ipp_jstate_t)0;
659   job_state_reasons[0] = '\0';
660
661   while (data->job_state < IPP_JSTATE_CANCELED)
662   {
663    /*
664     * Reconnect to the printer as needed...
665     */
666
667     if (httpGetFd(http) < 0)
668       httpReconnect2(http, 30000, NULL);
669
670     if (httpGetFd(http) >= 0)
671     {
672      /*
673       * Connected, so check on the printer state...
674       */
675
676       request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
677       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, data->uri);
678       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser());
679       ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", (int)(sizeof(pattrs) / sizeof(pattrs[0])), NULL, pattrs);
680
681       response = cupsDoRequest(http, request, data->resource);
682
683       if ((attr = ippFindAttribute(response, "printer-state", IPP_TAG_ENUM)) != NULL)
684         printer_state = (ipp_pstate_t)ippGetInteger(attr, 0);
685
686       if ((attr = ippFindAttribute(response, "printer-state-reasons", IPP_TAG_KEYWORD)) != NULL)
687         ippAttributeString(attr, printer_state_reasons, sizeof(printer_state_reasons));
688
689       if (printer_state != data->printer_state || strcmp(printer_state_reasons, data->printer_state_reasons))
690       {
691         printf("PRINTER: %s (%s)\n", ippEnumString("printer-state", (int)printer_state), printer_state_reasons);
692
693         data->printer_state = printer_state;
694         strlcpy(data->printer_state_reasons, printer_state_reasons, sizeof(data->printer_state_reasons));
695       }
696
697       ippDelete(response);
698
699       if (data->job_id > 0)
700       {
701        /*
702         * Check the status of the job itself...
703         */
704
705         request = ippNewRequest(IPP_OP_GET_JOB_ATTRIBUTES);
706         ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, data->uri);
707         ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", data->job_id);
708         ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser());
709         ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", (int)(sizeof(jattrs) / sizeof(jattrs[0])), NULL, jattrs);
710
711         response = cupsDoRequest(http, request, data->resource);
712
713         if ((attr = ippFindAttribute(response, "job-state", IPP_TAG_ENUM)) != NULL)
714           job_state = (ipp_jstate_t)ippGetInteger(attr, 0);
715
716         if ((attr = ippFindAttribute(response, "job-state-reasons", IPP_TAG_KEYWORD)) != NULL)
717           ippAttributeString(attr, job_state_reasons, sizeof(job_state_reasons));
718
719         if (job_state != data->job_state || strcmp(job_state_reasons, data->job_state_reasons))
720         {
721           printf("JOB %d: %s (%s)\n", data->job_id, ippEnumString("job-state", (int)job_state), job_state_reasons);
722
723           data->job_state = job_state;
724           strlcpy(data->job_state_reasons, job_state_reasons, sizeof(data->job_state_reasons));
725         }
726
727         ippDelete(response);
728       }
729     }
730
731     if (data->job_state < IPP_JSTATE_CANCELED)
732     {
733      /*
734       * Sleep for 5 seconds...
735       */
736
737       sleep(5);
738     }
739   }
740
741  /*
742   * Cleanup and return...
743   */
744
745   httpClose(http);
746
747   printf("FINISHED MONITORING JOB %d\n", data->job_id);
748
749   return (NULL);
750 }
751
752
753 /*
754  * 'run_client()' - Run a client thread.
755  */
756
757 static void *                           /* O - Thread exit code */
758 run_client(
759     _client_data_t *data)               /* I - Client data */
760 {
761   _cups_thread_t monitor_id;            /* Monitoring thread ID */
762   const char    *name;                  /* Job name */
763   char          tempfile[1024] = "";    /* Temporary file (if any) */
764   _client_data_t ldata;                 /* Local client data */
765   http_t        *http;                  /* Connection to printer */
766   ipp_t         *request,               /* IPP request */
767                 *response;              /* IPP response */
768   ipp_attribute_t *attr;                /* Attribute in response */
769   static const char * const pattrs[] =  /* Printer attributes we are interested in */
770   {
771     "job-template",
772     "printer-defaults",
773     "printer-description",
774     "media-col-database",
775     "media-col-ready"
776   };
777
778
779   ldata = *data;
780
781  /*
782   * Start monitoring the printer in the background...
783   */
784
785   monitor_id = _cupsThreadCreate((_cups_thread_func_t)monitor_printer, &ldata);
786
787  /*
788   * Open a connection to the printer...
789   */
790
791   http = httpConnect2(data->hostname, data->port, NULL, AF_UNSPEC, data->encryption, 1, 0, NULL);
792
793  /*
794   * Query printer status and capabilities...
795   */
796
797   request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
798   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, ldata.uri);
799   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser());
800   ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", (int)(sizeof(pattrs) / sizeof(pattrs[0])), NULL, pattrs);
801
802   response = cupsDoRequest(http, request, ldata.resource);
803
804   if (verbosity)
805     show_capabilities(response);
806
807  /*
808   * Now figure out what we will be printing...
809   */
810
811   if (ldata.docfile)
812   {
813    /*
814     * User specified a print file, figure out the format...
815     */
816     const char *ext;                    /* Filename extension */
817
818     if ((ext = strrchr(ldata.docfile, '.')) != NULL)
819     {
820      /*
821       * Guess the format from the extension...
822       */
823
824       if (!strcmp(ext, ".jpg"))
825         ldata.docformat = "image/jpeg";
826       else if (!strcmp(ext, ".pdf"))
827         ldata.docformat = "application/pdf";
828       else if (!strcmp(ext, ".ps"))
829         ldata.docformat = "application/postscript";
830       else if (!strcmp(ext, ".pwg"))
831         ldata.docformat = "image/pwg-raster";
832       else if (!strcmp(ext, ".urf"))
833         ldata.docformat = "image/urf";
834       else
835         ldata.docformat = "application/octet-stream";
836     }
837     else
838     {
839      /*
840       * Tell the printer to auto-detect...
841       */
842
843       ldata.docformat = "application/octet-stream";
844     }
845   }
846   else
847   {
848    /*
849     * No file specified, make something to test with...
850     */
851
852     if ((ldata.docfile = make_raster_file(response, ldata.grayscale, tempfile, sizeof(tempfile), &ldata.docformat)) == NULL)
853       return ((void *)1);
854   }
855
856   ippDelete(response);
857
858  /*
859   * Create a job and wait for completion...
860   */
861
862   request = ippNewRequest(IPP_OP_CREATE_JOB);
863   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, ldata.uri);
864   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser());
865
866   if ((name = strrchr(ldata.docfile, '/')) != NULL)
867     name ++;
868   else
869     name = ldata.docfile;
870
871   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL, name);
872
873   if (verbosity)
874     show_attributes("Create-Job request", 1, request);
875
876   response = cupsDoRequest(http, request, ldata.resource);
877
878   if (verbosity)
879     show_attributes("Create-Job response", 0, response);
880
881   if (cupsLastError() >= IPP_STATUS_REDIRECTION_OTHER_SITE)
882   {
883     printf("Unable to create print job: %s\n", cupsLastErrorString());
884
885     ldata.job_state = IPP_JSTATE_ABORTED;
886
887     goto cleanup;
888   }
889
890   if ((attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) == NULL)
891   {
892     puts("No job-id returned in Create-Job request.");
893
894     ldata.job_state = IPP_JSTATE_ABORTED;
895
896     goto cleanup;
897   }
898
899   ldata.job_id = ippGetInteger(attr, 0);
900
901   printf("CREATED JOB %d, sending %s of type %s\n", ldata.job_id, ldata.docfile, ldata.docformat);
902
903   ippDelete(response);
904
905   request = ippNewRequest(IPP_OP_SEND_DOCUMENT);
906   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, ldata.uri);
907   ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", ldata.job_id);
908   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser());
909   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, "document-format", NULL, ldata.docformat);
910   ippAddBoolean(request, IPP_TAG_OPERATION, "last-document", 1);
911
912   if (verbosity)
913     show_attributes("Send-Document request", 1, request);
914
915   response = cupsDoFileRequest(http, request, ldata.resource, ldata.docfile);
916
917   if (verbosity)
918     show_attributes("Send-Document response", 0, response);
919
920   if (cupsLastError() >= IPP_STATUS_REDIRECTION_OTHER_SITE)
921   {
922     printf("Unable to print file: %s\n", cupsLastErrorString());
923
924     ldata.job_state = IPP_JSTATE_ABORTED;
925
926     goto cleanup;
927   }
928
929   puts("WAITING FOR JOB TO COMPLETE");
930
931   while (ldata.job_state < IPP_JSTATE_CANCELED)
932     sleep(1);
933
934  /*
935   * Cleanup after ourselves...
936   */
937
938   cleanup:
939
940   httpClose(http);
941
942   if (tempfile[0] && !ldata.keepfile)
943     unlink(tempfile);
944
945   _cupsThreadWait(monitor_id);
946
947   _cupsMutexLock(&client_mutex);
948   client_count --;
949   _cupsMutexUnlock(&client_mutex);
950
951   return (NULL);
952 }
953
954
955 /*
956  * 'show_attributes()' - Show attributes in a request or response.
957  */
958
959 static void
960 show_attributes(const char *title,      /* I - Title */
961                 int        request,     /* I - 1 for request, 0 for response */
962                 ipp_t      *ipp)        /* I - IPP request/response */
963 {
964   int                   minor, major = ippGetVersion(ipp, &minor);
965                                         /* IPP version number */
966   ipp_tag_t             group = IPP_TAG_ZERO;
967                                         /* Current group tag */
968   ipp_attribute_t       *attr;          /* Current attribute */
969   const char            *name;          /* Attribute name */
970   char                  buffer[1024];   /* Value */
971
972
973   printf("%s:\n", title);
974   printf("  version=%d.%d\n", major, minor);
975   printf("  request-id=%d\n", ippGetRequestId(ipp));
976   if (!request)
977     printf("  status-code=%s\n", ippErrorString(ippGetStatusCode(ipp)));
978
979   for (attr = ippFirstAttribute(ipp); attr; attr = ippNextAttribute(ipp))
980   {
981     if (group != ippGetGroupTag(attr))
982     {
983       group = ippGetGroupTag(attr);
984       if (group)
985         printf("  %s:\n", ippTagString(group));
986     }
987
988     if ((name = ippGetName(attr)) != NULL)
989     {
990       ippAttributeString(attr, buffer, sizeof(buffer));
991       printf("    %s(%s%s)=%s\n", name, ippGetCount(attr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attr)), buffer);
992     }
993   }
994 }
995
996
997 /*
998  * 'show_capabilities()' - Show printer capabilities.
999  */
1000
1001 static void
1002 show_capabilities(ipp_t *response)      /* I - Printer attributes */
1003 {
1004   int                   i;              /* Looping var */
1005   ipp_attribute_t       *attr;          /* Attribute */
1006   char                  buffer[1024];   /* Attribute value buffer */
1007   static const char * const pattrs[] =  /* Attributes we want to show */
1008   {
1009     "copies-default",
1010     "copies-supported",
1011     "finishings-default",
1012     "finishings-ready",
1013     "finishings-supported",
1014     "media-default",
1015     "media-ready",
1016     "media-supported",
1017     "output-bin-default",
1018     "output-bin-supported",
1019     "print-color-mode-default",
1020     "print-color-mode-supported",
1021     "sides-default",
1022     "sides-supported",
1023     "document-format-default",
1024     "document-format-supported",
1025     "pwg-raster-document-resolution-supported",
1026     "pwg-raster-document-type-supported",
1027     "urf-supported"
1028   };
1029
1030
1031   puts("CAPABILITIES:");
1032   for (i = 0; i < (int)(sizeof(pattrs) / sizeof(pattrs[0])); i ++)
1033   {
1034      if ((attr = ippFindAttribute(response, pattrs[i], IPP_TAG_ZERO)) != NULL)
1035      {
1036        ippAttributeString(attr, buffer, sizeof(buffer));
1037        printf("  %s=%s\n", pattrs[i], buffer);
1038      }
1039   }
1040 }
1041
1042
1043 /*
1044  * 'usage()' - Show program usage...
1045  */
1046
1047 static void
1048 usage(void)
1049 {
1050   puts("Usage: ./testclient printer-uri [options]");
1051   puts("Options:");
1052   puts("  -c num-clients      Simulate multiple clients");
1053   puts("  -d document-format  Generate the specified format");
1054   puts("  -f print-file       Print the named file");
1055   puts("  -g                  Force grayscale printing");
1056   puts("  -k                  Keep temporary files");
1057   puts("  -v                  Be more verbose");
1058 }