C++程序  |  1360行  |  57.08 KB

/*
 * Copyright (C) 2016 The Android Open Source Project
 * Copyright (C) 2016 Mopria Alliance, Inc.
 * Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 */

#include "lib_wprint.h"
#include "cups.h"
#include "http-private.h"
#include "ipphelper.h"
#include "wprint_debug.h"

#include "ipp_print.h"
#include "../plugins/media.h"

#define TAG "ipphelper"

/*
 * Get the IPP version of the given printer
 */
static status_t determine_ipp_version(char *, http_t *);

/*
 * Tests IPP versions and sets it to the latest working version
 */
static status_t test_and_set_ipp_version(char *, http_t *, int, int);

/*
 * Parses supported IPP versions from the IPP response and copies them into ippVersions
 */
static void parse_IPPVersions(ipp_t *response, ipp_version_supported_t *ippVersions);

/*
 * Parses printer URIs from the IPP response and copies them into capabilities
 */
static void parse_printerUris(ipp_t *response, printer_capabilities_t *capabilities);

/*
 * Known media sizes.
 *
 * A note on rounding: In some cases the Android-specified width (in mils) is rounded down.
 * This causes artifacts in libjpeg-turbo when rendering to the correct width, so in these
 * cases we override with a rounded-up value.
 */
struct MediaSizeTableElement SupportedMediaSizes[SUPPORTED_MEDIA_SIZE_COUNT] = {
        { US_LETTER, "LETTER", 8500, 11000, UNKNOWN_VALUE, UNKNOWN_VALUE, "na_letter_8.5x11in" },
        { US_LEGAL, "LEGAL", 8500, 14000, UNKNOWN_VALUE, UNKNOWN_VALUE, "na_legal_8.5x14in" },
        { LEDGER, "LEDGER", 11000, 17000, UNKNOWN_VALUE, UNKNOWN_VALUE, "na_ledger_11x17in" },
        { INDEX_CARD_5X7, "5X7", 5000, 7000, UNKNOWN_VALUE, UNKNOWN_VALUE, "na_5x7_5x7in" },

        // Android system uses width of 11690
        { ISO_A3, "A3", 11694, 16540, 297, 420, "iso_a3_297x420mm" },

        // Android system uses width of 8267
        { ISO_A4, "A4", 8268, 11692, 210, 297, "iso_a4_210x297mm" },
        { ISO_A5, "A5", 5830, 8270, 148, 210, "iso_a5_148x210mm" },

        // Android system uses width of 10118
        { JIS_B4, "JIS B4", 10119, 14331, 257, 364, "jis_b4_257x364mm" },

        // Android system uses width of 7165
        { JIS_B5, "JIS B5", 7167, 10118, 182, 257, "jis_b5_182x257mm" },
        { US_GOVERNMENT_LETTER, "8x10", 8000, 10000, UNKNOWN_VALUE, UNKNOWN_VALUE,
        "na_govt-letter_8x10in" },
        { INDEX_CARD_4X6, "4x6", 4000, 6000, UNKNOWN_VALUE, UNKNOWN_VALUE, "na_index-4x6_4x6in" },
        { JPN_HAGAKI_PC, "JPOST", 3940, 5830, 100, 148, "jpn_hagaki_100x148mm" },
        { PHOTO_89X119, "89X119", 3504, 4685, 89, 119, "om_dsc-photo_89x119mm" },
        { CARD_54X86, "54X86", 2126, 3386, 54, 86, "om_card_54x86mm" },
        { OE_PHOTO_L, "L", 3500, 5000, UNKNOWN_VALUE, UNKNOWN_VALUE, "oe_photo-l_3.5x5in" }
};

typedef struct {
    double Lower;
    double Upper;
} media_dimension_mm_t;

static const char *__request_ipp_version[] = {"ipp-versions-supported"};

static int __ipp_version_major = 2;
static int __ipp_version_minor = 0;

status_t set_ipp_version(ipp_t *op_to_set, char *printer_uri, http_t *http,
        ipp_version_state use_existing_version) {
    LOGD("set_ipp_version(): Enter %d", use_existing_version);
    if (op_to_set == NULL) {
        return ERROR;
    }
    switch (use_existing_version) {
        case NEW_REQUEST_SEQUENCE:
            __ipp_version_major = 2;
            __ipp_version_minor = 0;
            break;
        case IPP_VERSION_RESOLVED:
            break;
        case IPP_VERSION_UNSUPPORTED:
            if (determine_ipp_version(printer_uri, http) != 0) {
                return ERROR;
            }
            break;
    }
    ippSetVersion(op_to_set, __ipp_version_major, __ipp_version_minor);
    LOGD("set_ipp_version(): Done");
    return OK;
}

static status_t determine_ipp_version(char *printer_uri, http_t *http) {
    LOGD("determine_ipp_version(): Enter printer_uri =  %s", printer_uri);

    if (http == NULL) {
        LOGE("determine_ipp_version(): http is NULL cannot continue");
        return ERROR;
    }
    if ((test_and_set_ipp_version(printer_uri, http, 1, 1) == OK)
            || (test_and_set_ipp_version(printer_uri, http, 1, 0) == OK)
            || (test_and_set_ipp_version(printer_uri, http, 2, 0) == OK)) {
        LOGD("successfully set ipp version.");
    } else {
        LOGD("could not get ipp version using any known ipp version.");
        return ERROR;
    }
    return OK;
}

static status_t test_and_set_ipp_version(char *printer_uri, http_t *http, int major, int minor) {
    status_t return_value = ERROR;
    int service_unavailable_retry_count = 0;
    int bad_request_retry_count = 0;
    ipp_t *request = NULL;
    ipp_t *response;
    ipp_version_supported_t ippVersions;
    char http_resource[1024];
    int op = IPP_GET_PRINTER_ATTRIBUTES;

    LOGD("test_and_set_ipp_version(): Enter %d - %d", major, minor);
    memset(&ippVersions, 0, sizeof(ipp_version_supported_t));
    getResourceFromURI(printer_uri, http_resource, 1024);
    do {
        request = ippNewRequest(op);
        ippSetVersion(request, major, minor);
        ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, printer_uri);
        ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes",
                sizeof(__request_ipp_version) / sizeof(__request_ipp_version[0]),
                NULL, __request_ipp_version);
        if ((response = cupsDoRequest(http, request, http_resource)) == NULL) {
            ipp_status_t ipp_status = cupsLastError();
            LOGD("test_and_set_ipp_version:  response is null:  ipp_status %d %s",
                    ipp_status, ippErrorString(ipp_status));
            if (ipp_status == IPP_INTERNAL_ERROR) {
                LOGE("test_and_set_ipp_version: 1280 received, bailing...");
                break;
            } else if ((ipp_status == IPP_SERVICE_UNAVAILABLE) &&
                    (service_unavailable_retry_count < IPP_SERVICE_ERROR_MAX_RETRIES)) {
                LOGE("test_and_set_ipp_version: 1282 received, retrying %d of %d",
                        service_unavailable_retry_count, IPP_SERVICE_ERROR_MAX_RETRIES);
                service_unavailable_retry_count++;
                continue;
            } else if (ipp_status == IPP_BAD_REQUEST) {
                LOGE("test_and_set_ipp_version: IPP_Status of IPP_BAD_REQUEST "
                        "received. retry (%d) of (%d)", bad_request_retry_count,
                        IPP_BAD_REQUEST_MAX_RETRIES);
                if (bad_request_retry_count > IPP_BAD_REQUEST_MAX_RETRIES) {
                    break;
                }
                bad_request_retry_count++;
                continue;
            } else if (ipp_status == IPP_NOT_FOUND) {
                LOGE("test_and_set_ipp_version: IPP_Status of IPP_NOT_FOUND received");
                break;
            }
            return_value = ERROR;
        } else {
            ipp_status_t ipp_status = cupsLastError();
            LOGD("ipp CUPS last ERROR: %d, %s", ipp_status, ippErrorString(ipp_status));
            if (ipp_status == IPP_BAD_REQUEST) {
                LOGD("IPP_Status of IPP_BAD_REQUEST received. retry (%d) of (%d)",
                        bad_request_retry_count, IPP_BAD_REQUEST_MAX_RETRIES);
                if (bad_request_retry_count > IPP_BAD_REQUEST_MAX_RETRIES) {
                    break;
                }
                bad_request_retry_count++;
                ippDelete(response);
                continue;
            }

            parse_IPPVersions(response, &ippVersions);
            if (ippVersions.supportsIpp20) {
                __ipp_version_major = 2;
                __ipp_version_minor = 0;
                return_value = OK;
                LOGD("test_and_set_ipp_version(): ipp version set to %d,%d",
                        __ipp_version_major, __ipp_version_minor);
            } else if (ippVersions.supportsIpp11) {
                __ipp_version_major = 1;
                __ipp_version_minor = 1;
                return_value = OK;
                LOGD("test_and_set_ipp_version(): ipp version set to %d,%d",
                        __ipp_version_major, __ipp_version_minor);
            } else if (ippVersions.supportsIpp10) {
                __ipp_version_major = 1;
                __ipp_version_minor = 0;
                return_value = OK;
                LOGD("test_and_set_ipp_version(): ipp version set to %d,%d",
                        __ipp_version_major, __ipp_version_minor);
            } else {
                LOGD("test_and_set_ipp_version: ipp version not found");
                return_value = ERROR;
            }
        }
        if (response != NULL) ippDelete(response);
        break;
    } while (1);
    return return_value;
}

