
/******************************************************************************\

Copyright 2015 HP Development Company, L.P.

This program is free software; you can redistribute it and/or modify it under
the terms of version 2 of the GNU General Public License as published by the
Free Software Foundation.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program;
if not, write to:
Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor
Boston, MA 02110-1301, USA.

\******************************************************************************/

#include <cups/cups.h>
#include <cups/language.h>
#include <cups/ppd.h>
#include <syslog.h>
#include <stdarg.h>
#include <sys/types.h>
#include <pwd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#include <sys/time.h>
#include <time.h>
#include <unistd.h>

#include "hp_ipp.h"
#include "hp_ipp_i.h"
#define O_BINARY 0
#define _STRINGIZE(x) #x
#define STRINGIZE(x) _STRINGIZE(x)
#define hplip_strlcpy(dst, src, size) \
    do { \
        if (!memccpy(dst, src, '\0', size)) \
		dst[size - 1] = '\0'; \
    } while (0)

http_t *acquireCupsInstance()
{
    if (http == NULL)
    {
        http = httpConnectEncrypt(cupsServer(), ippPort(), cupsEncryption());
    }

    return http;
}

void _releaseCupsInstance()
{
    if (http)
    {
        httpClose(http);
    }

    http = NULL;
}

/*
 * 'validate_name()' - Make sure the printer name only contains valid chars.
 */
static int                      /* O - 0 if name is no good, 1 if name is good */
validate_name(const char *name) /* I - Name to check */
{
    return 1; // TODO: Make it work with utf-8 encoding
}

const char *getCupsErrorString(int status)
{
    return ippErrorString(status);
}

void freePrinterList(printer_t *list)
{
    printer_t *temp;
    printer_t *elem = list;

    while (elem != NULL)
    {
        temp = elem;
        elem = elem->next;
        free(temp);
    }
}

int addCupsPrinter(char *name, char *device_uri, char *location, char *ppd_file, char *model, char *info)
{
    char printer_uri[HTTP_MAX_URI];
    cups_lang_t *language; /* Default language */
    int ret_status = 0;
    ipp_status_t status = IPP_BAD_REQUEST;
    ipp_t *request = NULL, /* IPP Request */
        *response = NULL;  /* IPP Response */

    if ((strlen(ppd_file) > 0 && strlen(model) > 0) ||
        (strlen(ppd_file) == 0 && strlen(model) == 0))
    {
        // status_str = "Invalid arguments: specify only ppd_file or model, not both or neither";
        goto abort;
    }

    if (!validate_name(name))
    {
        // status_str = "Invalid printer name";
        goto abort;
    }

     if ( info == NULL )
        hplip_strlcpy( info, name, sizeof(info));

    sprintf(printer_uri, "ipp://localhost/printers/%s", name);

    cupsSetUser("root");
    /* Connect to the HTTP server */
    if (acquireCupsInstance() == NULL)
    {
        // status_str = "Unable to connect to CUPS server";
        goto abort;
    }

    /* Assemble the IPP request */
    request = ippNew();
    language = cupsLangDefault();

    ippSetOperation(request, CUPS_ADD_PRINTER);
    ippSetRequestId(request, 1);

    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
                 "attributes-charset", NULL, cupsLangEncoding(language));

    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
                 "attributes-natural-language", NULL, language->language);

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

    ippAddInteger(request, IPP_TAG_PRINTER, IPP_TAG_ENUM,
                  "printer-state", IPP_PRINTER_IDLE);

    ippAddBoolean(request, IPP_TAG_PRINTER, "printer-is-accepting-jobs", 1);

    ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_URI, "device-uri", NULL,
                 device_uri);

    ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-info", NULL,
                 info);

    ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-location", NULL,
                 location);

    if (strlen(model) > 0)
    {
        ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME, "ppd-name", NULL, model);

        /* Send the request and get a response. */
        response = cupsDoRequest(http, request, "/admin/");
    }
    else
    {
        /* Send the request and get a response. */
        response = cupsDoFileRequest(http, request, "/admin/", ppd_file);
    }

    if (response == NULL)
        status = cupsLastError();
    else
        status = ippGetStatusCode(response);

    // If user cancels the authentication pop-up, changing error code to IPP_NOT_AUTHENTICATED from IPP_FORBIDDEN
    if (status == IPP_FORBIDDEN && auth_cancel_req)
    {
        status = IPP_NOT_AUTHENTICATED;
        auth_cancel_req = 0; // Reseting cancel request.
    }

    if (status <= IPP_OK_CONFLICT)
    {
        status = IPP_OK;
        ret_status = 0;
    }
    else
    {
        ret_status = 1;
    }

abort:
    if (response != NULL)
        ippDelete(response);

    return ret_status;
}

int delCupsPrinter(char *pr_name)
{
    int ret_status;
    cups_lang_t *language; /* Default language */
    char uri[HTTP_MAX_URI];
    const char *username = NULL;
    ipp_t *request = NULL, /* IPP Request */
        *response = NULL;  /* IPP Response */

    if (!validate_name(pr_name))
    {
        goto abort;
    }

    username = cupsUser();

    cupsSetUser("root");
    /* Connect to the HTTP server */
    if (acquireCupsInstance() == NULL)
    {
        goto abort;
    }
    snprintf(uri, sizeof(uri), "ipp://localhost/printers/%s", pr_name);

    /*
     * Build a CUPS_DELETE_PRINTER request, which requires the following
     * attributes:
     *
     *    attributes-charset
     *    attributes-natural-language
     *    printer-uri
     */
    request = ippNew();

    ippSetOperation(request, CUPS_DELETE_PRINTER);
    ippSetRequestId(request, 1);

    language = cupsLangDefault();

    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
                 "attributes-charset", NULL, cupsLangEncoding(language));

    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
                 "attributes-natural-language", NULL, language->language);

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

    /*
     * Do the request and get back a response...
     */
    response = cupsDoRequest(http, request, "/admin/");

    if (response == NULL)
        ret_status = cupsLastError();
    else
        ret_status = ippGetStatusCode(response);

    // If user cancels the authentication pop-up, changing error code to IPP_NOT_AUTHENTICATED from IPP_FORBIDDEN
    if (ret_status == IPP_FORBIDDEN && auth_cancel_req)
    {
        ret_status = IPP_NOT_AUTHENTICATED;
        auth_cancel_req = 0; // Reseting cancel request.
    }

    if (ret_status <= IPP_OK_CONFLICT)
        ret_status = IPP_OK;

