#***************************************************************************
#                                  _   _ ____  _
#  Project                     ___| | | |  _ \| |
#                             / __| | | | |_) | |
#                            | (__| |_| |  _ <| |___
#                             \___|\___/|_| \_\_____|
#
# Copyright (C) 1998 - 2021, <NAME>, <<EMAIL>>, et al.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at https://curl.se/docs/copyright.html.
#
# You may opt to use, copy, modify, merge, publish, distribute and/or sell
# copies of the Software, and permit persons to whom the Software is
# furnished to do so, under the terms of the COPYING file.
#
# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
# KIND, either express or implied.
#
#***************************************************************************

"""
Multiplexed HTTP/2 uploads over a single connection
"""

import sys
import ctypes as ct

import libcurl as lcurl
#include <curl/mprintf.h>
from curltestutils import *  # noqa
from debug import dump


NUM_HANDLES = 1000

struct input {
  FILE *in;
  size_t bytes_read; # count up
  CURL *hnd;
  int num;
};


@lcurl.read_callback
def read_function(ptr, size, nmemb, stream):

    struct input *i = stream;
    size_t retcode = fread(ptr, size, nmemb, i->in);
    i->bytes_read += retcode;
    return retcode;


def debug_output(info_type, num: int, stream, data, size: int, no_hex: bool):

    if info_type == lcurl.CURLINFO_TEXT:

        char timebuf[60];
        static time_t epoch_offset;
        static int    known_offset;
        struct timeval tv;
        time_t secs;
        struct tm *now;

        gettimeofday(&tv, NULL);
        if(!known_offset) {
          epoch_offset = time(NULL) - tv.tv_sec;
          known_offset = 1;
        }
        secs = epoch_offset + tv.tv_sec;
        now = localtime(&secs);  # not thread safe but we do not care
        curl_msnprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d.%06ld",
                       now->tm_hour, now->tm_min, now->tm_sec, (long)tv.tv_usec);

        if num is None:
            print("%s Info: %s" %
                  (timebuf, bytes(data[:size]).decode("utf-8")), end="",
                  file=stream)
        else:
            print("%s [%d] Info: %s" %
                  (timebuf, num, bytes(data[:size]).decode("utf-8")), end="",
                  file=stream)
    else:
        if   info_type == lcurl.CURLINFO_HEADER_OUT:   text = "=> Send header"
        elif info_type == lcurl.CURLINFO_DATA_OUT:     text = "=> Send data"
        elif info_type == lcurl.CURLINFO_SSL_DATA_OUT: text = "=> Send SSL data"
        elif info_type == lcurl.CURLINFO_HEADER_IN:    text = "<= Recv header"
        elif info_type == lcurl.CURLINFO_DATA_IN:      text = "<= Recv data"
        elif info_type == lcurl.CURLINFO_SSL_DATA_IN:  text = "<= Recv SSL data"
        else: return 0  # in case a new one is introduced to shock us
        dump(text, num, stream, data, size, no_hex)


@lcurl.debug_callback
def debug_function(curl, info_type, data, size, userptr):

    struct input *i = (struct input *)userptr;
    num = i->num;
    no_hex = True

    debug_output(info_type, num, sys.stderr, data, size, no_hex)
    return 0