ipp_status_t get_PrinterState(http_t *http, char *printer_uri,
        printer_state_dyn_t *printer_state_dyn, ipp_pstate_t *printer_state) {
    LOGD("get_PrinterState(): Enter");

    // Requested printer attributes
    static const char *pattrs[] = {"printer-make-and-model", "printer-state",
            "printer-state-message", "printer-state-reasons"};

    ipp_t *request = NULL;
    ipp_t *response = NULL;
    ipp_status_t ipp_status = IPP_OK;
    int op = IPP_GET_PRINTER_ATTRIBUTES;
    char http_resource[1024];
    getResourceFromURI(printer_uri, http_resource, 1024);

    if (printer_state_dyn == NULL) {
        LOGE("get_PrinterState(): printer_state_dyn is null");
        return ipp_status;
    }

    if (printer_state) {
        *printer_state = IPP_PRINTER_STOPPED;
    } else {
        LOGE("get_PrinterState(): printer_state is null");
    }
    request = ippNewRequest(op);

    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, printer_uri);

    ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes",
            sizeof(pattrs) / sizeof(pattrs[0]), NULL, pattrs);

    if ((response = ipp_doCupsRequest(http, request, http_resource, printer_uri)) == NULL) {
        ipp_status = cupsLastError();
        LOGE("get_PrinterState(): response is null: ipp_status %d", ipp_status);
        printer_state_dyn->printer_status = PRINT_STATUS_UNABLE_TO_CONNECT;
        printer_state_dyn->printer_reasons[0] = PRINT_STATUS_UNABLE_TO_CONNECT;
    } else {
        ipp_status = cupsLastError();
        LOGD("ipp CUPS last ERROR: %d, %s", ipp_status, ippErrorString(ipp_status));
        get_PrinterStateReason(response, printer_state, printer_state_dyn);
        LOGD("get_PrinterState(): printer_state_dyn->printer_status: %d",
                printer_state_dyn->printer_status);
    }
    LOGD("get_PrinterState(): exit http->fd %d, ipp_status %d, printer_state %d", http->fd,
            ipp_status, printer_state_dyn->printer_status);

    ippDelete(request);
    ippDelete(response);
    return ipp_status;
}