abort:
    if (username)
        cupsSetUser(username);

    if (response != NULL)
        ippDelete(response);

    return ret_status;
}

int setDefaultCupsPrinter(char *pr_name)
{
    int ret_status = 0;
    char uri[HTTP_MAX_URI]; /* URI for printer/class */
    ipp_t *request = NULL,  /* IPP Request */
        *response = NULL;   /* IPP Response */
    cups_lang_t *language;  /* Default language */
    const char *username = NULL;

    if (!validate_name(pr_name))
    {
        goto abort;
    }

    username = cupsUser();

    cupsSetUser("root");
    /* Connect to the HTTP server */
    if (acquireCupsInstance() == NULL)
    {
        goto abort;
    }

    /*
     * Build a CUPS_SET_DEFAULT request, which requires the following
     * attributes:
     *
     *    attributes-charset
     *    attributes-natural-language
     *    printer-uri
     */

    snprintf(uri, sizeof(uri), "ipp://localhost/printers/%s", pr_name);

    request = ippNew();

    ippSetOperation(request, CUPS_SET_DEFAULT);
    ippSetRequestId(request, 1);

    language = cupsLangDefault();

    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
                 "attributes-charset", NULL, "utf-8"); // cupsLangEncoding( language ) );

    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
                 "attributes-natural-language",
                 // NULL, language != NULL ? language->language : "en");
                 NULL, language->language);

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

    /*
     * Do the request and get back a response...
     */

    response = cupsDoRequest(http, request, "/admin/");
    if (response == NULL)
        ret_status = cupsLastError();
    else
        ret_status = ippGetStatusCode(response);

    // If user cancels the authentication pop-up, changing error code to IPP_NOT_AUTHENTICATED from IPP_FORBIDDEN
    if (ret_status == IPP_FORBIDDEN && auth_cancel_req)
    {
        ret_status = IPP_NOT_AUTHENTICATED;
        auth_cancel_req = 0; // Reseting cancel request.
    }

    // ToDo: When you assign ret_status with IPP_OK, the caller will lose getting the actual status
    if (ret_status <= IPP_OK_CONFLICT)
        ret_status = IPP_OK;

abort:
    if (username)
        cupsSetUser(username);

    if (response != NULL)
        ippDelete(response);

    return ret_status;
}

int controlCupsPrinter(char *pr_name, int op)
{
    ipp_t *request = NULL,  /* IPP Request */
        *response = NULL;   /* IPP Response */
    char uri[HTTP_MAX_URI]; /* URI for printer/class */
    cups_lang_t *language;
    const char *username = NULL;
    int ret_status = 0; // ToDo: Make use of IPP_BAD_REQUEST while changing to proper status codes

    if (!validate_name(pr_name))
    {
        goto abort;
    }

    username = cupsUser();
    cupsSetUser("root");
    /* Connect to the HTTP server */
    if (acquireCupsInstance() == NULL)
    {
        goto abort;
    }

    request = ippNew();

    ippSetOperation(request, op);
    ippSetRequestId(request, 1);

    language = cupsLangDefault();

    snprintf(uri, sizeof(uri), "ipp://localhost/printers/%s", pr_name);

    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
                 "attributes-charset", NULL, cupsLangEncoding(language));

    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
                 "attributes-natural-language", NULL, language->language);

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

    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
                 "requesting-user-name", NULL, cupsUser());

    if (op == IPP_PURGE_JOBS)
        ippAddBoolean(request, IPP_TAG_OPERATION, "purge-jobs", 1);

    response = cupsDoRequest(http, request, "/admin/");

    if (response == NULL)
        ret_status = cupsLastError();
    else
        ret_status = ippGetStatusCode(response);

    // If user cancels the authentication pop-up, changing error code to IPP_NOT_AUTHENTICATED from IPP_FORBIDDEN
    if (ret_status == IPP_FORBIDDEN && auth_cancel_req)
    {
        ret_status = IPP_NOT_AUTHENTICATED;
        auth_cancel_req = 0; // Reseting cancel request.
    }

abort:
    if (username)
        cupsSetUser(username);

    if (response != NULL)
        ippDelete(response);

    return ret_status;
}

/*
 * 'getCupsPrinters()' - Get installed cups printers.
 *
 * This function sends a CUPS_GET_PRINTERS request and return IPP response.
 * It also updates the number of cups printers found in  count variable.
 *
 */
ipp_t *__getCupsPrinters()
{
    ipp_t *request = NULL;  /* IPP request object */
    ipp_t *response = NULL; /* IPP response object */
    ipp_attribute_t *attr;  /* Current IPP attribute */

    static const char *attrs[] = /* Requested attributes */
        {
            "printer-info",
            "printer-location",
            "printer-make-and-model",
            "printer-state",
            "printer-name",
            "device-uri",
            "printer-uri-supported",
            "printer-is-accepting-jobs",
        };

    /* Connect to the HTTP server */
    if (acquireCupsInstance() == NULL)
    {
        goto abort;
    }

    /* Assemble the IPP request */
    request = ippNewRequest(CUPS_GET_PRINTERS);

    if (request == NULL)
        goto abort;

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

    /* Send the request and get a response. */
    response = cupsDoRequest(http, request, "/");

abort:

    return response;
}

/*
 * 'parsePrinterAttributes()'
 *
 * This function parses the IPP response and updates the printer_list buffer.
 *
 */