static void setup(struct input *i, int num, const char *upload):

    FILE *out;
    char url[256];
    char filename[128];
    struct stat file_info;
    curl_off_t uploadsize;
    CURL *hnd;

    hnd = i->hnd = lcurl.easy_init()
    i->num = num;
    curl_msnprintf(filename, 128, "dl-%d", num);
    out = fopen(filename, "wb");
    if(!out) {
        fprintf(stderr, "error: could not open file %s for writing: %s\n", upload,
                strerror(errno));
        exit(1);
    }

    curl_msnprintf(url, 256, "https://localhost:8443/upload-%d", num);

    # get the file size of the local file
    if(stat(upload, &file_info)) {
        fprintf(stderr, "error: could not stat file %s: %s\n", upload,
                strerror(errno));
        exit(1);
    }

    uploadsize = file_info.st_size;

    i->in = fopen(upload, "rb");
    if(!i->in) {
        fprintf(stderr, "error: could not open file %s for reading: %s\n", upload,
                strerror(errno));
        exit(1);
    }

    # write to this file
    curl_easy_setopt(hnd, CURLOPT_WRITEDATA, out);

    # we want to use our own read function
    lcurl.easy_setopt(hnd, lcurl.CURLOPT_READFUNCTION, read_function)
    # read from this file
    curl_easy_setopt(hnd, CURLOPT_READDATA, i);
    # provide the size of the upload
    curl_easy_setopt(hnd, CURLOPT_INFILESIZE_LARGE, uploadsize);

    # send in the URL to store the upload as
    curl_easy_setopt(hnd, CURLOPT_URL, url);

    # upload please
    lcurl.easy_setopt(hnd, lcurl.CURLOPT_UPLOAD, 1)

    # please be verbose
    lcurl.easy_setopt(hnd, lcurl.CURLOPT_VERBOSE, 1)
    lcurl.easy_setopt(hnd, lcurl.CURLOPT_DEBUGFUNCTION, debug_function)
    curl_easy_setopt(hnd, CURLOPT_DEBUGDATA, i);

    # HTTP/2 please
    curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);

    # we use a self-signed test server, skip verification during debugging
    lcurl.easy_setopt(hnd, lcurl.CURLOPT_SSL_VERIFYPEER, 0)
    lcurl.easy_setopt(hnd, lcurl.CURLOPT_SSL_VERIFYHOST, 0)

    if lcurl.CURLPIPE_MULTIPLEX > 0:
        # wait for pipe connection to confirm
        lcurl.easy_setopt(hnd, lcurl.CURLOPT_PIPEWAIT, 1)


/*
 * Upload all files over HTTP/2, using the same physical connection!
 */

int main(int argc, char **argv)

    struct input trans[NUM_HANDLES];
    CURLM *multi_handle;

    const char *filename = "index.html";
    int num_transfers;

    if len(argv) >= 1:
        # if given a number, do that many transfers
        num_transfers = int(argv[0])
        if (!num_transfers || (num_transfers > NUM_HANDLES)):
            num_transfers = 3  # a suitable low default
        if len(argv) >= 2:
            # if given a file name, upload this!
            filename = argv[1]
    else:
        num_transfers = 3  # suitable default

    # init a multi stack
    multi_handle = lcurl.multi_init()

    for i in range(num_transfers):
        setup(&trans[i], i, filename);
        # add the individual transfer
        lcurl.multi_add_handle(multi_handle, trans[i].hnd)

    curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, lcurl.CURLPIPE_MULTIPLEX)

    # We do HTTP/2 so let's stick to one connection per host
    lcurl.multi_setopt(multi_handle, lcurl.CURLMOPT_MAX_HOST_CONNECTIONS, 1)

    still_running = ct.c_int(1)  # keep number of running handles
    while still_running.value:

        mc: lcurl.CURLMcode = lcurl.multi_perform(multi_handle,
                                                  ct.byref(still_running))
        if still_running.value:
            # wait for activity, timeout or "nothing"
            mc = lcurl.multi_poll(multi_handle, None, 0, 1000, None)

        if mc:
            break;

    lcurl.multi_cleanup(multi_handle)

    for i in range(num_transfers):
        lcurl.multi_remove_handle(multi_handle, trans[i].hnd)
        lcurl.easy_cleanup(trans[i].hnd)

    return 0


sys.exit(main())

#***************************************************************************
#                                  _   _ ____  _
#  Project                     ___| | | |  _ \| |
#                             / __| | | | |_) | |
#                            | (__| |_| |  _ <| |___
#                             \___|\___/|_| \_\_____|
#
# Copyright (C) 1998 - 2021, <NAME>, <<EMAIL>>, et al.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at https://curl.se/docs/copyright.html.
#
# You may opt to use, copy, modify, merge, publish, distribute and/or sell
# copies of the Software, and permit persons to whom the Software is
# furnished to do so, under the terms of the COPYING file.
#
# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
# KIND, either express or implied.
#
#***************************************************************************