void get_PrinterStateReason(ipp_t *response, ipp_pstate_t *printer_state,
        printer_state_dyn_t *printer_state_dyn) {
    LOGD("get_PrinterStateReason(): Enter");
    ipp_attribute_t *attrptr;
    int reason_idx = 0;
    int idx = 0;
    ipp_pstate_t printer_ippstate = IPP_PRINTER_IDLE;

    if ((attrptr = ippFindAttribute(response, "printer-state", IPP_TAG_ENUM)) == NULL) {
        LOGE("get_PrinterStateReason printer-state null");
        printer_state_dyn->printer_status = PRINT_STATUS_UNABLE_TO_CONNECT;
        printer_state_dyn->printer_reasons[0] = PRINT_STATUS_UNABLE_TO_CONNECT;
    } else {
        printer_ippstate = (ipp_pstate_t) ippGetInteger(attrptr, 0);
        *printer_state = printer_ippstate;

        LOGD("get_PrinterStateReason printer-state: %d", printer_ippstate);
        // set the printer_status; they may be modified based on the status reasons below.
        switch (printer_ippstate) {
            case IPP_PRINTER_IDLE:
                printer_state_dyn->printer_status = PRINT_STATUS_IDLE;
                break;
            case IPP_PRINTER_PROCESSING:
                printer_state_dyn->printer_status = PRINT_STATUS_PRINTING;
                break;
            case IPP_PRINTER_STOPPED:
                printer_state_dyn->printer_status = PRINT_STATUS_SVC_REQUEST;
                break;
        }
    }

    if ((attrptr = ippFindAttribute(response, "printer-state-reasons", IPP_TAG_KEYWORD)) == NULL) {
        LOGE(" get_PrinterStateReason printer-state reason null");
        printer_state_dyn->printer_status = PRINT_STATUS_UNABLE_TO_CONNECT;
        printer_state_dyn->printer_reasons[0] = PRINT_STATUS_UNABLE_TO_CONNECT;
    } else {
        for (idx = 0; idx < ippGetCount(attrptr); idx++) {
            // Per RFC2911 any of these can have -error, -warning, or -report appended to end
            LOGD("get_PrinterStateReason printer-state-reason: %s",
                    ippGetString(attrptr, idx, NULL));
            if (strncmp(ippGetString(attrptr, idx, NULL), IPP_PRNT_STATE_NONE,
                    strlen(IPP_PRNT_STATE_NONE)) == 0) {
                switch (printer_ippstate) {
                    case IPP_PRINTER_IDLE:
                        printer_state_dyn->printer_reasons[reason_idx++] = PRINT_STATUS_IDLE;
                        break;
                    case IPP_PRINTER_PROCESSING:
                        printer_state_dyn->printer_reasons[reason_idx++] = PRINT_STATUS_PRINTING;
                        break;
                    case IPP_PRINTER_STOPPED:
                        // should this be PRINT_STATUS_SVC_REQUEST
                        printer_state_dyn->printer_reasons[reason_idx++] = PRINT_STATUS_UNKNOWN;
                        break;
                }
            } else if (strncmp(ippGetString(attrptr, idx, NULL), IPP_PRNT_STATE_SPOOL_FULL,
                    strlen(IPP_PRNT_STATE_SPOOL_FULL)) == 0) {
                switch (printer_ippstate) {
                    case IPP_PRINTER_IDLE:
                        printer_state_dyn->printer_reasons[reason_idx++] = PRINT_STATUS_UNKNOWN;
                        break;
                    case IPP_PRINTER_PROCESSING:
                        printer_state_dyn->printer_reasons[reason_idx++] = PRINT_STATUS_PRINTING;
                        break;
                    case IPP_PRINTER_STOPPED:
                        // should this be PRINT_STATUS_SVC_REQUEST
                        printer_state_dyn->printer_reasons[reason_idx++] = PRINT_STATUS_UNKNOWN;
                        break;
                }
            } else if (strncmp(ippGetString(attrptr, idx, NULL), IPP_PRNT_STATE_MARKER_SUPPLY_LOW,
                    strlen(IPP_PRNT_STATE_MARKER_SUPPLY_LOW)) == 0) {
                printer_state_dyn->printer_reasons[reason_idx++] = PRINT_STATUS_LOW_ON_INK;
            } else if (strncmp(ippGetString(attrptr, idx, NULL), IPP_PRNT_STATE_TONER_LOW,
                    strlen(IPP_PRNT_STATE_TONER_LOW)) == 0) {
                printer_state_dyn->printer_reasons[reason_idx++] = PRINT_STATUS_LOW_ON_TONER;
            } else if (strncmp(ippGetString(attrptr, idx, NULL), IPP_PRNT_STATE_OTHER_WARN,
                    strlen(IPP_PRNT_STATE_OTHER_WARN)) == 0) {
                printer_state_dyn->printer_reasons[reason_idx++] = PRINT_STATUS_UNKNOWN;
            } else {
                // check blocking cases
                if (strncmp(ippGetString(attrptr, idx, NULL), IPP_PRNT_STATE_MEDIA_NEEDED,
                        strlen(IPP_PRNT_STATE_MEDIA_NEEDED)) == 0) {
                    printer_state_dyn->printer_reasons[reason_idx++] = PRINT_STATUS_OUT_OF_PAPER;
                } else if (strncmp(ippGetString(attrptr, idx, NULL), IPP_PRNT_STATE_MEDIA_EMPTY,
                        strlen(IPP_PRNT_STATE_MEDIA_EMPTY)) == 0) {
                    printer_state_dyn->printer_reasons[reason_idx++] = PRINT_STATUS_OUT_OF_PAPER;
                } else if (strncmp(ippGetString(attrptr, idx, NULL), IPP_PRNT_STATE_TONER_EMPTY,
                        strlen(IPP_PRNT_STATE_TONER_EMPTY)) == 0) {
                    printer_state_dyn->printer_reasons[reason_idx++] = PRINT_STATUS_OUT_OF_TONER;
                } else if (strncmp(ippGetString(attrptr, idx, NULL),
                        IPP_PRNT_STATE_MARKER_SUPPLY_EMPTY,
                        strlen(IPP_PRNT_STATE_MARKER_SUPPLY_EMPTY)) == 0) {
                    printer_state_dyn->printer_reasons[reason_idx++] = PRINT_STATUS_OUT_OF_INK;
                } else if (strncmp(ippGetString(attrptr, idx, NULL), IPP_PRNT_STATE_DOOR_OPEN,
                        strlen(IPP_PRNT_STATE_DOOR_OPEN)) == 0) {
                    printer_state_dyn->printer_reasons[reason_idx++] = PRINT_STATUS_DOOR_OPEN;
                } else if (strncmp(ippGetString(attrptr, idx, NULL), IPP_PRNT_STATE_COVER_OPEN,
                        strlen(IPP_PRNT_STATE_COVER_OPEN)) == 0) {
                    printer_state_dyn->printer_reasons[reason_idx++] = PRINT_STATUS_DOOR_OPEN;
                } else if (strncmp(ippGetString(attrptr, idx, NULL), IPP_PRNT_STATE_MEDIA_JAM,
                        strlen(IPP_PRNT_STATE_MEDIA_JAM)) == 0) {
                    printer_state_dyn->printer_reasons[reason_idx++] = PRINT_STATUS_JAMMED;
                } else if (strncmp(ippGetString(attrptr, idx, NULL), IPP_PRNT_SHUTDOWN,
                        strlen(IPP_PRNT_SHUTDOWN)) == 0) {
                    printer_state_dyn->printer_reasons[reason_idx++] = PRINT_STATUS_SHUTTING_DOWN;
                } else if (strncmp(ippGetString(attrptr, idx, NULL), IPP_PRNT_STATE_OTHER_ERR,
                        strlen(IPP_PRNT_STATE_OTHER_ERR)) == 0) {
                    printer_state_dyn->printer_reasons[reason_idx++] = PRINT_STATUS_SVC_REQUEST;
                } else if (strncmp(ippGetString(attrptr, idx, NULL), IPP_PRNT_PAUSED,
                        strlen(IPP_PRNT_PAUSED)) == 0) {
                    printer_state_dyn->printer_reasons[reason_idx++] = PRINT_STATUS_UNKNOWN;
                }
            }
        }  // end of reasons loop
    }
}

static void print_col(ipp_t *col) {
    int i;
    ipp_attribute_t *attr;

    LOGD("{");
    for (attr = ippFirstAttribute(col); attr; attr = ippNextAttribute(col)) {
        switch (ippGetValueTag(attr)) {
            case IPP_TAG_INTEGER:
            case IPP_TAG_ENUM:
                for (i = 0; i < ippGetCount(attr); i++) {
                    LOGD("  %s(%s%s)= %d ", ippGetName(attr),
                            ippGetCount(attr) > 1 ? "1setOf " : "",
                            ippTagString(ippGetValueTag(attr)), ippGetInteger(attr, i));
                }
                break;
            case IPP_TAG_BOOLEAN:
                for (i = 0; i < ippGetCount(attr); i++) {
                    if (ippGetBoolean(attr, i)) {
                        LOGD("  %s(%s%s)= true ", ippGetName(attr),
                                ippGetCount(attr) > 1 ? "1setOf " : "",
                                ippTagString(ippGetValueTag(attr)));
                    } else {
                        LOGD("  %s(%s%s)= false ", ippGetName(attr),
                                ippGetCount(attr) > 1 ? "1setOf " : "",
                                ippTagString(ippGetValueTag(attr)));
                    }
                }
                break;
            case IPP_TAG_NOVALUE:
                LOGD("  %s(%s%s)= novalue", ippGetName(attr),
                        ippGetCount(attr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attr)));
                break;
            case IPP_TAG_RANGE:
                for (i = 0; i < ippGetCount(attr); i++) {
                    int lower, upper;
                    lower = ippGetRange(attr, i, &upper);
                    LOGD("  %s(%s%s)= %d-%d ", ippGetName(attr),
                            ippGetCount(attr) > 1 ? "1setOf " : "",
                            ippTagString(ippGetValueTag(attr)), lower, upper);
                }
                break;
            case IPP_TAG_RESOLUTION:
                for (i = 0; i < ippGetCount(attr); i++) {
                    ipp_res_t units;
                    int xres, yres;
                    xres = ippGetResolution(attr, i, &yres, &units);
                    LOGD("  %s(%s%s)= %dx%d%s ", ippGetName(attr),
                            ippGetCount(attr) > 1 ? "1setOf " : "",
                            ippTagString(ippGetValueTag(attr)), xres, yres,
                            units == IPP_RES_PER_INCH ? "dpi" : "dpc");
                }
                break;
            case IPP_TAG_STRING:
            case IPP_TAG_TEXT:
            case IPP_TAG_NAME:
            case IPP_TAG_KEYWORD:
            case IPP_TAG_CHARSET:
            case IPP_TAG_URI:
            case IPP_TAG_MIMETYPE:
            case IPP_TAG_LANGUAGE:
                for (i = 0; i < ippGetCount(attr); i++) {
                    LOGD("  %s(%s%s)= \"%s\" ", ippGetName(attr),
                            ippGetCount(attr) > 1 ? "1setOf " : "",
                            ippTagString(ippGetValueTag(attr)), ippGetString(attr, i, NULL));
                }
                break;
            case IPP_TAG_TEXTLANG:
            case IPP_TAG_NAMELANG:
                for (i = 0; i < ippGetCount(attr); i++) {
                    const char *charset;
                    const char *text;
                    text = ippGetString(attr, i, &charset);
                    LOGD("  %s(%s%s)= \"%s\",%s ", ippGetName(attr),
                            ippGetCount(attr) > 1 ? "1setOf " : "",
                            ippTagString(ippGetValueTag(attr)), text, charset);
                }
                break;
            case IPP_TAG_BEGIN_COLLECTION:
                for (i = 0; i < ippGetCount(attr); i++) {
                    print_col(ippGetCollection(attr, i));
                }
                break;
            default:
                break;
        }
    }
    LOGD("}");
}