int __parsePrinterAttributes(ipp_t *response, printer_t **printer_list)
{
    ipp_attribute_t *attr;     
    ipp_tag_t grp_tag;
    ipp_tag_t val_tag;
    char *attr_name;

    printer_t *t_printer = NULL;
    printer_t *t_printer_list = NULL;

    for ( attr = ippFirstAttribute(response); attr != NULL; attr = ippNextAttribute(response) )
    {
        if ( ippGetGroupTag(attr) != IPP_TAG_PRINTER )
            continue;

	t_printer = (printer_t *) calloc(1, sizeof(printer_t));
        if (t_printer == NULL) {
            BUG("Memory allocation for printer struct failed!\n");
            goto abort;
        }

	if (t_printer_list == NULL) {
	    t_printer_list = t_printer;
            *printer_list = t_printer_list;
        } else {
	    t_printer_list->next = t_printer;
	    t_printer_list = t_printer;
	}

        while ( attr != NULL && ippGetGroupTag(attr) == IPP_TAG_PRINTER ) {
             attr_name = ippGetName(attr);
             val_tag  = ippGetValueTag(attr); 

             if ( strcmp(attr_name, "printer-name") == 0 &&
                                        val_tag == IPP_TAG_NAME ) {
                  hplip_strlcpy(t_printer->name, ippGetString(attr, 0, NULL), sizeof(t_printer->name) );
             }
             else if ( strcmp(attr_name, "device-uri") == 0 &&
                                         val_tag == IPP_TAG_URI ) {
                  hplip_strlcpy(t_printer->device_uri, ippGetString(attr, 0, NULL), sizeof(t_printer->device_uri) );
             }
             else if ( strcmp(attr_name, "printer-uri-supported") == 0 &&
                                                 val_tag == IPP_TAG_URI ) {
                  hplip_strlcpy(t_printer->printer_uri, ippGetString(attr, 0, NULL), sizeof(t_printer->printer_uri) );
             }
             else if ( strcmp(attr_name, "printer-info") == 0 &&
                                        val_tag == IPP_TAG_TEXT ) {
                  hplip_strlcpy(t_printer->info, ippGetString(attr, 0, NULL), sizeof(t_printer->info) );
             }
             else if ( strcmp(attr_name, "printer-location") == 0 &&
                                           val_tag == IPP_TAG_TEXT ) {
                  hplip_strlcpy(t_printer->location, ippGetString(attr, 0, NULL), sizeof(t_printer->location) );
             }
             else if ( strcmp(attr_name, "printer-make-and-model") == 0 &&
                                                  val_tag == IPP_TAG_TEXT ) {
                  hplip_strlcpy(t_printer->make_model, ippGetString(attr, 0, NULL), sizeof(t_printer->make_model));
             } 
             else if ( strcmp(attr_name, "printer-state") == 0 &&
                                             val_tag == IPP_TAG_ENUM ) {
                   t_printer->state = ( ipp_state_t ) ippGetInteger(attr, 0);
             }
             else if (!strcmp(attr_name, "printer-is-accepting-jobs") &&
                                             val_tag == IPP_TAG_BOOLEAN) {
                   t_printer->accepting = ippGetBoolean(attr, 0);
             }

             attr = ippNextAttribute(response);
        }
    }

abort:
    return 0;
}

int getCupsPrinters(printer_t **printer_list)
{
    ipp_t *response = NULL;
    int ret_status = 0;

    response = __getCupsPrinters();

    if (response)
    {
        ret_status = __parsePrinterAttributes(response, printer_list);
        ippDelete(response);
    }

    return ret_status;
}

/*
 * 'initializeIPPRequest()' - Initialize request with those attributes which
 * are common for all requests .
 */
void initializeIPPRequest(ipp_t *request)
{

    if (request)
    {
        // Set common request fields
        ippSetVersion(request, 2, 0);
        ippSetRequestId(request, 100);
    }
}

/*
 * 'createFaxDetailRequest()' - Create IPP request and update the same with values needed for getting fax attributes.
 */
ipp_t *createFaxDetailRequest(const char *printer_name)
{
    char uri[HTTP_MAX_URI] = {0};
    ipp_t *request = NULL;       /* IPP request object */
    static const char *attrs[] = /* Requested attributes */
        {
            "all",
            "printer-fax-modem-number",
            "printer-fax-modem-name",
        };

    /* Assemble the IPP request */
    request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
    initializeIPPRequest(request);
    if (request)
    {
        snprintf(uri, HTTP_MAX_URI, "ipp://localhost/printers/%s/ipp/faxout", printer_name);
        // snprintf(uri, HTTP_MAX_URI, "ipp://146.205.2.54:631/ipp/faxout");
        ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
        // ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-fax-modem-number", NULL, uuid);
        // ippAddStrings( request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", sizeof( attrs ) / sizeof( attrs[ 0 ] ),  NULL, attrs );
    }

    return request;
}

/*
 * 'createDeviceStatusRequest()' - Create IPP request and update the same with values needed for getting device status attributes.
 */
ipp_t *createDeviceStatusRequest(const char *printer_name)
{
    char uri[HTTP_MAX_URI] = {0};
    ipp_t *request = NULL; /* IPP request object */

    static const char *attrs[] = /* Requested attributes */
        {
            "marker-names",
            "marker-types",
            "marker-levels",
            "marker-low-levels",
            "printer-state",
            "printer-state-reasons",
        };

    /* Assemble the IPP request */
    request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
    initializeIPPRequest(request);
    if (request)
    {
        snprintf(uri, HTTP_MAX_URI, "ipp://localhost/printers/%s", printer_name);
        ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
        ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", sizeof(attrs) / sizeof(attrs[0]), NULL, attrs);
    }

    return request;
}

/*
 * 'getDeviceFaxModemAttributes()' - Get Device Fax Modem Attributes.
 *
 * This function sends a IPP_GET_PRINTER_ATTRIBUTES request  and request for
 * printer-fax-modem-number,printer-fax-modem-name.
 * In addition to this it also updates the attribute count in the response.
 */
ipp_t *getDeviceFaxModemAttributes(char *device_uri, char *printer_name, int *count)
{
    ipp_t *request = NULL;  /* IPP request object */
    ipp_t *response = NULL; /* IPP response object */
    ipp_attribute_t *attr;  /* Current IPP attribute */
    int max_count = 0;

    // Create Device Status Request
    request = createFaxDetailRequest(printer_name);
    if (request == NULL)
        goto abort;

    // Send request to the server based on connection
    if (strcasestr(device_uri, ":/usb") != NULL)
    {
        response = usbDoRequest(request, device_uri, "/ipp/faxout");
    }
    else if (strcasestr(device_uri, ":/net") != NULL)
    {
        response = networkDoRequest(request, device_uri, "/ipp/faxout");
    }
    else
    {
        BUG("Invalid device URI (%s)\n", device_uri);
        goto abort;
    }

    // Find out the attribute count.
    if (response)
        for (attr = ippFirstAttribute(response); attr != NULL; attr = ippNextAttribute(response))
            max_count++;

abort:
    *count = max_count;
    return response;
}

