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#include "curlcheck.h" 25 26#include "urldata.h" 27#include "bufq.h" 28#include "curl_trc.h" 29 30static CURLcode unit_setup(void) 31{ 32 CURLcode res = CURLE_OK; 33 return res; 34} 35 36static void unit_stop(void) 37{ 38} 39 40static const char *tail_err(struct bufq *q) 41{ 42 struct buf_chunk *chunk; 43 44 if(!q->tail) { 45 return q->head? "tail is NULL, but head is not" : NULL; 46 } 47 48 chunk = q->head; 49 while(chunk) { 50 if(chunk == q->tail) { 51 if(chunk->next) { 52 return "tail points to queue, but not at the end"; 53 } 54 return NULL; 55 } 56 chunk = chunk->next; 57 } 58 return "tail not part of queue"; 59} 60 61static void dump_bufq(struct bufq *q, const char *msg) 62{ 63 struct buf_chunk *chunk; 64 const char *terr; 65 size_t n; 66 67 fprintf(stderr, "bufq[chunk_size=%zu, max_chunks=%zu] %s\n", 68 q->chunk_size, q->max_chunks, msg); 69 fprintf(stderr, "- queue[\n"); 70 chunk = q->head; 71 while(chunk) { 72 fprintf(stderr, " chunk[len=%zu, roff=%zu, woff=%zu]\n", 73 chunk->dlen, chunk->r_offset, chunk->w_offset); 74 chunk = chunk->next; 75 } 76 fprintf(stderr, " ]\n"); 77 terr = tail_err(q); 78 fprintf(stderr, "- tail: %s\n", terr? terr : "ok"); 79 n = 0; 80 chunk = q->spare; 81 while(chunk) { 82 ++n; 83 chunk = chunk->next; 84 } 85 fprintf(stderr, "- chunks: %zu\n", q->chunk_count); 86 fprintf(stderr, "- spares: %zu\n", n); 87} 88 89static unsigned char test_data[32*1024]; 90 91static void check_bufq(size_t pool_spares, 92 size_t chunk_size, size_t max_chunks, 93 size_t wsize, size_t rsize, int opts) 94{ 95 struct bufq q; 96 struct bufc_pool pool; 97 size_t max_len = chunk_size * max_chunks; 98 CURLcode result; 99 ssize_t n, i; 100 size_t nwritten, nread; 101 102 if(pool_spares > 0) { 103 Curl_bufcp_init(&pool, chunk_size, pool_spares); 104 Curl_bufq_initp(&q, &pool, max_chunks, opts); 105 } 106 else { 107 Curl_bufq_init2(&q, chunk_size, max_chunks, opts); 108 } 109 110 fail_unless(q.chunk_size == chunk_size, "chunk_size init wrong"); 111 fail_unless(q.max_chunks == max_chunks, "max_chunks init wrong"); 112 fail_unless(q.head == NULL, "init: head not NULL"); 113 fail_unless(q.tail == NULL, "init: tail not NULL"); 114 fail_unless(q.spare == NULL, "init: spare not NULL"); 115 fail_unless(Curl_bufq_len(&q) == 0, "init: bufq length != 0"); 116 117 n = Curl_bufq_write(&q, test_data, wsize, &result); 118 fail_unless(n >= 0, "write: negative size returned"); 119 fail_unless((size_t)n <= wsize, "write: wrong size returned"); 120 fail_unless(result == CURLE_OK, "write: wrong result returned"); 121 122 /* write empty bufq full */ 123 nwritten = 0; 124 Curl_bufq_reset(&q); 125 while(!Curl_bufq_is_full(&q)) { 126 n = Curl_bufq_write(&q, test_data, wsize, &result); 127 if(n >= 0) { 128 nwritten += (size_t)n; 129 } 130 else if(result != CURLE_AGAIN) { 131 fail_unless(result == CURLE_AGAIN, "write-loop: unexpected result"); 132 break; 133 } 134 } 135 if(nwritten != max_len) { 136 fprintf(stderr, "%zu bytes written, but max_len=%zu\n", 137 nwritten, max_len); 138 dump_bufq(&q, "after writing full"); 139 fail_if(TRUE, "write: bufq full but nwritten wrong"); 140 } 141 142 /* read full bufq empty */ 143 nread = 0; 144 while(!Curl_bufq_is_empty(&q)) { 145 n = Curl_bufq_read(&q, test_data, rsize, &result); 146 if(n >= 0) { 147 nread += (size_t)n; 148 } 149 else if(result != CURLE_AGAIN) { 150 fail_unless(result == CURLE_AGAIN, "read-loop: unexpected result"); 151 break; 152 } 153 } 154 if(nread != max_len) { 155 fprintf(stderr, "%zu bytes read, but max_len=%zu\n", 156 nwritten, max_len); 157 dump_bufq(&q, "after reading empty"); 158 fail_if(TRUE, "read: bufq empty but nread wrong"); 159 } 160 if(q.tail) { 161 dump_bufq(&q, "after reading empty"); 162 fail_if(TRUE, "read empty, but tail is not NULL"); 163 } 164 165 for(i = 0; i < 1000; ++i) { 166 n = Curl_bufq_write(&q, test_data, wsize, &result); 167 if(n < 0 && result != CURLE_AGAIN) { 168 fail_unless(result == CURLE_AGAIN, "rw-loop: unexpected write result"); 169 break; 170 } 171 n = Curl_bufq_read(&q, test_data, rsize, &result); 172 if(n < 0 && result != CURLE_AGAIN) { 173 fail_unless(result == CURLE_AGAIN, "rw-loop: unexpected read result"); 174 break; 175 } 176 } 177 178 /* Test SOFT_LIMIT option */ 179 Curl_bufq_free(&q); 180 Curl_bufq_init2(&q, chunk_size, max_chunks, (opts|BUFQ_OPT_SOFT_LIMIT)); 181 nwritten = 0; 182 while(!Curl_bufq_is_full(&q)) { 183 n = Curl_bufq_write(&q, test_data, wsize, &result); 184 if(n < 0 || (size_t)n != wsize) { 185 fail_unless(n > 0 && (size_t)n == wsize, "write should be complete"); 186 break; 187 } 188 nwritten += (size_t)n; 189 } 190 if(nwritten < max_len) { 191 fprintf(stderr, "%zu bytes written, but max_len=%zu\n", 192 nwritten, max_len); 193 dump_bufq(&q, "after writing full"); 194 fail_if(TRUE, "write: bufq full but nwritten wrong"); 195 } 196 /* do one more write on a full bufq, should work */ 197 n = Curl_bufq_write(&q, test_data, wsize, &result); 198 fail_unless(n > 0 && (size_t)n == wsize, "write should be complete"); 199 nwritten += (size_t)n; 200 /* see that we get all out again */ 201 nread = 0; 202 while(!Curl_bufq_is_empty(&q)) { 203 n = Curl_bufq_read(&q, test_data, rsize, &result); 204 if(n <= 0) { 205 fail_unless(n > 0, "read-loop: unexpected fail"); 206 break; 207 } 208 nread += (size_t)n; 209 } 210 fail_unless(nread == nwritten, "did not get the same out as put in"); 211 212 dump_bufq(&q, "at end of test"); 213 Curl_bufq_free(&q); 214 if(pool_spares > 0) 215 Curl_bufcp_free(&pool); 216} 217 218UNITTEST_START 219 struct bufq q; 220 ssize_t n; 221 CURLcode result; 222 unsigned char buf[16*1024]; 223 224 Curl_bufq_init(&q, 8*1024, 12); 225 n = Curl_bufq_read(&q, buf, 128, &result); 226 fail_unless(n < 0 && result == CURLE_AGAIN, "read empty fail"); 227 Curl_bufq_free(&q); 228 229 check_bufq(0, 1024, 4, 128, 128, BUFQ_OPT_NONE); 230 check_bufq(0, 1024, 4, 129, 127, BUFQ_OPT_NONE); 231 check_bufq(0, 1024, 4, 2000, 16000, BUFQ_OPT_NONE); 232 check_bufq(0, 1024, 4, 16000, 3000, BUFQ_OPT_NONE); 233 234 check_bufq(0, 8000, 10, 1234, 1234, BUFQ_OPT_NONE); 235 check_bufq(0, 8000, 10, 8*1024, 4*1024, BUFQ_OPT_NONE); 236 237 check_bufq(0, 1024, 4, 128, 128, BUFQ_OPT_NO_SPARES); 238 check_bufq(0, 1024, 4, 129, 127, BUFQ_OPT_NO_SPARES); 239 check_bufq(0, 1024, 4, 2000, 16000, BUFQ_OPT_NO_SPARES); 240 check_bufq(0, 1024, 4, 16000, 3000, BUFQ_OPT_NO_SPARES); 241 242 check_bufq(8, 1024, 4, 128, 128, BUFQ_OPT_NONE); 243 check_bufq(8, 8000, 10, 1234, 1234, BUFQ_OPT_NONE); 244 check_bufq(8, 1024, 4, 129, 127, BUFQ_OPT_NO_SPARES); 245 246UNITTEST_STOP 247