113498266Sopenharmony_ci/***************************************************************************
213498266Sopenharmony_ci *                                  _   _ ____  _
313498266Sopenharmony_ci *  Project                     ___| | | |  _ \| |
413498266Sopenharmony_ci *                             / __| | | | |_) | |
513498266Sopenharmony_ci *                            | (__| |_| |  _ <| |___
613498266Sopenharmony_ci *                             \___|\___/|_| \_\_____|
713498266Sopenharmony_ci *
813498266Sopenharmony_ci * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
913498266Sopenharmony_ci *
1013498266Sopenharmony_ci * This software is licensed as described in the file COPYING, which
1113498266Sopenharmony_ci * you should have received as part of this distribution. The terms
1213498266Sopenharmony_ci * are also available at https://curl.se/docs/copyright.html.
1313498266Sopenharmony_ci *
1413498266Sopenharmony_ci * You may opt to use, copy, modify, merge, publish, distribute and/or sell
1513498266Sopenharmony_ci * copies of the Software, and permit persons to whom the Software is
1613498266Sopenharmony_ci * furnished to do so, under the terms of the COPYING file.
1713498266Sopenharmony_ci *
1813498266Sopenharmony_ci * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
1913498266Sopenharmony_ci * KIND, either express or implied.
2013498266Sopenharmony_ci *
2113498266Sopenharmony_ci * SPDX-License-Identifier: curl
2213498266Sopenharmony_ci *
2313498266Sopenharmony_ci ***************************************************************************/
2413498266Sopenharmony_ci/* <DESC>
2513498266Sopenharmony_ci * HTTP/2 download pausing
2613498266Sopenharmony_ci * </DESC>
2713498266Sopenharmony_ci */
2813498266Sopenharmony_ci/* This is based on the poc client of issue #11982
2913498266Sopenharmony_ci */
3013498266Sopenharmony_ci#include <stdio.h>
3113498266Sopenharmony_ci#include <string.h>
3213498266Sopenharmony_ci#include <sys/time.h>
3313498266Sopenharmony_ci#include <unistd.h>
3413498266Sopenharmony_ci#include <stdlib.h>
3513498266Sopenharmony_ci#include <curl/curl.h>
3613498266Sopenharmony_ci#include <curl/mprintf.h>
3713498266Sopenharmony_ci
3813498266Sopenharmony_ci#define HANDLECOUNT 2
3913498266Sopenharmony_ci
4013498266Sopenharmony_cistatic void log_line_start(FILE *log, const char *idsbuf, curl_infotype type)
4113498266Sopenharmony_ci{
4213498266Sopenharmony_ci  /*
4313498266Sopenharmony_ci   * This is the trace look that is similar to what libcurl makes on its
4413498266Sopenharmony_ci   * own.
4513498266Sopenharmony_ci   */
4613498266Sopenharmony_ci  static const char * const s_infotype[] = {
4713498266Sopenharmony_ci    "* ", "< ", "> ", "{ ", "} ", "{ ", "} "
4813498266Sopenharmony_ci  };
4913498266Sopenharmony_ci  if(idsbuf && *idsbuf)
5013498266Sopenharmony_ci    fprintf(log, "%s%s", idsbuf, s_infotype[type]);
5113498266Sopenharmony_ci  else
5213498266Sopenharmony_ci    fputs(s_infotype[type], log);
5313498266Sopenharmony_ci}
5413498266Sopenharmony_ci
5513498266Sopenharmony_ci#define TRC_IDS_FORMAT_IDS_1  "[%" CURL_FORMAT_CURL_OFF_T "-x] "
5613498266Sopenharmony_ci#define TRC_IDS_FORMAT_IDS_2  "[%" CURL_FORMAT_CURL_OFF_T "-%" \
5713498266Sopenharmony_ci                                   CURL_FORMAT_CURL_OFF_T "] "
5813498266Sopenharmony_ci/*
5913498266Sopenharmony_ci** callback for CURLOPT_DEBUGFUNCTION
6013498266Sopenharmony_ci*/
6113498266Sopenharmony_cistatic int debug_cb(CURL *handle, curl_infotype type,
6213498266Sopenharmony_ci                    char *data, size_t size,
6313498266Sopenharmony_ci                    void *userdata)
6413498266Sopenharmony_ci{
6513498266Sopenharmony_ci  FILE *output = stderr;
6613498266Sopenharmony_ci  static int newl = 0;
6713498266Sopenharmony_ci  static int traced_data = 0;
6813498266Sopenharmony_ci  char idsbuf[60];
6913498266Sopenharmony_ci  curl_off_t xfer_id, conn_id;
7013498266Sopenharmony_ci
7113498266Sopenharmony_ci  (void)handle; /* not used */
7213498266Sopenharmony_ci  (void)userdata;
7313498266Sopenharmony_ci
7413498266Sopenharmony_ci  if(!curl_easy_getinfo(handle, CURLINFO_XFER_ID, &xfer_id) && xfer_id >= 0) {
7513498266Sopenharmony_ci    if(!curl_easy_getinfo(handle, CURLINFO_CONN_ID, &conn_id) &&
7613498266Sopenharmony_ci        conn_id >= 0) {
7713498266Sopenharmony_ci      curl_msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_2,
7813498266Sopenharmony_ci                     xfer_id, conn_id);
7913498266Sopenharmony_ci    }
8013498266Sopenharmony_ci    else {
8113498266Sopenharmony_ci      curl_msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_1, xfer_id);
8213498266Sopenharmony_ci    }
8313498266Sopenharmony_ci  }
8413498266Sopenharmony_ci  else
8513498266Sopenharmony_ci    idsbuf[0] = 0;
8613498266Sopenharmony_ci
8713498266Sopenharmony_ci  switch(type) {
8813498266Sopenharmony_ci  case CURLINFO_HEADER_OUT:
8913498266Sopenharmony_ci    if(size > 0) {
9013498266Sopenharmony_ci      size_t st = 0;
9113498266Sopenharmony_ci      size_t i;
9213498266Sopenharmony_ci      for(i = 0; i < size - 1; i++) {
9313498266Sopenharmony_ci        if(data[i] == '\n') { /* LF */
9413498266Sopenharmony_ci          if(!newl) {
9513498266Sopenharmony_ci            log_line_start(output, idsbuf, type);
9613498266Sopenharmony_ci          }
9713498266Sopenharmony_ci          (void)fwrite(data + st, i - st + 1, 1, output);
9813498266Sopenharmony_ci          st = i + 1;
9913498266Sopenharmony_ci          newl = 0;
10013498266Sopenharmony_ci        }
10113498266Sopenharmony_ci      }
10213498266Sopenharmony_ci      if(!newl)
10313498266Sopenharmony_ci        log_line_start(output, idsbuf, type);
10413498266Sopenharmony_ci      (void)fwrite(data + st, i - st + 1, 1, output);
10513498266Sopenharmony_ci    }
10613498266Sopenharmony_ci    newl = (size && (data[size - 1] != '\n')) ? 1 : 0;
10713498266Sopenharmony_ci    traced_data = 0;
10813498266Sopenharmony_ci    break;
10913498266Sopenharmony_ci  case CURLINFO_TEXT:
11013498266Sopenharmony_ci  case CURLINFO_HEADER_IN:
11113498266Sopenharmony_ci    if(!newl)
11213498266Sopenharmony_ci      log_line_start(output, idsbuf, type);
11313498266Sopenharmony_ci    (void)fwrite(data, size, 1, output);
11413498266Sopenharmony_ci    newl = (size && (data[size - 1] != '\n')) ? 1 : 0;
11513498266Sopenharmony_ci    traced_data = 0;
11613498266Sopenharmony_ci    break;
11713498266Sopenharmony_ci  case CURLINFO_DATA_OUT:
11813498266Sopenharmony_ci  case CURLINFO_DATA_IN:
11913498266Sopenharmony_ci  case CURLINFO_SSL_DATA_IN:
12013498266Sopenharmony_ci  case CURLINFO_SSL_DATA_OUT:
12113498266Sopenharmony_ci    if(!traced_data) {
12213498266Sopenharmony_ci      if(!newl)
12313498266Sopenharmony_ci        log_line_start(output, idsbuf, type);
12413498266Sopenharmony_ci      fprintf(output, "[%ld bytes data]\n", (long)size);
12513498266Sopenharmony_ci      newl = 0;
12613498266Sopenharmony_ci      traced_data = 1;
12713498266Sopenharmony_ci    }
12813498266Sopenharmony_ci    break;
12913498266Sopenharmony_ci  default: /* nada */
13013498266Sopenharmony_ci    newl = 0;
13113498266Sopenharmony_ci    traced_data = 1;
13213498266Sopenharmony_ci    break;
13313498266Sopenharmony_ci  }
13413498266Sopenharmony_ci
13513498266Sopenharmony_ci  return 0;
13613498266Sopenharmony_ci}
13713498266Sopenharmony_ci
13813498266Sopenharmony_cistatic int err(void)
13913498266Sopenharmony_ci{
14013498266Sopenharmony_ci  fprintf(stderr, "something unexpected went wrong - bailing out!\n");
14113498266Sopenharmony_ci  exit(2);
14213498266Sopenharmony_ci}
14313498266Sopenharmony_ci
14413498266Sopenharmony_cistruct handle
14513498266Sopenharmony_ci{
14613498266Sopenharmony_ci  int idx;
14713498266Sopenharmony_ci  int paused;
14813498266Sopenharmony_ci  int resumed;
14913498266Sopenharmony_ci  CURL *h;
15013498266Sopenharmony_ci};
15113498266Sopenharmony_ci
15213498266Sopenharmony_cistatic size_t cb(void *data, size_t size, size_t nmemb, void *clientp)
15313498266Sopenharmony_ci{
15413498266Sopenharmony_ci  size_t realsize = size * nmemb;
15513498266Sopenharmony_ci  struct handle *handle = (struct handle *) clientp;
15613498266Sopenharmony_ci  curl_off_t totalsize;
15713498266Sopenharmony_ci
15813498266Sopenharmony_ci  (void)data;
15913498266Sopenharmony_ci  if(curl_easy_getinfo(handle->h, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T,
16013498266Sopenharmony_ci                       &totalsize) == CURLE_OK)
16113498266Sopenharmony_ci    fprintf(stderr, "INFO: [%d] write, Content-Length %"CURL_FORMAT_CURL_OFF_T
16213498266Sopenharmony_ci            "\n", handle->idx, totalsize);
16313498266Sopenharmony_ci
16413498266Sopenharmony_ci  if(!handle->resumed) {
16513498266Sopenharmony_ci    ++handle->paused;
16613498266Sopenharmony_ci    fprintf(stderr, "INFO: [%d] write, PAUSING %d time on %lu bytes\n",
16713498266Sopenharmony_ci            handle->idx, handle->paused, (long)realsize);
16813498266Sopenharmony_ci    return CURL_WRITEFUNC_PAUSE;
16913498266Sopenharmony_ci  }
17013498266Sopenharmony_ci  fprintf(stderr, "INFO: [%d] write, accepting %lu bytes\n",
17113498266Sopenharmony_ci          handle->idx, (long)realsize);
17213498266Sopenharmony_ci  return realsize;
17313498266Sopenharmony_ci}
17413498266Sopenharmony_ci
17513498266Sopenharmony_ciint main(int argc, char *argv[])
17613498266Sopenharmony_ci{
17713498266Sopenharmony_ci  struct handle handles[HANDLECOUNT];
17813498266Sopenharmony_ci  CURLM *multi_handle;
17913498266Sopenharmony_ci  int i, still_running = 1, msgs_left, numfds;
18013498266Sopenharmony_ci  CURLMsg *msg;
18113498266Sopenharmony_ci  int rounds = 0;
18213498266Sopenharmony_ci  int rc = 0;
18313498266Sopenharmony_ci  CURLU *cu;
18413498266Sopenharmony_ci  struct curl_slist *resolve = NULL;
18513498266Sopenharmony_ci  char resolve_buf[1024];
18613498266Sopenharmony_ci  char *url, *host = NULL, *port = NULL;
18713498266Sopenharmony_ci  int all_paused = 0;
18813498266Sopenharmony_ci  int resume_round = -1;
18913498266Sopenharmony_ci
19013498266Sopenharmony_ci  if(argc != 2) {
19113498266Sopenharmony_ci    fprintf(stderr, "ERROR: need URL as argument\n");
19213498266Sopenharmony_ci    return 2;
19313498266Sopenharmony_ci  }
19413498266Sopenharmony_ci  url = argv[1];
19513498266Sopenharmony_ci
19613498266Sopenharmony_ci  curl_global_init(CURL_GLOBAL_DEFAULT);
19713498266Sopenharmony_ci  curl_global_trace("ids,time,http/2");
19813498266Sopenharmony_ci
19913498266Sopenharmony_ci  cu = curl_url();
20013498266Sopenharmony_ci  if(!cu) {
20113498266Sopenharmony_ci    fprintf(stderr, "out of memory\n");
20213498266Sopenharmony_ci    exit(1);
20313498266Sopenharmony_ci  }
20413498266Sopenharmony_ci  if(curl_url_set(cu, CURLUPART_URL, url, 0)) {
20513498266Sopenharmony_ci    fprintf(stderr, "not a URL: '%s'\n", url);
20613498266Sopenharmony_ci    exit(1);
20713498266Sopenharmony_ci  }
20813498266Sopenharmony_ci  if(curl_url_get(cu, CURLUPART_HOST, &host, 0)) {
20913498266Sopenharmony_ci    fprintf(stderr, "could not get host of '%s'\n", url);
21013498266Sopenharmony_ci    exit(1);
21113498266Sopenharmony_ci  }
21213498266Sopenharmony_ci  if(curl_url_get(cu, CURLUPART_PORT, &port, 0)) {
21313498266Sopenharmony_ci    fprintf(stderr, "could not get port of '%s'\n", url);
21413498266Sopenharmony_ci    exit(1);
21513498266Sopenharmony_ci  }
21613498266Sopenharmony_ci  memset(&resolve, 0, sizeof(resolve));
21713498266Sopenharmony_ci  curl_msnprintf(resolve_buf, sizeof(resolve_buf)-1,
21813498266Sopenharmony_ci                 "%s:%s:127.0.0.1", host, port);
21913498266Sopenharmony_ci  resolve = curl_slist_append(resolve, resolve_buf);
22013498266Sopenharmony_ci
22113498266Sopenharmony_ci  for(i = 0; i<HANDLECOUNT; i++) {
22213498266Sopenharmony_ci    handles[i].idx = i;
22313498266Sopenharmony_ci    handles[i].paused = 0;
22413498266Sopenharmony_ci    handles[i].resumed = 0;
22513498266Sopenharmony_ci    handles[i].h = curl_easy_init();
22613498266Sopenharmony_ci    if(!handles[i].h ||
22713498266Sopenharmony_ci      curl_easy_setopt(handles[i].h, CURLOPT_WRITEFUNCTION, cb) != CURLE_OK ||
22813498266Sopenharmony_ci      curl_easy_setopt(handles[i].h, CURLOPT_WRITEDATA, &handles[i])
22913498266Sopenharmony_ci        != CURLE_OK ||
23013498266Sopenharmony_ci      curl_easy_setopt(handles[i].h, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK ||
23113498266Sopenharmony_ci      curl_easy_setopt(handles[i].h, CURLOPT_VERBOSE, 1L) != CURLE_OK ||
23213498266Sopenharmony_ci      curl_easy_setopt(handles[i].h, CURLOPT_DEBUGFUNCTION, debug_cb)
23313498266Sopenharmony_ci        != CURLE_OK ||
23413498266Sopenharmony_ci      curl_easy_setopt(handles[i].h, CURLOPT_SSL_VERIFYPEER, 0L) != CURLE_OK ||
23513498266Sopenharmony_ci      curl_easy_setopt(handles[i].h, CURLOPT_RESOLVE, resolve) != CURLE_OK ||
23613498266Sopenharmony_ci      curl_easy_setopt(handles[i].h, CURLOPT_URL, url) != CURLE_OK) {
23713498266Sopenharmony_ci      err();
23813498266Sopenharmony_ci    }
23913498266Sopenharmony_ci  }
24013498266Sopenharmony_ci
24113498266Sopenharmony_ci  multi_handle = curl_multi_init();
24213498266Sopenharmony_ci  if(!multi_handle)
24313498266Sopenharmony_ci    err();
24413498266Sopenharmony_ci
24513498266Sopenharmony_ci  for(i = 0; i<HANDLECOUNT; i++) {
24613498266Sopenharmony_ci    if(curl_multi_add_handle(multi_handle, handles[i].h) != CURLM_OK)
24713498266Sopenharmony_ci      err();
24813498266Sopenharmony_ci  }
24913498266Sopenharmony_ci
25013498266Sopenharmony_ci  for(rounds = 0;; rounds++) {
25113498266Sopenharmony_ci    fprintf(stderr, "INFO: multi_perform round %d\n", rounds);
25213498266Sopenharmony_ci    if(curl_multi_perform(multi_handle, &still_running) != CURLM_OK)
25313498266Sopenharmony_ci      err();
25413498266Sopenharmony_ci
25513498266Sopenharmony_ci    if(!still_running) {
25613498266Sopenharmony_ci      int as_expected = 1;
25713498266Sopenharmony_ci      fprintf(stderr, "INFO: no more handles running\n");
25813498266Sopenharmony_ci      for(i = 0; i<HANDLECOUNT; i++) {
25913498266Sopenharmony_ci        if(!handles[i].paused) {
26013498266Sopenharmony_ci          fprintf(stderr, "ERROR: [%d] NOT PAUSED\n", i);
26113498266Sopenharmony_ci          as_expected = 0;
26213498266Sopenharmony_ci        }
26313498266Sopenharmony_ci        else if(handles[i].paused != 1) {
26413498266Sopenharmony_ci          fprintf(stderr, "ERROR: [%d] PAUSED %d times!\n",
26513498266Sopenharmony_ci                  i, handles[i].paused);
26613498266Sopenharmony_ci          as_expected = 0;
26713498266Sopenharmony_ci        }
26813498266Sopenharmony_ci        else if(!handles[i].resumed) {
26913498266Sopenharmony_ci          fprintf(stderr, "ERROR: [%d] NOT resumed!\n", i);
27013498266Sopenharmony_ci          as_expected = 0;
27113498266Sopenharmony_ci        }
27213498266Sopenharmony_ci      }
27313498266Sopenharmony_ci      if(!as_expected) {
27413498266Sopenharmony_ci        fprintf(stderr, "ERROR: handles not in expected state "
27513498266Sopenharmony_ci                "after %d rounds\n", rounds);
27613498266Sopenharmony_ci        rc = 1;
27713498266Sopenharmony_ci      }
27813498266Sopenharmony_ci      break;
27913498266Sopenharmony_ci    }
28013498266Sopenharmony_ci
28113498266Sopenharmony_ci    if(curl_multi_poll(multi_handle, NULL, 0, 100, &numfds) != CURLM_OK)
28213498266Sopenharmony_ci      err();
28313498266Sopenharmony_ci
28413498266Sopenharmony_ci    while((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
28513498266Sopenharmony_ci      if(msg->msg == CURLMSG_DONE) {
28613498266Sopenharmony_ci        for(i = 0; i<HANDLECOUNT; i++) {
28713498266Sopenharmony_ci          if(msg->easy_handle == handles[i].h) {
28813498266Sopenharmony_ci            if(handles[i].paused != 1 || !handles[i].resumed) {
28913498266Sopenharmony_ci              fprintf(stderr, "ERROR: [%d] done, pauses=%d, resumed=%d, "
29013498266Sopenharmony_ci                      "result %d - wtf?\n", i, handles[i].paused,
29113498266Sopenharmony_ci                      handles[i].resumed, msg->data.result);
29213498266Sopenharmony_ci              rc = 1;
29313498266Sopenharmony_ci              goto out;
29413498266Sopenharmony_ci            }
29513498266Sopenharmony_ci          }
29613498266Sopenharmony_ci        }
29713498266Sopenharmony_ci      }
29813498266Sopenharmony_ci    }
29913498266Sopenharmony_ci
30013498266Sopenharmony_ci    /* Successfully paused? */
30113498266Sopenharmony_ci    if(!all_paused) {
30213498266Sopenharmony_ci      for(i = 0; i<HANDLECOUNT; i++) {
30313498266Sopenharmony_ci        if(!handles[i].paused) {
30413498266Sopenharmony_ci          break;
30513498266Sopenharmony_ci        }
30613498266Sopenharmony_ci      }
30713498266Sopenharmony_ci      all_paused = (i == HANDLECOUNT);
30813498266Sopenharmony_ci      if(all_paused) {
30913498266Sopenharmony_ci        fprintf(stderr, "INFO: all transfers paused\n");
31013498266Sopenharmony_ci        /* give transfer some rounds to mess things up */
31113498266Sopenharmony_ci        resume_round = rounds + 3;
31213498266Sopenharmony_ci      }
31313498266Sopenharmony_ci    }
31413498266Sopenharmony_ci    if(resume_round > 0 && rounds == resume_round) {
31513498266Sopenharmony_ci      /* time to resume */
31613498266Sopenharmony_ci      for(i = 0; i<HANDLECOUNT; i++) {
31713498266Sopenharmony_ci        fprintf(stderr, "INFO: [%d] resumed\n", i);
31813498266Sopenharmony_ci        handles[i].resumed = 1;
31913498266Sopenharmony_ci        curl_easy_pause(handles[i].h, CURLPAUSE_CONT);
32013498266Sopenharmony_ci      }
32113498266Sopenharmony_ci    }
32213498266Sopenharmony_ci  }
32313498266Sopenharmony_ci
32413498266Sopenharmony_ciout:
32513498266Sopenharmony_ci  for(i = 0; i<HANDLECOUNT; i++) {
32613498266Sopenharmony_ci    curl_multi_remove_handle(multi_handle, handles[i].h);
32713498266Sopenharmony_ci    curl_easy_cleanup(handles[i].h);
32813498266Sopenharmony_ci  }
32913498266Sopenharmony_ci
33013498266Sopenharmony_ci
33113498266Sopenharmony_ci  curl_slist_free_all(resolve);
33213498266Sopenharmony_ci  curl_free(host);
33313498266Sopenharmony_ci  curl_free(port);
33413498266Sopenharmony_ci  curl_url_cleanup(cu);
33513498266Sopenharmony_ci  curl_multi_cleanup(multi_handle);
33613498266Sopenharmony_ci  curl_global_cleanup();
33713498266Sopenharmony_ci
33813498266Sopenharmony_ci  return rc;
33913498266Sopenharmony_ci}
340