/*
 * 'networkDoRequestFax()' - Send ipp request to a network device and read back the response.
 *
 * This function makes use of cups API (cupsDoRequest) to send ipp request and get back the ipp response.
 */
ipp_t *networkDoRequestFax(ipp_t *request, char *device_uri)
{
    ipp_t *response = NULL; /* IPP response object */
    char ip[HPMUD_LINE_SIZE] = {0};
    http_t *http = NULL; /* HTTP object */

    // Get IP address
    hpmud_get_uri_datalink(device_uri, ip, HPMUD_LINE_SIZE);

    // Create http connection
    http = httpConnectEncrypt(ip, ippPort(), cupsEncryption());
    if (http == NULL)
        goto abort;

    // Send the request and get a response.
    if ((response = cupsDoRequest(http, request, "/ipp/faxout")) == NULL)
    {
        goto abort;
    }

abort:

    return response;
}

/*
 * 'ExtractIPPData()' - Extract IPP Data from IPP raw response. It will have HTTP header and may have chunk data.
 */
HPIPP_RESULT ExtractIPPData(char *data, int *length)
{
    char *ptr = data;
    int chunked = 0;
    int content_length = 0;
    HPIPP_RESULT status = HPIPP_OK;

    status = parseResponseHeader(data, &content_length, &chunked, NULL);
    if (HPIPP_OK != status)
        return status;

    DBG("Transfer-Encoding (chunked = %d), DataLength(with http header) = [%d]\n", chunked, *length);

    // Remove http header.
    ptr = strstr(data, CRLFCRLF);
    if (ptr)
    {
        ptr += CRLFCRLF_LENGTH;
        *length = *length - (ptr - data);
        memmove(data, ptr, *length);
    }

    if (chunked)
    {
        status = removeChunkInfo(data, length);
    }

    return status;
}

/*
 * 'parseResponseHeader()' - Parse HTTP Header and update content_length and chunk flag.
 *
 */
HPIPP_RESULT parseResponseHeader(char *header, int *content_length, int *chunked, int *header_size)
{
    HPIPP_RESULT hr = HPIPP_OK;
    char *ptr = NULL;

    if (!header || !content_length || !chunked)
        return HPIPP_ERROR;

    if (header_size)
    {
        ptr = strstr(header, CRLFCRLF);
        *header_size = (ptr) ? (ptr - header + CRLFCRLF_LENGTH) : 0;
    }

    if (strcasestr(header, "Transfer-Encoding:") && strcasestr(header, "chunked"))
    {
        *chunked = 1;
        *content_length = 0;
    }
    else if (ptr = strstr(header, "Content-Length:"))
    {
        *content_length = strtol(ptr + strlen("Content-Length:"), NULL, BASE_DECIMAL);
        *chunked = 0;
    }
    else
    {
        DBG("parseResponseHeader: Could not find Transfer-Encoding: chunked or Content-Length: \n");
        hr = HPIPP_ERROR;
    }

    DBG("chunked = [%d] , Content_length = [%d]\n", *chunked, *content_length);
    return hr;
}

/*
 * 'getDeviceStatusAttributes()' - Get Device Status Attributes.
 *
 * This function sends a IPP_GET_PRINTER_ATTRIBUTES request  and request for
 * marker-names, marker-types, marker-levels, marker-low-levels, printer-state, printer-state-reasons.
 * In addition to this it also updates the attribute count in the response.
 */
ipp_t *getDeviceStatusAttributes(char *device_uri, char *printer_name, int *count)
{
    ipp_t *request = NULL;  /* IPP request object */
    ipp_t *response = NULL; /* IPP response object */
    ipp_attribute_t *attr;  /* Current IPP attribute */
    int max_count = 0;

    // Create Device Status Request
    request = createDeviceStatusRequest(printer_name);
    if (request == NULL)
        goto abort;

    // Send request to the server based on connection
    if (strcasestr(device_uri, ":/usb") != NULL)
    {
        response = usbDoRequest(request, device_uri, "/ipp/printer");
    }
    else if (strcasestr(device_uri, ":/net") != NULL)
    {
        response = networkDoRequest(request, device_uri, "/ipp/printer");
    }
    else
    {
        BUG("Invalid device URI (%s)\n", device_uri);
        goto abort;
    }

    // Find out the attribute count.
    if (response)
        for (attr = ippFirstAttribute(response); attr != NULL; attr = ippNextAttribute(response))
            max_count++;

abort:
    *count = max_count;
    return response;
}

/*************************** UTILS ***************************/

/*
 * 'usbDoRequest()' - Send ipp request to a usb device and read back the response.
 *
 * This function sends ipp request to a device correponding to caller provided device_uri and reads back the response.
 * This function then parses and stores the raw response into ipp_t (response) and sends back the same.
 */
ipp_t *usbDoRequest(ipp_t *request, char *device_uri, const char *resource)
{
    ipp_t *response = NULL; /* IPP response object */
    ipp_state_t state;
    raw_ipp raw_request;
    raw_ipp raw_response;

    memset(&raw_request, 0, sizeof(raw_request));
    memset(&raw_response, 0, sizeof(raw_response));

    // Convert ipp_t request structure into raw ipp format
    state = ippWriteIO(&raw_request, (ipp_iocb_t)raw_ipp_request_callback, 1, NULL, request);
    if (IPP_ERROR == state)
    {
        BUG("ippWriteIO Failed...\n");
        goto abort;
    }

    // Prepend http header to the raw request buffer
    if (prepend_http_header(&raw_request, resource) != HPIPP_OK)
        goto abort;

    // Send request to the device and get back the response
    if (HPMUD_R_OK != sendUSBRequest(raw_request.data, raw_request.data_length, &raw_response, device_uri))
    {
        goto abort;
    }

    // Convert Raw data into ipp_t response structure format
    response = ippNew();
    state = ippReadIO(&raw_response, (ipp_iocb_t)raw_ipp_response_read_callback, 1, NULL, response);
    if (response && (IPP_ERROR == state))
    {
        BUG("ippWriteIO Failed...\n");
        ippDelete(response);
        response = NULL;
    }

abort:
    return response;
}