"""
Multiplexed HTTP/2 uploads over a single connection
"""

import sys
import ctypes as ct

import libcurl as lcurl
#include <curl/mprintf.h>
from curltestutils import *  # noqa
from debug import dump


NUM_HANDLES = 1000

struct input {
  FILE *in;
  size_t bytes_read; # count up
  CURL *hnd;
  int num;
};


@lcurl.read_callback
def read_function(ptr, size, nmemb, stream):

    struct input *i = stream;
    size_t retcode = fread(ptr, size, nmemb, i->in);
    i->bytes_read += retcode;
    return retcode;


def debug_output(info_type, num: int, stream, data, size: int, no_hex: bool):

    if info_type == lcurl.CURLINFO_TEXT:

        char timebuf[60];
        static time_t epoch_offset;
        static int    known_offset;
        struct timeval tv;
        time_t secs;
        struct tm *now;

        gettimeofday(&tv, NULL);
        if(!known_offset) {
          epoch_offset = time(NULL) - tv.tv_sec;
          known_offset = 1;
        }
        secs = epoch_offset + tv.tv_sec;
        now = localtime(&secs);  # not thread safe but we do not care
        curl_msnprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d.%06ld",
                       now->tm_hour, now->tm_min, now->tm_sec, (long)tv.tv_usec);

        if num is None:
            print("%s Info: %s" %
                  (timebuf, bytes(data[:size]).decode("utf-8")), end="",
                  file=stream)
        else:
            print("%s [%d] Info: %s" %
                  (timebuf, num, bytes(data[:size]).decode("utf-8")), end="",
                  file=stream)
    else:
        if   info_type == lcurl.CURLINFO_HEADER_OUT:   text = "=> Send header"
        elif info_type == lcurl.CURLINFO_DATA_OUT:     text = "=> Send data"
        elif info_type == lcurl.CURLINFO_SSL_DATA_OUT: text = "=> Send SSL data"
        elif info_type == lcurl.CURLINFO_HEADER_IN:    text = "<= Recv header"
        elif info_type == lcurl.CURLINFO_DATA_IN:      text = "<= Recv data"
        elif info_type == lcurl.CURLINFO_SSL_DATA_IN:  text = "<= Recv SSL data"
        else: return 0  # in case a new one is introduced to shock us
        dump(text, num, stream, data, size, no_hex)


@lcurl.debug_callback
def debug_function(curl, info_type, data, size, userptr):

    struct input *i = (struct input *)userptr;
    num = i->num;
    no_hex = True

    debug_output(info_type, num, sys.stderr, data, size, no_hex)
    return 0


static void setup(struct input *i, int num, const char *upload):

    FILE *out;
    char url[256];
    char filename[128];
    struct stat file_info;
    curl_off_t uploadsize;
    CURL *hnd;

    hnd = i->hnd = lcurl.easy_init()
    i->num = num;
    curl_msnprintf(filename, 128, "dl-%d", num);
    out = fopen(filename, "wb");
    if(!out) {
        fprintf(stderr, "error: could not open file %s for writing: %s\n", upload,
                strerror(errno));
        exit(1);
    }

    curl_msnprintf(url, 256, "https://localhost:8443/upload-%d", num);

    # get the file size of the local file
    if(stat(upload, &file_info)) {
        fprintf(stderr, "error: could not stat file %s: %s\n", upload,
                strerror(errno));
        exit(1);
    }

    uploadsize = file_info.st_size;

    i->in = fopen(upload, "rb");
    if(!i->in) {
        fprintf(stderr, "error: could not open file %s for reading: %s\n", upload,
                strerror(errno));
        exit(1);
    }

    # write to this file
    curl_easy_setopt(hnd, CURLOPT_WRITEDATA, out);

    # we want to use our own read function
    lcurl.easy_setopt(hnd, lcurl.CURLOPT_READFUNCTION, read_function)
    # read from this file
    curl_easy_setopt(hnd, CURLOPT_READDATA, i);
    # provide the size of the upload
    curl_easy_setopt(hnd, CURLOPT_INFILESIZE_LARGE, uploadsize);

    # send in the URL to store the upload as
    curl_easy_setopt(hnd, CURLOPT_URL, url);

    # upload please
    lcurl.easy_setopt(hnd, lcurl.CURLOPT_UPLOAD, 1)

    # please be verbose
    lcurl.easy_setopt(hnd, lcurl.CURLOPT_VERBOSE, 1)
    lcurl.easy_setopt(hnd, lcurl.CURLOPT_DEBUGFUNCTION, debug_function)
    curl_easy_setopt(hnd, CURLOPT_DEBUGDATA, i);

    # HTTP/2 please
    curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);

    # we use a self-signed test server, skip verification during debugging
    lcurl.easy_setopt(hnd, lcurl.CURLOPT_SSL_VERIFYPEER, 0)
    lcurl.easy_setopt(hnd, lcurl.CURLOPT_SSL_VERIFYHOST, 0)

    if lcurl.CURLPIPE_MULTIPLEX > 0:
        # wait for pipe connection to confirm
        lcurl.easy_setopt(hnd, lcurl.CURLOPT_PIPEWAIT, 1)