void print_attr(ipp_attribute_t *attr) {
    int i;

    if (ippGetName(attr) == NULL) {
        return;
    }

    switch (ippGetValueTag(attr)) {
        case IPP_TAG_INTEGER:
        case IPP_TAG_ENUM:
            for (i = 0; i < ippGetCount(attr); i++) {
                LOGD("%s (%s%s) = %d ", ippGetName(attr), ippGetCount(attr) > 1 ? "1setOf " : "",
                        ippTagString(ippGetValueTag(attr)), ippGetInteger(attr, i));
            }
            break;
        case IPP_TAG_BOOLEAN:
            for (i = 0; i < ippGetCount(attr); i++) {
                if (ippGetBoolean(attr, i)) {
                    LOGD("%s (%s%s) = true ", ippGetName(attr),
                            ippGetCount(attr) > 1 ? "1setOf " : "",
                            ippTagString(ippGetValueTag(attr)));
                } else {
                    LOGD("%s (%s%s) = false ", ippGetName(attr),
                            ippGetCount(attr) > 1 ? "1setOf " : "",
                            ippTagString(ippGetValueTag(attr)));
                }
            }
            break;
        case IPP_TAG_NOVALUE:
            LOGD("%s (%s%s) = novalue", ippGetName(attr),
                    ippGetCount(attr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attr)));
            break;
        case IPP_TAG_RANGE:
            for (i = 0; i < ippGetCount(attr); i++) {
                int lower, upper;
                lower = ippGetRange(attr, i, &upper);
                LOGD("%s (%s%s) = %d-%d ", ippGetName(attr), ippGetCount(attr) > 1 ? "1setOf " : "",
                        ippTagString(ippGetValueTag(attr)), lower, upper);
            }
            break;
        case IPP_TAG_RESOLUTION:
            for (i = 0; i < ippGetCount(attr); i++) {
                ipp_res_t units;
                int xres, yres;
                xres = ippGetResolution(attr, i, &yres, &units);
                LOGD("%s (%s%s) = %dx%d%s ", ippGetName(attr),
                        ippGetCount(attr) > 1 ? "1setOf " : "",
                        ippTagString(ippGetValueTag(attr)), xres, yres,
                        units == IPP_RES_PER_INCH ? "dpi" : "dpc");
            }
            break;
        case IPP_TAG_STRING:
        case IPP_TAG_TEXT:
        case IPP_TAG_NAME:
        case IPP_TAG_KEYWORD:
        case IPP_TAG_CHARSET:
        case IPP_TAG_URI:
        case IPP_TAG_MIMETYPE:
        case IPP_TAG_LANGUAGE:
            for (i = 0; i < ippGetCount(attr); i++) {
                LOGD("%s (%s%s) = \"%s\" ", ippGetName(attr),
                        ippGetCount(attr) > 1 ? "1setOf " : "",
                        ippTagString(ippGetValueTag(attr)), ippGetString(attr, i, NULL));
            }
            break;
        case IPP_TAG_TEXTLANG:
        case IPP_TAG_NAMELANG:
            for (i = 0; i < ippGetCount(attr); i++) {
                const char *charset;
                const char *text;
                text = ippGetString(attr, i, &charset);
                LOGD("%s (%s%s) = \"%s\",%s ", ippGetName(attr),
                        ippGetCount(attr) > 1 ? "1setOf " : "",
                        ippTagString(ippGetValueTag(attr)), text, charset);
            }
            break;

        case IPP_TAG_BEGIN_COLLECTION:
            for (i = 0; i < ippGetCount(attr); i++) {
                LOGD("%s (%s%s): IPP_TAG_BEGIN_COLLECTION", ippGetName(attr),
                        ippGetCount(attr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attr)));
                print_col(ippGetCollection(attr, i));
            }
            LOGD("%s (%s%s): IPP_TAG_END_COLLECTION", ippGetName(attr),
                    ippGetCount(attr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attr)));
            break;

        default:
            break;
    }
}

void parse_IPPVersions(ipp_t *response, ipp_version_supported_t *ippVersions) {
    int i;
    ipp_attribute_t *attrptr;
    char ipp10[] = "1.0";
    char ipp11[] = "1.1";
    char ipp20[] = "2.0";
    LOGD(" Entered IPPVersions");
    if (ippVersions != NULL) {
        memset(ippVersions, 0, sizeof(ipp_version_supported_t));
        LOGD(" in get_supportedIPPVersions");
        attrptr = ippFindAttribute(response, "ipp-versions-supported", IPP_TAG_KEYWORD);
        if (attrptr != NULL) {
            LOGD(" in get_supportedIPPVersions: %d", ippGetCount(attrptr));
            for (i = 0; i < ippGetCount(attrptr); i++) {
                if (strcmp(ipp10, ippGetString(attrptr, i, NULL)) == 0) {
                    ippVersions->supportsIpp10 = 1;
                } else if (strcmp(ipp11, ippGetString(attrptr, i, NULL)) == 0) {
                    ippVersions->supportsIpp11 = 1;
                } else if (strcmp(ipp20, ippGetString(attrptr, i, NULL)) == 0) {
                    ippVersions->supportsIpp20 = 1;
                } else {
                    LOGD("found another ipp version. %s", ippGetString(attrptr, i, NULL));
                }
            }
        }
    }
}

const char *mapDFMediaToIPPKeyword(media_size_t media_size) {
    int i;
    for (i = 0; i < SUPPORTED_MEDIA_SIZE_COUNT; i++) {
        if (SupportedMediaSizes[i].media_size == (media_size_t) media_size) {
            return (SupportedMediaSizes[i].PWGName);
        }
    }
    return (SupportedMediaSizes[0].PWGName);
}

int ipp_find_media_size(const char *ipp_media_keyword, media_size_t *media_size) {
    int i;
    LOGD("ipp_find_media_size entry is %s", ipp_media_keyword);
    for (i = 0; i < SUPPORTED_MEDIA_SIZE_COUNT; i++) {
        if (strcmp(SupportedMediaSizes[i].PWGName, ipp_media_keyword) == 0) {
            LOGD(" mediaArraySize: match string  %s  PT_size: %d",
                    SupportedMediaSizes[i].PWGName, SupportedMediaSizes[i].media_size);
            break;
        }
    }
    if (i < SUPPORTED_MEDIA_SIZE_COUNT) {
        *media_size = SupportedMediaSizes[i].media_size;
        return i;
    } else {
        return -1;
    }
    return -1;
}

void parse_getMediaSupported(ipp_t *response, media_supported_t *media_supported) {
    int i;
    ipp_attribute_t *attrptr;
    int sizes_idx = 0;
    LOGD(" Entered getMediaSupported");

    media_size_t media_sizeTemp;
    int idx = 0;

    if ((attrptr = ippFindAttribute(response, "media-supported", IPP_TAG_KEYWORD)) != NULL) {
        LOGD("media-supported  found; number of values %d", ippGetCount(attrptr));
        for (i = 0; i < ippGetCount(attrptr); i++) {
            idx = ipp_find_media_size(ippGetString(attrptr, i, NULL), &media_sizeTemp);

            // Modified since anytime the find media size returned 0 it could either mean
            // NOT found or na_letter.
            if (idx >= 0) {
                media_supported->media_size[sizes_idx] = media_sizeTemp;
                media_supported->idxKeywordTranTable[sizes_idx] = idx;
                sizes_idx++;
            }
        }
    } else {
        LOGD("media-supported not found");
    }
}