/*
 * 'networkDoRequest()' - Send ipp request to a network device and read back the response.
 *
 * This function makes use of cups API (cupsDoRequest) to send ipp request and get back the ipp response.
 */
ipp_t *networkDoRequest(ipp_t *request, char *device_uri, const char *resource)
{
    ipp_t *response = NULL; /* IPP response object */
    char ip[HPMUD_LINE_SIZE] = {0};
    http_t *http = NULL; /* HTTP object */

    // Get IP address
    hpmud_get_uri_datalink(device_uri, ip, HPMUD_LINE_SIZE);

    // Create http connection
    http = httpConnectEncrypt(ip, ippPort(), cupsEncryption());
    if (http == NULL)
        goto abort;

    // Send the request and get a response.
    if ((response = cupsDoRequest(http, request, resource)) == NULL)
    {
        goto abort;
    }

abort:

    return response;
}

/*
 * 'sendUSBRequest()' - Send request and read response.
 *
 */
enum HPMUD_RESULT sendUSBRequest(char *buf, int size, raw_ipp *responseptr, char *device_uri)
{
    HPMUD_DEVICE hd = 0;
    HPMUD_CHANNEL cd;
    enum HPMUD_RESULT stat = HPMUD_R_OK;
    int device_already_open = 0;

    DBG("sendUSBRequest: buf = %p, size = %d, responseptr = %p, device_uri = %s\n", buf, size, responseptr, device_uri);

    /* Open hp device. */
    if ((stat = hpmud_open_device(device_uri, HPMUD_RAW_MODE, &hd)) != HPMUD_R_OK)
    {
        if (stat == HPMUD_R_INVALID_STATE)
        {
            // hpmud reports HPMUD_R_INVALID_STATE error when we try to open the device again which was already opened by tools like hp-levels
            /// or hp-toolbox. We can use this already opened device but we need to make sure we do not close the device after use.
            hd = 1;
            device_already_open = 1;
        }
        else
        {
            BUG("Device open failed with status code = %d\n", stat);
            goto abort;
        }
    }

    /* Open ipp channel. */
    if ((stat = hpmud_open_channel(hd, HPMUD_S_IPP_CHANNEL, &cd)) != HPMUD_R_OK)
    {
        if ((stat = hpmud_open_channel(hd, HPMUD_S_IPP_CHANNEL2, &cd)) != HPMUD_R_OK)
        {
            BUG("Channel open failed with status code = %d\n", stat);
            goto abort;
        }
    }

    // Write request on the channel
    if ((stat = writeChannel(buf, size, hd, cd)) != HPMUD_R_OK)
    {
        BUG("Channel write failed with status code = %d\n", stat);
        goto abort;
    }

    // Read the response from the channel
    if ((stat = readChannel(responseptr, hd, cd)) != HPMUD_R_OK)
    {
        BUG("Channel read failed with status code = %d\n", stat);
        // goto abort;
    }

    // Remove header and chunking information from the raw IPP response
    ExtractIPPData(responseptr->data, &responseptr->data_length);
    stat = HPMUD_R_OK;

abort:

    if (cd > 0)
        hpmud_close_channel(hd, cd);
    if (hd > 0 && !device_already_open)
        hpmud_close_device(hd);

    return stat;
}

/*
 * 'sendUSBRequest()' - Send request and read response.
 *
 */
enum HPMUD_RESULT sendUSBFileRequest(char *buf, int size, int fileHandle,raw_ipp *responseptr, char *device_uri)
{
    HPMUD_DEVICE hd = 0;
    HPMUD_CHANNEL cd;
    enum HPMUD_RESULT stat = HPMUD_R_OK;
    int device_already_open = 0;
    char fileData[4096] = {0};
    int bytes = 0;

    DBG("sendUSBRequest: buf = %p, size = %d, responseptr = %p, device_uri = %s\n", buf, size, responseptr, device_uri);

    /* Open hp device. */
    if ((stat = hpmud_open_device(device_uri, HPMUD_RAW_MODE, &hd)) != HPMUD_R_OK)
    {
        if (stat == HPMUD_R_INVALID_STATE)
        {
            // hpmud reports HPMUD_R_INVALID_STATE error when we try to open the device again which was already opened by tools like hp-levels
            /// or hp-toolbox. We can use this already opened device but we need to make sure we do not close the device after use.
            hd = 1;
            device_already_open = 1;
        }
        else
        {
            BUG("Device open failed with status code = %d\n", stat);
            goto abort;
        }
    }

    /* Open ipp channel. */
    if ((stat = hpmud_open_channel(hd, HPMUD_S_IPP_CHANNEL, &cd)) != HPMUD_R_OK)
    {
        if ((stat = hpmud_open_channel(hd, HPMUD_S_IPP_CHANNEL2, &cd)) != HPMUD_R_OK)
        {
            BUG("Channel open failed with status code = %d\n", stat);
            goto abort;
        }
    }

    // Write request on the channel
    if ((stat = writeChannel(buf, size, hd, cd)) != HPMUD_R_OK)
    {
        BUG("Channel write failed with status code = %d\n", stat);
        goto abort;
    }

    while ((bytes = read(fileHandle, fileData, sizeof(fileData))) > 0)
    {    
        if ((stat = writeChannel(fileData, bytes, hd, cd)) != HPMUD_R_OK)
            break;
        BUG("Channel write failed with status code = %d\n", stat);
    }

    // Read the response from the channel
    if ((stat = readChannel(responseptr, hd, cd)) != HPMUD_R_OK)
    {
        BUG("Channel read failed with status code = %d\n", stat);
        // goto abort;
    }

    // Remove header and chunking information from the raw IPP response
    ExtractIPPData(responseptr->data, &responseptr->data_length);
    stat = HPMUD_R_OK;

abort:

    if (cd > 0)
        hpmud_close_channel(hd, cd);
    if (hd > 0 && !device_already_open)
        hpmud_close_device(hd);

    return stat;
}

/*
 * 'readChannel()' - Read response from the channel.
 *
 */
enum HPMUD_RESULT readChannel(raw_ipp *responseptr, HPMUD_DEVICE hd, HPMUD_CHANNEL cd)
{
    int bytes_read = 0;
    int bytes_remaining = 0;
    int content_length = 0;
    int chunked = 0;
    int header_size = 0;
    enum HPMUD_RESULT stat = HPMUD_R_OK;
    char *data = responseptr->data;
    int *size = &responseptr->data_length;