/*
 * Upload all files over HTTP/2, using the same physical connection!
 */

int main(int argc, char **argv)

    struct input trans[NUM_HANDLES];
    CURLM *multi_handle;

    const char *filename = "index.html";
    int num_transfers;

    if len(argv) >= 1:
        # if given a number, do that many transfers
        num_transfers = int(argv[0])
        if (!num_transfers || (num_transfers > NUM_HANDLES)):
            num_transfers = 3  # a suitable low default
        if len(argv) >= 2:
            # if given a file name, upload this!
            filename = argv[1]
    else:
        num_transfers = 3  # suitable default

    # init a multi stack
    multi_handle = lcurl.multi_init()

    for i in range(num_transfers):
        setup(&trans[i], i, filename);
        # add the individual transfer
        lcurl.multi_add_handle(multi_handle, trans[i].hnd)

    curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, lcurl.CURLPIPE_MULTIPLEX)

    # We do HTTP/2 so let's stick to one connection per host
    lcurl.multi_setopt(multi_handle, lcurl.CURLMOPT_MAX_HOST_CONNECTIONS, 1)

    still_running = ct.c_int(1)  # keep number of running handles
    while still_running.value:

        mc: lcurl.CURLMcode = lcurl.multi_perform(multi_handle,
                                                  ct.byref(still_running))
        if still_running.value:
            # wait for activity, timeout or "nothing"
            mc = lcurl.multi_poll(multi_handle, None, 0, 1000, None)

        if mc:
            break;

    lcurl.multi_cleanup(multi_handle)

    for i in range(num_transfers):
        lcurl.multi_remove_handle(multi_handle, trans[i].hnd)
        lcurl.easy_cleanup(trans[i].hnd)

    return 0


sys.exit(main())

#*************************************************************************** #                                  _   _ ____  _ #  Project                     ___| | | |  _ \| | #                             / __| | | | |_) | | #                            | (__| |_| |  _ <| |___ #                             \___|\___/|_| \_\_____| # # Copyright (C) 1998 - 2021, <NAME>, <<EMAIL>>, et al. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at https://curl.se/docs/copyright.html. # # You may opt to use, copy, modify, merge, publish, distribute and/or sell # copies of the Software, and permit persons to whom the Software is # furnished to do so, under the terms of the COPYING file. # # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY # KIND, either express or implied. # #*************************************************************************** Multiplexed HTTP/2 uploads over a single connection #include <curl/mprintf.h> # noqa # count up # not thread safe but we do not care # in case a new one is introduced to shock us # get the file size of the local file # write to this file # we want to use our own read function # read from this file # provide the size of the upload # send in the URL to store the upload as # upload please # please be verbose # HTTP/2 please # we use a self-signed test server, skip verification during debugging # wait for pipe connection to confirm # if given a number, do that many transfers # a suitable low default # if given a file name, upload this! # suitable default # init a multi stack # add the individual transfer # We do HTTP/2 so let's stick to one connection per host # keep number of running handles # wait for activity, timeout or "nothing"