static void get_supportedPrinterResolutions(ipp_attribute_t *attrptr,
        printer_capabilities_t *capabilities) {
    int idx = 0;
    int i;
    for (i = 0; i < ippGetCount(attrptr); i++) {
        ipp_res_t units;
        int xres, yres;
        xres = ippGetResolution(attrptr, i, &yres, &units);
        if (units == IPP_RES_PER_INCH) {
            if ((idx < MAX_RESOLUTIONS_SUPPORTED) && (xres == yres)) {
                capabilities->supportedResolutions[idx] = xres;
                idx++;
            }
        }
    }
    capabilities->numSupportedResolutions = idx;
}

void getResourceFromURI(const char *uri, char *resource, int resourcelen) {
    char scheme[1024];
    char username[1024];
    char host[1024];
    int port;
    httpSeparateURI(0, uri, scheme, 1024, username, 1024, host, 1024, &port, resource, resourcelen);
}

/*
 * Add a new media type to a printer's collection of supported media types
 */
static void addMediaType(printer_capabilities_t *capabilities, media_type_t mediaType) {
    int index;
    for (index = 0; index < capabilities->numSupportedMediaTypes; index++) {
        // Skip if already present
        if (capabilities->supportedMediaTypes[index] == mediaType) return;
    }

    // Add if not found and not too many
    if (capabilities->numSupportedMediaTypes < MAX_MEDIA_TYPES_SUPPORTED) {
        capabilities->supportedMediaTypes[capabilities->numSupportedMediaTypes++] = mediaType;
    } else {
        LOGI("Hit MAX_MEDIA_TYPES_SUPPORTED while adding %d", mediaType);
    }
}