    if (!responseptr)
    {
        DBG("NULL  responseptr passed.\n");
        return HPMUD_R_INVALID_LENGTH;
    }

    memset(responseptr, 0, MAX_IPP_DATA_LENGTH);

    // Read http header and figure out Transfer-Encoding of the response
    stat = hpmud_read_channel(hd, cd, data, USB_BULK_TRANSFER_LENGTH, 60, &bytes_read);
    if (HPMUD_R_OK != stat && HPMUD_R_IO_TIMEOUT != stat)
    {
        return stat;
    }

    DBG("Header bytes read from the channel = %d, status = [%d] \n", bytes_read, stat);
    *size += bytes_read;

    if (HPIPP_OK != parseResponseHeader(data, &content_length, &chunked, &header_size))
        return HPMUD_R_IO_ERROR;

    bytes_remaining = content_length - (bytes_read - header_size); // Update bytes remaining

    // Read remaining response
    while (bytes_read)
    {
        if (*size + USB_BULK_TRANSFER_LENGTH > MAX_IPP_DATA_LENGTH) // Check for the overflow condition
        {
            stat = HPMUD_R_INVALID_LENGTH;
            break;
        }

        stat = hpmud_read_channel(hd, cd, &data[*size], USB_BULK_TRANSFER_LENGTH, TIMEOUT, &bytes_read);

        DBG("Bytes read from the channel = %d , status = [%d], bytes_remaining = [%d]\n", bytes_read, stat, bytes_remaining);
        if (HPMUD_R_OK != stat && HPMUD_R_IO_TIMEOUT != stat)
            break;

        *size += bytes_read;
        if (chunked)
        {
            if (memcmp(&data[*size - CHUNK_DELIMITER_LENGTH], CHUNK_DELIMITER, CHUNK_DELIMITER_LENGTH) == 0)
            {
                DBG("Chunk end recieved....\n");
                break;
            }
        }
        else
        {
            bytes_remaining -= bytes_read;
            if (bytes_remaining == 0)
            {
                DBG("Complete unchunked data recieved....\n");
                break;
            }
        }
    }

    DBG("Total bytes read from the channel = %d\n", *size);
    return stat;
}

/*
 * 'writeChannel()' - Write request on the channel.
 */
enum HPMUD_RESULT writeChannel(char *buf, int size, HPMUD_DEVICE hd, HPMUD_CHANNEL cd)
{
    int timeout = 1;
    int total = 0;
    int len = 0;
    int transfer_size = 0;
    enum HPMUD_RESULT stat = HPMUD_R_OK;

    while (size > 0)
    {
        transfer_size = (size > USB_BULK_TRANSFER_LENGTH) ? USB_BULK_TRANSFER_LENGTH : size;
        stat = hpmud_write_channel(hd, cd, buf + total, transfer_size, timeout, &len);
        DBG("Bytes written on the channel = %d Size = %d, transfer_size = %d\n\n\n", len, size, transfer_size);
        total = len;
        size -= len;
    }

    DBG("Total bytes written on the channel = %d\n", total);

    return stat;
}

/*
 * 'raw_ipp_response_read_callback()' - Read IPP data from a raw IPP response buffer.
 *
 * This callback function is called from cups API ippReadIO. ippReadIO function reads data from file,
 * buffer or socket and then parses and converts raw ipp data into ipp_t format.
 */
static ssize_t                                      /* O - Number of bytes read */
raw_ipp_response_read_callback(raw_ipp *src,        /* I - Source raw ipp data buffer */
                               ipp_uchar_t *buffer, /* O - Read buffer */
                               size_t length)       /* O - Number of bytes to read */
{

    // Check if remaining data has length bytes left. If not then return only remaining bytes.
    if (src->data_length < length)
    {
        DBG("Requested Length(%d) is more than source buffer length(%d).\n", length, src->data_length);
        length = src->data_length;
    }

    // Copy data to the requested buffer
    memcpy(buffer, src->data, length);

    // Remove copied data from the src, so that next time same data is not copied.
    memmove(src->data, &src->data[length], src->data_length - length);

    // Update src with new length.
    src->data_length = src->data_length - length;

    return (length);
}

/*
 * 'raw_ipp_request_callback()' - Write RAW IPP data to a buffer.
 *
 * This callback function (called from cupsSendRequest) recieves the converted data from
 * ipp_t request structure  to * a binary format in iterations. This function keeps on
 * updating raw_buffer which is passed by the caller. Once all iterations are over,
 * raw_buffer will contain complete IPP request data in a binary form which can later
 * be sent to the IPP server.
 */
static ssize_t                                         /* O - Number of bytes written */
raw_ipp_request_callback(volatile raw_ipp *raw_buffer, /* O - RAW IPP buffer*/
                         ipp_uchar_t *buffer,          /* I - Data to write */
                         size_t length)                /* I - Number of bytes to write */
{

    if (raw_buffer->data_length + length < MAX_IPP_DATA_LENGTH)
    {
        memcpy((char *)&raw_buffer->data[raw_buffer->data_length], buffer, length);
        raw_buffer->data_length = raw_buffer->data_length + length;
    }
    else
    {
        BUG("Insufficient raw_ipp->data size. %d bytes needed.\n", raw_buffer->data_length + length);
    }

    return (length);
}

/*
 * 'prepend_http_header()' - Adds http header in the beginning of the ipp raw data and shifts
 * raw data accordingly.
 */
HPIPP_RESULT prepend_http_header(raw_ipp *raw_request, const char *resource)
{

    char *http_header_tamplate = "POST %s HTTP/1.1\r\nContent-Length: %d\r\nContent-Type: application/ipp\r\nHOST: Localhost\r\n\r\n";
    char http_header[1024] = {0};
    int http_header_size = 0;

    // Create transport header for the request
    http_header_size = sprintf(http_header, http_header_tamplate, resource, raw_request->data_length);


    // Shift raw_request->data by http_header_size bytes and copy http_header in the beginning
    if (raw_request->data_length + http_header_size >= MAX_IPP_DATA_LENGTH)
        return HPIPP_INVALID_LENGTH;

    memmove(raw_request->data + http_header_size, raw_request->data, raw_request->data_length);
    memcpy(raw_request->data, http_header, http_header_size);
    raw_request->data_length = raw_request->data_length + http_header_size;

    return HPIPP_OK;
}

