h2_pausing.c (8832B)
1 /*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. 9 * 10 * This software is licensed as described in the file COPYING, which 11 * you should have received as part of this distribution. The terms 12 * are also available at https://curl.se/docs/copyright.html. 13 * 14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell 15 * copies of the Software, and permit persons to whom the Software is 16 * furnished to do so, under the terms of the COPYING file. 17 * 18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 19 * KIND, either express or implied. 20 * 21 * SPDX-License-Identifier: curl 22 * 23 ***************************************************************************/ 24 /* This is based on the PoC client of issue #11982 25 */ 26 #include "first.h" 27 28 static void usage_h2_pausing(const char *msg) 29 { 30 if(msg) 31 curl_mfprintf(stderr, "%s\n", msg); 32 curl_mfprintf(stderr, 33 "usage: [options] url\n" 34 " pause downloads with following options:\n" 35 " -V http_version (http/1.1, h2, h3) http version to use\n" 36 ); 37 } 38 39 struct handle 40 { 41 size_t idx; 42 int paused; 43 int resumed; 44 int errored; 45 int fail_write; 46 CURL *h; 47 }; 48 49 static size_t cb(char *data, size_t size, size_t nmemb, void *clientp) 50 { 51 size_t realsize = size * nmemb; 52 struct handle *handle = (struct handle *) clientp; 53 curl_off_t totalsize; 54 55 (void)data; 56 if(curl_easy_getinfo(handle->h, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, 57 &totalsize) == CURLE_OK) 58 curl_mfprintf(stderr, "INFO: [%d] write, " 59 "Content-Length %" CURL_FORMAT_CURL_OFF_T "\n", 60 (int)handle->idx, totalsize); 61 62 if(!handle->resumed) { 63 ++handle->paused; 64 curl_mfprintf(stderr, "INFO: [%d] write, PAUSING %d time on %lu bytes\n", 65 (int)handle->idx, handle->paused, (long)realsize); 66 assert(handle->paused == 1); 67 return CURL_WRITEFUNC_PAUSE; 68 } 69 if(handle->fail_write) { 70 ++handle->errored; 71 curl_mfprintf(stderr, "INFO: [%d] FAIL write of %lu bytes, %d time\n", 72 (int)handle->idx, (long)realsize, handle->errored); 73 return CURL_WRITEFUNC_ERROR; 74 } 75 curl_mfprintf(stderr, "INFO: [%d] write, accepting %lu bytes\n", 76 (int)handle->idx, (long)realsize); 77 return realsize; 78 } 79 80 static int test_h2_pausing(int argc, char *argv[]) 81 { 82 struct handle handles[2]; 83 CURLM *multi_handle; 84 int still_running = 1, msgs_left, numfds; 85 size_t i; 86 CURLMsg *msg; 87 int rounds = 0; 88 int rc = 0; 89 CURLU *cu; 90 struct curl_slist *resolve = NULL; 91 char resolve_buf[1024]; 92 char *url, *host = NULL, *port = NULL; 93 int all_paused = 0; 94 int resume_round = -1; 95 int http_version = CURL_HTTP_VERSION_2_0; 96 int ch; 97 98 while((ch = cgetopt(argc, argv, "hV:")) != -1) { 99 switch(ch) { 100 case 'h': 101 usage_h2_pausing(NULL); 102 return 2; 103 case 'V': { 104 if(!strcmp("http/1.1", coptarg)) 105 http_version = CURL_HTTP_VERSION_1_1; 106 else if(!strcmp("h2", coptarg)) 107 http_version = CURL_HTTP_VERSION_2_0; 108 else if(!strcmp("h3", coptarg)) 109 http_version = CURL_HTTP_VERSION_3ONLY; 110 else { 111 usage_h2_pausing("invalid http version"); 112 return 1; 113 } 114 break; 115 } 116 default: 117 usage_h2_pausing("invalid option"); 118 return 1; 119 } 120 } 121 argc -= coptind; 122 argv += coptind; 123 124 if(argc != 1) { 125 curl_mfprintf(stderr, "ERROR: need URL as argument\n"); 126 return 2; 127 } 128 url = argv[0]; 129 130 curl_global_init(CURL_GLOBAL_DEFAULT); 131 curl_global_trace("ids,time,http/2,http/3"); 132 133 cu = curl_url(); 134 if(!cu) { 135 curl_mfprintf(stderr, "out of memory\n"); 136 return 1; 137 } 138 if(curl_url_set(cu, CURLUPART_URL, url, 0)) { 139 curl_mfprintf(stderr, "not a URL: '%s'\n", url); 140 return 1; 141 } 142 if(curl_url_get(cu, CURLUPART_HOST, &host, 0)) { 143 curl_mfprintf(stderr, "could not get host of '%s'\n", url); 144 return 1; 145 } 146 if(curl_url_get(cu, CURLUPART_PORT, &port, 0)) { 147 curl_mfprintf(stderr, "could not get port of '%s'\n", url); 148 return 1; 149 } 150 memset(&resolve, 0, sizeof(resolve)); 151 curl_msnprintf(resolve_buf, sizeof(resolve_buf)-1, "%s:%s:127.0.0.1", 152 host, port); 153 resolve = curl_slist_append(resolve, resolve_buf); 154 155 for(i = 0; i < CURL_ARRAYSIZE(handles); i++) { 156 handles[i].idx = i; 157 handles[i].paused = 0; 158 handles[i].resumed = 0; 159 handles[i].errored = 0; 160 handles[i].fail_write = 1; 161 handles[i].h = curl_easy_init(); 162 if(!handles[i].h || 163 curl_easy_setopt(handles[i].h, CURLOPT_WRITEFUNCTION, cb) != CURLE_OK || 164 curl_easy_setopt(handles[i].h, CURLOPT_WRITEDATA, &handles[i]) 165 != CURLE_OK || 166 curl_easy_setopt(handles[i].h, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK || 167 curl_easy_setopt(handles[i].h, CURLOPT_VERBOSE, 1L) != CURLE_OK || 168 curl_easy_setopt(handles[i].h, CURLOPT_DEBUGFUNCTION, debug_cb) 169 != CURLE_OK || 170 curl_easy_setopt(handles[i].h, CURLOPT_SSL_VERIFYPEER, 0L) != CURLE_OK || 171 curl_easy_setopt(handles[i].h, CURLOPT_RESOLVE, resolve) != CURLE_OK || 172 curl_easy_setopt(handles[i].h, CURLOPT_PIPEWAIT, 1L) || 173 curl_easy_setopt(handles[i].h, CURLOPT_URL, url) != CURLE_OK) { 174 ERR(); 175 } 176 curl_easy_setopt(handles[i].h, CURLOPT_HTTP_VERSION, (long)http_version); 177 } 178 179 multi_handle = curl_multi_init(); 180 if(!multi_handle) 181 ERR(); 182 183 for(i = 0; i < CURL_ARRAYSIZE(handles); i++) { 184 if(curl_multi_add_handle(multi_handle, handles[i].h) != CURLM_OK) 185 ERR(); 186 } 187 188 for(rounds = 0;; rounds++) { 189 curl_mfprintf(stderr, "INFO: multi_perform round %d\n", rounds); 190 if(curl_multi_perform(multi_handle, &still_running) != CURLM_OK) 191 ERR(); 192 193 if(!still_running) { 194 int as_expected = 1; 195 curl_mfprintf(stderr, "INFO: no more handles running\n"); 196 for(i = 0; i < CURL_ARRAYSIZE(handles); i++) { 197 if(!handles[i].paused) { 198 curl_mfprintf(stderr, "ERROR: [%d] NOT PAUSED\n", (int)i); 199 as_expected = 0; 200 } 201 else if(handles[i].paused != 1) { 202 curl_mfprintf(stderr, "ERROR: [%d] PAUSED %d times!\n", 203 (int)i, handles[i].paused); 204 as_expected = 0; 205 } 206 else if(!handles[i].resumed) { 207 curl_mfprintf(stderr, "ERROR: [%d] NOT resumed!\n", (int)i); 208 as_expected = 0; 209 } 210 else if(handles[i].errored != 1) { 211 curl_mfprintf(stderr, "ERROR: [%d] NOT errored once, %d instead!\n", 212 (int)i, handles[i].errored); 213 as_expected = 0; 214 } 215 } 216 if(!as_expected) { 217 curl_mfprintf(stderr, "ERROR: handles not in expected state " 218 "after %d rounds\n", rounds); 219 rc = 1; 220 } 221 break; 222 } 223 224 if(curl_multi_poll(multi_handle, NULL, 0, 100, &numfds) != CURLM_OK) 225 ERR(); 226 227 /* !checksrc! disable EQUALSNULL 1 */ 228 while((msg = curl_multi_info_read(multi_handle, &msgs_left)) != NULL) { 229 if(msg->msg == CURLMSG_DONE) { 230 for(i = 0; i < CURL_ARRAYSIZE(handles); i++) { 231 if(msg->easy_handle == handles[i].h) { 232 if(handles[i].paused != 1 || !handles[i].resumed) { 233 curl_mfprintf(stderr, "ERROR: [%d] done, pauses=%d, resumed=%d, " 234 "result %d - wtf?\n", (int)i, handles[i].paused, 235 handles[i].resumed, msg->data.result); 236 rc = 1; 237 goto out; 238 } 239 } 240 } 241 } 242 } 243 244 /* Successfully paused? */ 245 if(!all_paused) { 246 for(i = 0; i < CURL_ARRAYSIZE(handles); i++) { 247 if(!handles[i].paused) { 248 break; 249 } 250 } 251 all_paused = (i == CURL_ARRAYSIZE(handles)); 252 if(all_paused) { 253 curl_mfprintf(stderr, "INFO: all transfers paused\n"); 254 /* give transfer some rounds to mess things up */ 255 resume_round = rounds + 2; 256 } 257 } 258 if(resume_round > 0 && rounds == resume_round) { 259 /* time to resume */ 260 for(i = 0; i < CURL_ARRAYSIZE(handles); i++) { 261 curl_mfprintf(stderr, "INFO: [%d] resumed\n", (int)i); 262 handles[i].resumed = 1; 263 curl_easy_pause(handles[i].h, CURLPAUSE_CONT); 264 } 265 } 266 } 267 268 out: 269 for(i = 0; i < CURL_ARRAYSIZE(handles); i++) { 270 curl_multi_remove_handle(multi_handle, handles[i].h); 271 curl_easy_cleanup(handles[i].h); 272 } 273 274 curl_slist_free_all(resolve); 275 curl_free(host); 276 curl_free(port); 277 curl_url_cleanup(cu); 278 curl_multi_cleanup(multi_handle); 279 curl_global_cleanup(); 280 281 return rc; 282 }