void parse_printerAttributes(ipp_t *response, printer_capabilities_t *capabilities) {
    int i, j;
    ipp_attribute_t *attrptr;

    LOGD("Entered parse_printerAttributes");

    media_supported_t media_supported;
    for (i = 0; i <= PAGE_STATUS_MAX - 1; i++) {
        media_supported.media_size[i] = 0;
    }
    parse_getMediaSupported(response, &media_supported);

    parse_printerUris(response, capabilities);

    LOGD("Media Supported: ");
    int idx = 0;
    capabilities->numSupportedMediaTypes = 0;
    for (i = 0; i <= PAGE_STATUS_MAX - 1; i++) {
        if (media_supported.media_size[i] != 0) {
            capabilities->supportedMediaSizes[capabilities->numSupportedMediaSizes++] =
                    media_supported.media_size[i];
            idx = media_supported.idxKeywordTranTable[i];
            LOGD(" i %d, \tPT_Size: %d  \tidx %d \tKeyword: %s", i, media_supported.media_size[i],
                    idx, SupportedMediaSizes[idx].PWGName);
        }
    }

    if ((attrptr = ippFindAttribute(response, "printer-dns-sd-name", IPP_TAG_NAME)) != NULL) {
        strlcpy(capabilities->name, ippGetString(attrptr, 0, NULL), sizeof(capabilities->name));
    }

    if (!capabilities->name[0]) {
        if ((attrptr = ippFindAttribute(response, "printer-info", IPP_TAG_TEXT)) != NULL) {
            strlcpy(capabilities->name, ippGetString(attrptr, 0, NULL), sizeof(capabilities->name));
        }
    }

    if (!capabilities->name[0]) {
        if ((attrptr = ippFindAttribute(response, "printer-name", IPP_TAG_TEXT)) != NULL) {
            strlcpy(capabilities->name, ippGetString(attrptr, 0, NULL), sizeof(capabilities->name));
        }
    }

    if ((attrptr = ippFindAttribute(response, "printer-make-and-model", IPP_TAG_TEXT)) != NULL) {
        strlcpy(capabilities->make, ippGetString(attrptr, 0, NULL), sizeof(capabilities->make));
    }

    if ((attrptr = ippFindAttribute(response, "printer-uuid", IPP_TAG_URI)) != NULL) {
        strlcpy(capabilities->uuid, ippGetString(attrptr, 0, NULL), sizeof(capabilities->uuid));
    }

    if ((attrptr = ippFindAttribute(response, "printer-location", IPP_TAG_TEXT)) != NULL) {
        strlcpy(capabilities->location, ippGetString(attrptr, 0, NULL),
                sizeof(capabilities->location));
    }

    if ((attrptr = ippFindAttribute(response, "media-default", IPP_TAG_KEYWORD)) != NULL) {
        strlcpy(capabilities->mediaDefault, ippGetString(attrptr, 0, NULL),
                sizeof(capabilities->mediaDefault));
    }

    if ((attrptr = ippFindAttribute(response, "color-supported", IPP_TAG_BOOLEAN)) != NULL) {
        if (ippGetBoolean(attrptr, 0)) {
            capabilities->color = 1;
        }
    }
    if ((attrptr = ippFindAttribute(response, "copies-supported", IPP_TAG_RANGE)) != NULL) {
        int upper = 0;
        for (i = 0; i < ippGetCount(attrptr); i++) {
            ippGetRange(attrptr, i, &upper);
        }
        if (upper > 1) {
            capabilities->canCopy = 1;
        }
    }
    if ((attrptr = ippFindAttribute(response, "print-color-mode-supported", IPP_TAG_KEYWORD)) !=
            NULL) {
        for (i = 0; i < ippGetCount(attrptr); i++) {
            if (strcmp("color", ippGetString(attrptr, i, NULL)) == 0) {
                capabilities->color = 1;
            }
        }
    }
    if ((attrptr = ippFindAttribute(response, "print-quality-supported", IPP_TAG_ENUM)) !=
            NULL) {
        for (i = 0; i < ippGetCount(attrptr) && capabilities->numSupportedQuality
                < MAX_QUALITY_SUPPORTED; i++) {
            LOGD("print-quality-supported: %d", ippGetInteger(attrptr, i));
            capabilities->supportedQuality[capabilities->numSupportedQuality++] =
                ippGetInteger(attrptr, i);
        }
    }

    char imagePCLm[] = "application/PCLm";
    char imagePWG[] = "image/pwg-raster";
    char imagePDF[] = "image/pdf";
    char applicationPDF[] = "application/pdf";

    if ((attrptr = ippFindAttribute(response, "document-format-supported", IPP_TAG_MIMETYPE))
            != NULL) {
        for (i = 0; i < ippGetCount(attrptr); i++) {
            if (strcmp(imagePDF, ippGetString(attrptr, i, NULL)) == 0) {
                capabilities->canPrintPDF = 1;
            } else if (strcmp(applicationPDF, ippGetString(attrptr, i, NULL)) == 0) {
                capabilities->canPrintPDF = 1;
            } else if (strcmp(imagePCLm, ippGetString(attrptr, i, NULL)) == 0) {
                capabilities->canPrintPCLm = 1;
            } else if (strcmp(applicationPDF, ippGetString(attrptr, i, NULL)) == 0) {
                capabilities->canPrintPDF = 1;
            } else if (strcmp(imagePWG, ippGetString(attrptr, i, NULL)) == 0) {
                capabilities->canPrintPWG = 1;
            }
        }
    }

    if ((attrptr = ippFindAttribute(response, "sides-supported", IPP_TAG_KEYWORD)) != NULL) {
        for (i = 0; i < ippGetCount(attrptr); i++) {
            if (strcmp(IPP_SIDES_TWO_SIDED_SHORT_EDGE, ippGetString(attrptr, i, NULL)) == 0) {
                capabilities->duplex = 1;
            } else if (strcmp(IPP_SIDES_TWO_SIDED_LONG_EDGE, ippGetString(attrptr, i, NULL)) == 0) {
                capabilities->duplex = 1;
            }
        }
    }

    // Look up supported media types
    capabilities->numSupportedMediaTypes = 0;
    if (((attrptr = ippFindAttribute(response, "media-type-supported", IPP_TAG_KEYWORD)) != NULL)
            || ((attrptr = ippFindAttribute(response, "media-type-supported", IPP_TAG_NAME))
                    != NULL)) {
        for (i = 0; i < ippGetCount(attrptr); i++) {
            if (strcasestr(ippGetString(attrptr, i, NULL), "photographic-glossy")) {
                addMediaType(capabilities, MEDIA_PHOTO_GLOSSY);
            } else if (strcasestr(ippGetString(attrptr, i, NULL), "photo")) {
                addMediaType(capabilities, MEDIA_PHOTO);
            } else if (strcasestr(ippGetString(attrptr, i, NULL), "stationery")) {
                addMediaType(capabilities, MEDIA_PLAIN);
            }
        }
    }

    if (capabilities->numSupportedMediaTypes == 0) {
        // If no recognized media types were found, fall back to all 3 just in case
        addMediaType(capabilities, MEDIA_PLAIN);
        addMediaType(capabilities, MEDIA_PHOTO);
        addMediaType(capabilities, MEDIA_PHOTO_GLOSSY);
    }

    capabilities->numSupportedResolutions = 0;
    // only appears that SMM supports the pclm-source-resolution-supported attribute
    // if that is not present, use the printer-resolution-supported attribute to determine
    // if 300DPI is supported
    if ((attrptr = ippFindAttribute(response, "pclm-source-resolution-supported",
            IPP_TAG_RESOLUTION)) != NULL) {
        get_supportedPrinterResolutions(attrptr, capabilities);
    } else if ((attrptr = ippFindAttribute(response, "printer-resolution-supported",
            IPP_TAG_RESOLUTION)) != NULL) {
        get_supportedPrinterResolutions(attrptr, capabilities);
    }

    char ipp10[] = "1.0";
    char ipp11[] = "1.1";
    char ipp20[] = "2.0";

    if ((attrptr = ippFindAttribute(response, "ipp-versions-supported", IPP_TAG_KEYWORD)) != NULL) {
        unsigned char supportsIpp20 = 0;
        unsigned char supportsIpp11 = 0;
        unsigned char supportsIpp10 = 0;

        for (i = 0; i < ippGetCount(attrptr); i++) {
            if (strcmp(ipp10, ippGetString(attrptr, i, NULL)) == 0) {
                supportsIpp10 = 1;
            } else if (strcmp(ipp11, ippGetString(attrptr, i, NULL)) == 0) {
                supportsIpp11 = 1;
            } else if (strcmp(ipp20, ippGetString(attrptr, i, NULL)) == 0) {
                supportsIpp20 = 1;
            } else {
                LOGD("found another ipp version. %s", ippGetString(attrptr, i, NULL));
            }
            if (supportsIpp20) {
                capabilities->ippVersionMajor = 2;
                capabilities->ippVersionMinor = 0;
            } else if (supportsIpp11) {
                capabilities->ippVersionMajor = 1;
                capabilities->ippVersionMinor = 1;
            } else if (supportsIpp10) {
                capabilities->ippVersionMajor = 1;
                capabilities->ippVersionMinor = 0;
            } else {
                // default to 1.0
                capabilities->ippVersionMajor = 1;
                capabilities->ippVersionMinor = 0;
            }
        }
    }

    char epcl10[] = "1.0";
    if ((attrptr = ippFindAttribute(response, "epcl-version-supported", IPP_TAG_KEYWORD)) != NULL) {
        for (i = 0; i < ippGetCount(attrptr); i++) {
            LOGD("setting epcl_ipp_version (KEYWORD) %s", ippGetString(attrptr, i, NULL));

            // substring match because different devices implemented spec differently
            if (strstr(ippGetString(attrptr, i, NULL), epcl10) != NULL) {
                LOGD("setting epcl_ipp_version = 1");
                capabilities->ePclIppVersion = 1;
            }
        }
    }

    if ((attrptr = ippFindAttribute(response, "epcl-version-supported", IPP_TAG_TEXT)) != NULL) {
        for (i = 0; i < ippGetCount(attrptr); i++) {
            LOGD("setting epcl_ipp_verion (TEXT) %s", ippGetString(attrptr, i, NULL));

            // substring match because different devices implemented spec differently
            if (strstr(ippGetString(attrptr, i, NULL), epcl10) != NULL) {
                LOGD("setting epcl_ipp_verion = 1");
                capabilities->ePclIppVersion = 1;
            }
        }
    }

    if ((attrptr = ippFindAttribute(response, "media-col-default", IPP_TAG_BEGIN_COLLECTION)) !=
            NULL) {
        for (i = 0; i < ippGetCount(attrptr); i++) {
            LOGD("Gathering margins supported");

            ipp_t *collection = ippGetCollection(attrptr, i);

            for (j = 0, attrptr = ippFirstAttribute(collection);
                    (j < 4) && (attrptr != NULL); attrptr = ippNextAttribute(collection)) {
                if (strcmp("media-top-margin", ippGetName(attrptr)) == 0) {
                    capabilities->printerTopMargin = ippGetInteger(attrptr, 0);
                } else if (strcmp("media-bottom-margin", ippGetName(attrptr)) == 0) {
                    capabilities->printerBottomMargin = ippGetInteger(attrptr, 0);
                } else if (strcmp("media-left-margin", ippGetName(attrptr)) == 0) {
                    capabilities->printerLeftMargin = ippGetInteger(attrptr, 0);
                } else if (strcmp("media-right-margin", ippGetName(attrptr)) == 0) {
                    capabilities->printerRightMargin = ippGetInteger(attrptr, 0);
                }
            }
        }
    }

    if ((attrptr = ippFindAttribute(response, "media-size-name", IPP_TAG_KEYWORD)) != NULL) {
        capabilities->isMediaSizeNameSupported = true;
    } else {
        capabilities->isMediaSizeNameSupported = false;
    }

    // is strip length supported? if so, stored in capabilities
    if ((attrptr = ippFindAttribute(response, "pclm-strip-height-preferred",
            IPP_TAG_INTEGER)) != NULL) {
        LOGD("pclm-strip-height-preferred=%d", ippGetInteger(attrptr, 0));

        // if the strip height is 0, the device wants us to send the entire page in one band
        // (according to ePCL spec). Since our code doesn't currently support generating an entire
        // page in one band, set the strip height to the default value every device *should* support
        // also, for some reason our code crashes when it attempts to generate strips at 512 or
        // above. Therefore, limiting the upper bound strip height to 256
        if (ippGetInteger(attrptr, 0) == 0 || ippGetInteger(attrptr, 0) > 256) {
            capabilities->stripHeight = STRIPE_HEIGHT;
        } else {
            capabilities->stripHeight = ippGetInteger(attrptr, 0);
        }
    } else {
        capabilities->stripHeight = STRIPE_HEIGHT;
    }

    // what is the preferred compression method - jpeg, flate, rle
    if ((attrptr = ippFindAttribute(response, "pclm-compression-method-preferred",
            IPP_TAG_KEYWORD)) != NULL) {
        LOGD("pclm-compression-method-preferred=%s", ippGetString(attrptr, 0, NULL));
    }

    // is device able to rotate back page for duplex jobs?
    if ((attrptr = ippFindAttribute(response, "pclm-raster-back-side", IPP_TAG_KEYWORD)) != NULL) {
        LOGD("pclm-raster-back-side=%s", ippGetString(attrptr, 0, NULL));
        if (strcmp(ippGetString(attrptr, 0, NULL), "rotated") == 0) {
            capabilities->canRotateDuplexBackPage = 0;
            LOGD("Device cannot rotate back page for duplex jobs.");
        } else {
            capabilities->canRotateDuplexBackPage = 1;
        }
    }

    // look for full-bleed supported by looking for 0 on all margins
    bool topsupported = false, bottomsupported = false, rightsupported = false,
            leftsupported = false;
    if ((attrptr = ippFindAttribute(response, "media-top-margin-supported", IPP_TAG_INTEGER)) !=
            NULL) {
        for (i = 0; i < ippGetCount(attrptr); i++) {
            if (ippGetInteger(attrptr, i) == 0) {
                LOGD("Top Margin Supported");
                topsupported = true;
                break;
            }
        }
    }
    if ((attrptr = ippFindAttribute(response, "media-bottom-margin-supported", IPP_TAG_INTEGER)) !=
            NULL) {
        for (i = 0; i < ippGetCount(attrptr); i++) {
            if (ippGetInteger(attrptr, i) == 0) {
                LOGD("Bottom Margin Supported");
                bottomsupported = true;
                break;
            }
        }
    }
    if ((attrptr = ippFindAttribute(response, "media-right-margin-supported", IPP_TAG_INTEGER)) !=
            NULL) {
        for (i = 0; i < ippGetCount(attrptr); i++) {
            if (ippGetInteger(attrptr, i) == 0) {
                LOGD("Right Margin Supported");
                rightsupported = true;
                break;
            }
        }
    }
    if ((attrptr = ippFindAttribute(response, "media-left-margin-supported", IPP_TAG_INTEGER)) !=
            NULL) {
        for (i = 0; i < ippGetCount(attrptr); i++) {
            if (ippGetInteger(attrptr, i) == 0) {
                LOGD("Left Margin Supported");
                leftsupported = true;
                break;
            }
        }
    }

    if (topsupported && bottomsupported && rightsupported && leftsupported) {
        LOGD("full-bleed is supported");
        capabilities->borderless = 1;
    } else {
        LOGD("full-bleed is NOT supported");
    }

    if ((attrptr = ippFindAttribute(response, "printer-device-id", IPP_TAG_TEXT)) != NULL) {
        if (strstr(ippGetString(attrptr, 0, NULL), "PCL3GUI") != NULL) {
            capabilities->inkjet = 1;
        }
    } else if (capabilities->borderless == 1) {
        capabilities->inkjet = 1;
    }

    // determine if device prints pages face-down
    capabilities->faceDownTray = 1;
    if ((attrptr = ippFindAttribute(response, "output-bin-supported", IPP_TAG_KEYWORD)) != NULL) {
        if (strstr(ippGetString(attrptr, 0, NULL), "face-up") != NULL) {
            capabilities->faceDownTray = 0;
        }
    }

    // Determine supported document format details
    if ((attrptr = ippFindAttribute(response, "document-format-details-supported",
            IPP_TAG_KEYWORD)) != NULL) {
        for (i = 0; i < ippGetCount(attrptr); i++) {
            if (strcmp("document-source-application-name", ippGetString(attrptr, i, NULL)) == 0) {
                capabilities->docSourceAppName = 1;
            } else if (
                    strcmp("document-source-application-version", ippGetString(attrptr, i, NULL)) ==
                            0) {
                capabilities->docSourceAppVersion = 1;
            } else if (strcmp("document-source-os-name", ippGetString(attrptr, i, NULL)) == 0) {
                capabilities->docSourceOsName = 1;
            } else if (strcmp("document-source-os-version", ippGetString(attrptr, i, NULL)) == 0) {
                capabilities->docSourceOsVersion = 1;
            }
        }
    }
    debuglist_printerCapabilities(capabilities);
}