HPIPP_RESULT prepend_http_header_with_data(raw_ipp *raw_request, const char *resource,size_t fileSize)
{

    char *http_header_tamplate = "POST %s HTTP/1.1\r\nContent-Length: %d\r\nContent-Type: application/ipp\r\nHOST: Localhost\r\n\r\n";
    char http_header[1024] = {0};
    int http_header_size = 0;

    // Create transport header for the request
    http_header_size = sprintf(http_header, http_header_tamplate, resource, raw_request->data_length+fileSize);

    // Shift raw_request->data by http_header_size bytes and copy http_header in the beginning
    if (raw_request->data_length + http_header_size >= MAX_IPP_DATA_LENGTH)
        return HPIPP_INVALID_LENGTH;

    memmove(raw_request->data + http_header_size, raw_request->data, raw_request->data_length);
    memcpy(raw_request->data, http_header, http_header_size);
    raw_request->data_length = raw_request->data_length + http_header_size;

    return HPIPP_OK;
}


/*
 * 'removeChunkInfo()' - Remove Chunk length information from the response.
 */
HPIPP_RESULT removeChunkInfo(char *data, int *length)
{
    char *chunksize_start = data;
    char *chunksize_end = NULL;
    int chunklen = 0;
    int remaining_bytes = *length;
    HPIPP_RESULT hr = HPIPP_OK;
    int newlength = *length;

    do
    {
        chunksize_end = strstr(chunksize_start, CRLF);
        if (chunksize_end == NULL)
        {
            BUG("removeChunkInfo failed.\n");
            return HPIPP_ERROR;
        }

        chunksize_end += CRLF_LENGTH;
        chunklen = strtol(chunksize_start, NULL, BASE_HEX);   // Convert ChunkLength from ASCII hex format to an integer.
        remaining_bytes -= (chunksize_end - chunksize_start); // Update remmaining length

        DBG("chunklen = [%d], remaining_bytes= [%d], newlength = [%d]\n", chunklen, remaining_bytes, newlength);
        if (chunklen > remaining_bytes)
        {
            BUG("RemoveChunkInfo failed.\n");
            return HPIPP_ERROR;
        }

        memmove(chunksize_start, chunksize_end, remaining_bytes); // Delete chunklengthinfo from buffer by moving remaining_bytes data
        newlength -= (chunksize_end - chunksize_start);           // Subtract size of chunklengthinfo from ipp data length
        chunksize_start += chunklen;                              // Move to next chunk
        remaining_bytes -= chunklen;                              // Update remmaining length

        // Check if chunk data ends with CRLF. If so then remove that also.
        if (memcmp(chunksize_start, CRLF, CRLF_LENGTH) == 0)
        {
            remaining_bytes -= CRLF_LENGTH;
            newlength -= CRLF_LENGTH;
            memmove(chunksize_start, chunksize_start + CRLF_LENGTH, remaining_bytes);
        }

    } while (chunklen);

    // Set remaining_bytes buffer to 0 and update length of the buffer to newlength before returning.
    memset(data + newlength, 0, MAX_IPP_DATA_LENGTH - newlength);
    *length = newlength;

    return hr;
}

/*
 * 'getJobStatus()' - Create IPP request and update the same with values needed for getting device status attributes.
 */
int getJobStatus(int iJobID, const char *iPrinterName, const char *iDeviceUri)
{
    char uri[HTTP_MAX_URI] = {0};
    int aRetnValue = 0;
    ipp_t *request = NULL, *response = NULL;
    static const char *attrs[] = /* Requested attributes */
        {
            "job-state",
        };
    /* Assemble the IPP request */
    request = ippNewRequest(IPP_OP_GET_JOB_ATTRIBUTES);
    initializeIPPRequest(request);
    if (request)
    {
        snprintf(uri, HTTP_MAX_URI, "ipp://localhost/printers/%s", iPrinterName);
        ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
        ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", iJobID);
        ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser());
        ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", sizeof(attrs) / sizeof(attrs[0]), NULL, attrs);
        if (strcasestr(iDeviceUri, ":/usb") != NULL)
        {
            response = usbDoRequest(request, iDeviceUri, "/ipp/faxout");
        }
        else if (strcasestr(iDeviceUri, ":/net") != NULL)
        {
            response = networkDoRequest(request, iDeviceUri, "/ipp/faxout");
        }
        else
        {
            BUG("Invalid device URI (%s)\n", iDeviceUri);
        }

        if (response != NULL)
        {
            aRetnValue = ippGetInteger(ippFindAttribute(response, "job-state", IPP_TAG_ENUM), 0);
            ippDelete(response);
            return aRetnValue;
        }
    }
}

HPIPP_RESULT sendFaxJob(const char *iDeviceUri, const char *iPrinterName, const char *iFileName, const char *iFaxNumber)
{
    ipp_t *request = NULL, *response = NULL;
    ipp_t *faxNumber = NULL;
    char uri[HTTP_MAX_URI] = {0};
    char ip[HPMUD_LINE_SIZE] = {0};
    int aJobID = 0;   
    char aFaxNumberUri[HTTP_MAX_URI] = {0};
    int infile = 0;
    request = ippNewRequest(IPP_OP_PRINT_JOB);
    initializeIPPRequest(request);
    if (request)
    {
        faxNumber = ippNew();
        if (!faxNumber)
        {
            return HPIPP_ERROR;
        }

        snprintf(aFaxNumberUri, HTTP_MAX_URI, "tel:%s", iFaxNumber);
        ippAddString(faxNumber, IPP_TAG_JOB, IPP_TAG_URI, "destination-uri", NULL, aFaxNumberUri);

        if (strcasestr(iDeviceUri, ":/net") != NULL)
        {
            hpmud_get_uri_datalink(iDeviceUri, ip, HPMUD_LINE_SIZE);
            snprintf(uri, HTTP_MAX_URI, "ipp://%s/ipp/faxout",ip);
        }
        else
        {
            snprintf(uri, HTTP_MAX_URI, "ipp://localhost/printers/%s/ipp/faxout", iPrinterName);
        }
        ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
        ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser());
        ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, "document-format", NULL, "application/octet-stream");
        ippAddCollection(request, IPP_TAG_JOB, "destination-uris", faxNumber);
        if (faxNumber != NULL)
        {
            ippDelete(faxNumber);
        }
        // Send request to the server based on connection
        if (strcasestr(iDeviceUri, ":/usb") != NULL)
        {       
            infile = open(iFileName, O_RDONLY | O_BINARY);
            response = usbDoFileRequest(request, infile,iDeviceUri, "/ipp/faxout");
        }
        else if (strcasestr(iDeviceUri, ":/net") != NULL)
        {
            response = networkDoFileRequest(request, iDeviceUri, "/ipp/faxout", iFileName);
        }
        else
        {
            BUG("Invalid device URI (%s)\n", iDeviceUri);
        }

        if (response != NULL)
        {
            aJobID = ippGetInteger(ippFindAttribute(response, "job-id", IPP_TAG_INTEGER), 0);
            ippDelete(response);
        }
    }
    return aJobID;
}

/*
 * 'usbDoFileRequest()' - Send ipp request along with file to a usb device and read back the response.
 *
 * This function sends ipp request to a device correponding to caller provided device_uri and reads back the response.
 * This function then parses and stores the raw response into ipp_t (response) and sends back the same.
 */
ipp_t *usbDoFileRequest(ipp_t *request, int iFileHandle,char *device_uri, const char *resource)
{
    ipp_t *response = NULL; /* IPP response object */
    ipp_state_t state;
    raw_ipp raw_request;
    raw_ipp raw_response;
    struct stat	fileinfo;
    memset(&raw_request, 0, sizeof(raw_request));
    memset(&raw_response, 0, sizeof(raw_response));
    char buffer [8192] = {0};
    int bytes = 0;

    HPMUD_DEVICE hd = 0;
    HPMUD_CHANNEL cd;
    enum HPMUD_RESULT stat = HPMUD_R_OK;
    int device_already_open = 0;
    

    /* Open hp device. */
    if ((stat = hpmud_open_device(device_uri, HPMUD_RAW_MODE, &hd)) != HPMUD_R_OK)
    {
        if (stat == HPMUD_R_INVALID_STATE)
        {
            // hpmud reports HPMUD_R_INVALID_STATE error when we try to open the device again which was already opened by tools like hp-levels
            /// or hp-toolbox. We can use this already opened device but we need to make sure we do not close the device after use.
            hd = 1;
            device_already_open = 1;
        }
        else
        {
            fprintf(stderr,"Device open failed with status code = %d\n", stat);
            goto abort;
        }
    }

    /* Open ipp channel. */
    if ((stat = hpmud_open_channel(hd, HPMUD_S_IPP_CHANNEL, &cd)) != HPMUD_R_OK)
    {
        if ((stat = hpmud_open_channel(hd, HPMUD_S_IPP_CHANNEL2, &cd)) != HPMUD_R_OK)
        {
            fprintf(stderr,"Channel open failed with status code = %d\n", stat);
            goto abort;
        }
    }


    // Convert ipp_t request structure into raw ipp format
    state = ippWriteIO(&raw_request, (ipp_iocb_t)raw_ipp_request_callback, 1, NULL, request);
    if (IPP_ERROR == state)
    {
        fprintf(stderr,"ippWriteIO Failed...\n");
        goto abort;
    }

    fstat(iFileHandle, &fileinfo);
    
    // Prepend http header to the raw request buffer
    if (prepend_http_header_with_data(&raw_request, resource,fileinfo.st_size) != HPIPP_OK)
       goto abort;


    // Write request on the channel
    if ((stat = writeChannel(raw_request.data, raw_request.data_length, hd, cd)) != HPMUD_R_OK)
    {
        fprintf(stderr,"Channel write failed with status code = %d\n", stat);
        goto abort;
    }

    lseek(iFileHandle, 0, SEEK_SET);

    while ((bytes = read(iFileHandle, buffer, sizeof(buffer))) > 0)
    {
        //raw_ipp_request_callback(&raw_request,buffer, (size_t)bytes);
        // Write request on the channel
        if ((stat = writeChannel(buffer, bytes, hd, cd)) != HPMUD_R_OK)
        {
            fprintf(stderr,"Channel write failed with status code = %d\n", stat);
            goto abort;
        }
        memset(buffer,0,sizeof(buffer));
    }

    memset(&raw_response, 0, sizeof(raw_response));
    if ((stat = readChannel(&raw_response, hd, cd)) != HPMUD_R_OK)
    {
        fprintf(stderr,"Channel read failed with status code = %d\n", stat);
        // goto abort;
    }        

    // Convert Raw data into ipp_t response structure format
    response = ippNew();
    state = ippReadIO(&raw_response, (ipp_iocb_t)raw_ipp_response_read_callback, 1, NULL, response);
    if (response && (IPP_ERROR == state))
    {
        fprintf(stderr,"ippReadIO Failed...\n");
        ippDelete(response);
        response = NULL;
    }

    close(iFileHandle) ; 

abort:
    if (cd > 0)
        hpmud_close_channel(hd, cd);
    if (hd > 0 && !device_already_open)
        hpmud_close_device(hd);

    return response;
}


/*
 * 'networkDoFileRequest()' - Send ipp request to a network device and read back the response.
 *
 * This function makes use of cups API (cupsDoRequest) to send ipp request and get back the ipp response.
 */
ipp_t *networkDoFileRequest(ipp_t *request, char *device_uri, const char *resource, const char *iFilename)
{
    ipp_t *response = NULL; /* IPP response object */
    char ip[HPMUD_LINE_SIZE] = {0};
    char uri[HTTP_MAX_URI] = {0};
    http_t *http = NULL; /* HTTP object */
    // Get IP address
    hpmud_get_uri_datalink(device_uri, ip, HPMUD_LINE_SIZE);
    // Create http connection
    http = httpConnectEncrypt(ip, ippPort(), cupsEncryption());
    if (http == NULL)
        return NULL;

    if (request == NULL)
    {
        return NULL;
    }
    // Send the request and get a response.
    if ((response = cupsDoFileRequest(http, request, resource, iFilename)) == NULL)
    {
        return response;
    }
    httpClose(http);
    return response;
}