// Used in parse_printerUris
#define MAX_URIS 10
typedef struct {
    const char *uri;
    int valid;
} parsed_uri_t;

static void parse_printerUris(ipp_t *response, printer_capabilities_t *capabilities) {
    ipp_attribute_t *attrptr;
    int i;
    parsed_uri_t uris[MAX_URIS] = {0};

    if ((attrptr = ippFindAttribute(response, "printer-uri-supported", IPP_TAG_URI)) != NULL) {
        for (i = 0; i < MIN(ippGetCount(attrptr), MAX_URIS); i++) {
            uris[i].uri = ippGetString(attrptr, i, NULL);
            uris[i].valid = true;
        }
    }

    // If security or authentication is required (non-"none") at any URI, mark it invalid

    if ((attrptr = ippFindAttribute(response, "uri-security-supported", IPP_TAG_KEYWORD)) != NULL) {
        for (i = 0; i < MIN(ippGetCount(attrptr), MAX_URIS); i++) {
            if (strcmp("none", ippGetString(attrptr, i, NULL)) != 0) {
                LOGD("parse_printerUris %s invalid because sec=%s", uris[i].uri,
                        ippGetString(attrptr, i, NULL));
                uris[i].valid = false;
            }
        }
    }

    if ((attrptr = ippFindAttribute(response, "uri-authentication-supported", IPP_TAG_KEYWORD))
            != NULL) {
        for (i = 0; i < MIN(ippGetCount(attrptr), MAX_URIS); i++) {
            // Allow "none" and "requesting-user-name" only
            if (strcmp("none", ippGetString(attrptr, i, NULL)) != 0 &&
                    strcmp("requesting-user-name", ippGetString(attrptr, i, NULL)) != 0) {
                LOGD("parse_printerUris %s invalid because auth=%s", uris[i].uri,
                        ippGetString(attrptr, i, NULL));
                uris[i].valid = false;
            }
        }
    }

    // Find a valid URI and copy it into place.
    for (i = 0; i < MAX_URIS; i++) {
        if (uris[i].valid) {
            LOGD("parse_printerUris found %s", uris[i].uri);
            strlcpy(capabilities->printerUri, uris[i].uri, sizeof(capabilities->printerUri));
            break;
        }
    }
}

void debuglist_printerCapabilities(printer_capabilities_t *capabilities) {
    LOGD("printer make: %s", capabilities->make);
    LOGD("printer default media: %s", capabilities->mediaDefault);
    LOGD("canPrintPDF: %d", capabilities->canPrintPDF);
    LOGD("duplex: %d", capabilities->duplex);
    LOGD("canRotateDuplexBackPage: %d", capabilities->canRotateDuplexBackPage);
    LOGD("color: %d", capabilities->color);
    LOGD("canCopy: %d", capabilities->canCopy);
    LOGD("ippVersionMajor: %d", capabilities->ippVersionMajor);
    LOGD("ippVersionMinor: %d", capabilities->ippVersionMinor);
    LOGD("strip height: %d", capabilities->stripHeight);
    LOGD("faceDownTray: %d", capabilities->faceDownTray);
}

void debuglist_printerStatus(printer_state_dyn_t *printer_state_dyn) {
    const char *decoded = "unknown";
    if (printer_state_dyn->printer_status == PRINT_STATUS_INITIALIZING) {
        decoded = "Initializing";
    } else if (printer_state_dyn->printer_status == PRINT_STATUS_SHUTTING_DOWN) {
        decoded = "Shutting Down";
    } else if (printer_state_dyn->printer_status == PRINT_STATUS_UNABLE_TO_CONNECT) {
        decoded = "Unable To Connect";
    } else if (printer_state_dyn->printer_status == PRINT_STATUS_UNKNOWN) {
        decoded = "Unknown";
    } else if (printer_state_dyn->printer_status == PRINT_STATUS_OFFLINE) {
        decoded = "Offline";
    } else if (printer_state_dyn->printer_status == PRINT_STATUS_IDLE) {
        decoded = "Idle";
    } else if (printer_state_dyn->printer_status == PRINT_STATUS_PRINTING) {
        decoded = "Printing";
    } else if (printer_state_dyn->printer_status == PRINT_STATUS_OUT_OF_PAPER) {
        decoded = "Out Of Paper";
    } else if (printer_state_dyn->printer_status == PRINT_STATUS_OUT_OF_INK) {
        decoded = "Out Of Ink";
    } else if (printer_state_dyn->printer_status == PRINT_STATUS_JAMMED) {
        decoded = "Jammed";
    } else if (printer_state_dyn->printer_status == PRINT_STATUS_DOOR_OPEN) {
        decoded = "Door Open";
    } else if (printer_state_dyn->printer_status == PRINT_STATUS_SVC_REQUEST) {
        decoded = "Service Request";
    }
    LOGD("printer status: %d (%s)", printer_state_dyn->printer_status, decoded);

    int idx = 0;
    for (idx = 0; idx < (PRINT_STATUS_MAX_STATE + 1); idx++) {
        if (PRINT_STATUS_MAX_STATE != printer_state_dyn->printer_reasons[idx]) {
            LOGD("printer_reasons (%d): %d", idx, printer_state_dyn->printer_reasons[idx]);
        }
    }
}

/*
 * Handle server certificate information.
 */
static int ipp_server_cert_cb(http_t *http, void *tls, cups_array_t *certs, void *user_data) {
    wprint_connect_info_t *connect_info = (wprint_connect_info_t *)user_data;
    int error = 0;
    if (connect_info->validate_certificate) {
        http_credential_t *credential = cupsArrayFirst(certs);
        if (credential) {
            LOGD("ipp_server_cert_cb: validate_certificate (len=%d)", credential->datalen);
            error = connect_info->validate_certificate(connect_info, credential->data, credential->datalen);
        }
    }
    return error;
}

http_t *ipp_cups_connect(const wprint_connect_info_t *connect_info, char *printer_uri,
        unsigned int uriLength) {
    const char *uri_path;
    http_t *curl_http = NULL;

    cupsSetServerCertCB(ipp_server_cert_cb, (void *)connect_info);

    if ((connect_info->uri_path == NULL) || (strlen(connect_info->uri_path) == 0)) {
        uri_path = DEFAULT_IPP_URI_RESOURCE;
    } else {
        uri_path = connect_info->uri_path;
    }

    int ippPortNumber = ((connect_info->port_num == IPP_PORT) ? ippPort() : connect_info->port_num);

    if (strstr(connect_info->uri_scheme,IPPS_PREFIX) != NULL) {
        curl_http = httpConnectEncrypt(connect_info->printer_addr, ippPortNumber, HTTP_ENCRYPTION_ALWAYS);

        // If ALWAYS doesn't work, fall back to REQUIRED
        if (curl_http == NULL) {
            curl_http = httpConnectEncrypt(connect_info->printer_addr, ippPortNumber, HTTP_ENCRYPT_REQUIRED);
        }
    } else {
        curl_http = httpConnectEncrypt(connect_info->printer_addr, ippPortNumber, HTTP_ENCRYPTION_IF_REQUESTED);
    }

    httpSetTimeout(curl_http, (double)connect_info->timeout / 1000, NULL, 0);
    httpAssembleURIf(HTTP_URI_CODING_ALL, printer_uri, uriLength, connect_info->uri_scheme, NULL,
            connect_info->printer_addr, ippPortNumber, uri_path);

    if (curl_http == NULL) {
        LOGD("ipp_cups_connect failed addr=%s port=%d", connect_info->printer_addr, ippPortNumber);
    }

    cupsSetServerCertCB(NULL, NULL);
    return curl_http;
}

/*
 * Send a request using cupsSendRequest(). Loop if we get NULL or CONTINUE. Does not delete
 * the request.
 */
static ipp_t *ippSendRequest(http_t *http, ipp_t *request, char *resource) {
    ipp_t *response = NULL;
    http_status_t result;
    bool retry;

    do {
        retry = false;
        result = cupsSendRequest(http, request, resource, ippLength(request));
        if (result != HTTP_ERROR) {
            response = cupsGetResponse(http, resource);
            result = httpGetStatus(http);
        }

        if (result == HTTP_CONTINUE && response == NULL) {
            // We need to retry when this happens.
            LOGD("ippSendRequest: (Continue with NULL response) Retry");
            retry = true;
        } else if (result == HTTP_ERROR || result >= HTTP_BAD_REQUEST) {
            _cupsSetHTTPError(result);
            break;
        }

        if (http->state != HTTP_WAITING) {
            httpFlush(http);
        }
    } while (retry);

    return response;
}

/*
 * Call ippDoCupsIORequest, repeating if a failure occurs based on failure conditions, and
 * returning the response (or NULL if it failed).
 *
 * Does not free the request, and the caller must call ippDelete to free any valid response.
 */
ipp_t *ipp_doCupsRequest(http_t *http, ipp_t *request, char *http_resource, char *printer_uri) {
    ipp_status_t ipp_status;
    ipp_t *response = NULL;
    int service_unavailable_retry_count = 0;
    int bad_request_retry_count = 0;
    int internal_error_retry_count = 0;
    ipp_version_state ipp_version_supported = IPP_VERSION_RESOLVED;

    // Fail if any of these parameters are NULL
    if (http == NULL || request == NULL || http_resource == NULL || printer_uri == NULL) {
        return NULL;
    }

    do {
        // Give up immediately if wprint is done.
        if (!wprintIsRunning()) return NULL;

        // This is a no-op until we hit the error IPP_VERSION_NOT_SUPPORTED and retry.
        if (set_ipp_version(request, printer_uri, http, ipp_version_supported) != 0) {
            // We tried to find the correct IPP version by doing a series of get attribute
            // requests but they all failed... we give up.
            LOGE("ipp_doCupsRequest: set_ipp_version!=0, version not set");
            break;
        }

        response = ippSendRequest(http, request, http_resource);
        if (response == NULL) {
            ipp_status = cupsLastError();
            if (ipp_status == IPP_INTERNAL_ERROR || ipp_status == HTTP_ERROR) {
                internal_error_retry_count++;
                if (internal_error_retry_count > IPP_INTERNAL_ERROR_MAX_RETRIES) {
                    break;
                }

                LOGE("ipp_doCupsRequest: %s %d received, retry %d of %d",
                        printer_uri, ipp_status, internal_error_retry_count,
                        IPP_INTERNAL_ERROR_MAX_RETRIES);
                continue;
            } else if (ipp_status == IPP_SERVICE_UNAVAILABLE) {
                service_unavailable_retry_count++;
                if (service_unavailable_retry_count > IPP_SERVICE_ERROR_MAX_RETRIES) {
                    break;
                }

                LOGE("ipp_doCupsRequest: %s IPP_SERVICE_UNAVAILABLE received, retrying %d of %d",
                        printer_uri, service_unavailable_retry_count,
                        IPP_SERVICE_ERROR_MAX_RETRIES);
                continue;
            } else if (ipp_status == IPP_BAD_REQUEST) {
                bad_request_retry_count++;
                if (bad_request_retry_count > IPP_BAD_REQUEST_MAX_RETRIES) {
                    break;
                }

                LOGD("ipp_doCupsRequest: %s IPP_BAD_REQUEST received. retry (%d) of (%d)",
                        printer_uri, bad_request_retry_count, IPP_BAD_REQUEST_MAX_RETRIES);
                continue;
            } else if (ipp_status == IPP_NOT_FOUND) {
                LOGE("ipp_doCupsRequest: %s IPP_NOT_FOUND received.", printer_uri);
                break;
            }
        } else {
            ipp_status = cupsLastError();
            if (ipp_status == IPP_BAD_REQUEST) {
                bad_request_retry_count++;
                LOGE("ipp_doCupsRequest: %s IPP_BAD_REQUEST received. retry (%d) of (%d)",
                        printer_uri, bad_request_retry_count, IPP_BAD_REQUEST_MAX_RETRIES);
                if (bad_request_retry_count > IPP_BAD_REQUEST_MAX_RETRIES) {
                    break;
                }

                ippDelete(response);
                response = NULL;
                continue;
            } else if (ipp_status == IPP_VERSION_NOT_SUPPORTED) {
                ipp_version_supported = IPP_VERSION_UNSUPPORTED;
                ippDelete(response);
                response = NULL;
                continue;
            }
        }
        break;
    } while (1);

    return response;
}