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/* <DESC> 25 * Websockets data echos 26 * </DESC> 27 */ 28 29/* curl stuff */ 30#include "curl_setup.h" 31#include <curl/curl.h> 32 33#include <stdio.h> 34#include <stdlib.h> 35#include <string.h> 36 37/* somewhat unix-specific */ 38#include <sys/time.h> 39#include <unistd.h> 40 41#ifdef USE_WEBSOCKETS 42 43static 44void dump(const char *text, unsigned char *ptr, size_t size, 45 char nohex) 46{ 47 size_t i; 48 size_t c; 49 50 unsigned int width = 0x10; 51 52 if(nohex) 53 /* without the hex output, we can fit more on screen */ 54 width = 0x40; 55 56 fprintf(stderr, "%s, %lu bytes (0x%lx)\n", 57 text, (unsigned long)size, (unsigned long)size); 58 59 for(i = 0; i<size; i += width) { 60 61 fprintf(stderr, "%4.4lx: ", (unsigned long)i); 62 63 if(!nohex) { 64 /* hex not disabled, show it */ 65 for(c = 0; c < width; c++) 66 if(i + c < size) 67 fprintf(stderr, "%02x ", ptr[i + c]); 68 else 69 fputs(" ", stderr); 70 } 71 72 for(c = 0; (c < width) && (i + c < size); c++) { 73 /* check for 0D0A; if found, skip past and start a new line of output */ 74 if(nohex && (i + c + 1 < size) && ptr[i + c] == 0x0D && 75 ptr[i + c + 1] == 0x0A) { 76 i += (c + 2 - width); 77 break; 78 } 79 fprintf(stderr, "%c", 80 (ptr[i + c] >= 0x20) && (ptr[i + c]<0x80)?ptr[i + c]:'.'); 81 /* check again for 0D0A, to avoid an extra \n if it's at width */ 82 if(nohex && (i + c + 2 < size) && ptr[i + c + 1] == 0x0D && 83 ptr[i + c + 2] == 0x0A) { 84 i += (c + 3 - width); 85 break; 86 } 87 } 88 fputc('\n', stderr); /* newline */ 89 } 90} 91 92static CURLcode send_binary(CURL *curl, char *buf, size_t buflen) 93{ 94 size_t nwritten; 95 CURLcode result = 96 curl_ws_send(curl, buf, buflen, &nwritten, 0, CURLWS_BINARY); 97 fprintf(stderr, "ws: send_binary(len=%ld) -> %d, %ld\n", 98 (long)buflen, result, (long)nwritten); 99 return result; 100} 101 102static CURLcode recv_binary(CURL *curl, char *exp_data, size_t exp_len) 103{ 104 const struct curl_ws_frame *frame; 105 char recvbuf[256]; 106 size_t r_offset, nread; 107 CURLcode result; 108 109 fprintf(stderr, "recv_binary: expected payload %ld bytes\n", (long)exp_len); 110 r_offset = 0; 111 while(1) { 112 result = curl_ws_recv(curl, recvbuf, sizeof(recvbuf), &nread, &frame); 113 if(result == CURLE_AGAIN) { 114 fprintf(stderr, "EAGAIN, sleep, try again\n"); 115 usleep(100*1000); 116 continue; 117 } 118 fprintf(stderr, "ws: curl_ws_recv(offset=%ld, len=%ld) -> %d, %ld\n", 119 (long)r_offset, (long)sizeof(recvbuf), result, (long)nread); 120 if(result) { 121 return result; 122 } 123 if(!(frame->flags & CURLWS_BINARY)) { 124 fprintf(stderr, "recv_data: wrong frame, got %ld bytes rflags %x\n", 125 (long)nread, frame->flags); 126 return CURLE_RECV_ERROR; 127 } 128 if(frame->offset != (curl_off_t)r_offset) { 129 fprintf(stderr, "recv_data: frame offset, expected %ld, got %ld\n", 130 (long)r_offset, (long)frame->offset); 131 return CURLE_RECV_ERROR; 132 } 133 if(frame->bytesleft != (curl_off_t)(exp_len - r_offset - nread)) { 134 fprintf(stderr, "recv_data: frame bytesleft, expected %ld, got %ld\n", 135 (long)(exp_len - r_offset - nread), (long)frame->bytesleft); 136 return CURLE_RECV_ERROR; 137 } 138 if(r_offset + nread > exp_len) { 139 fprintf(stderr, "recv_data: data length, expected %ld, now at %ld\n", 140 (long)exp_len, (long)(r_offset + nread)); 141 return CURLE_RECV_ERROR; 142 } 143 if(memcmp(exp_data + r_offset, recvbuf, nread)) { 144 fprintf(stderr, "recv_data: data differs, offset=%ld, len=%ld\n", 145 (long)r_offset, (long)nread); 146 dump("expected:", (unsigned char *)exp_data + r_offset, nread, 0); 147 dump("received:", (unsigned char *)recvbuf, nread, 0); 148 return CURLE_RECV_ERROR; 149 } 150 r_offset += nread; 151 if(r_offset >= exp_len) { 152 fprintf(stderr, "recv_data: frame complete\n"); 153 break; 154 } 155 } 156 return CURLE_OK; 157} 158 159/* just close the connection */ 160static void websocket_close(CURL *curl) 161{ 162 size_t sent; 163 CURLcode result = 164 curl_ws_send(curl, "", 0, &sent, 0, CURLWS_CLOSE); 165 fprintf(stderr, 166 "ws: curl_ws_send returned %u, sent %u\n", (int)result, (int)sent); 167} 168 169static CURLcode data_echo(CURL *curl, size_t plen_min, size_t plen_max) 170{ 171 CURLcode res; 172 size_t len; 173 char *send_buf; 174 size_t i; 175 176 send_buf = calloc(1, plen_max); 177 if(!send_buf) 178 return CURLE_OUT_OF_MEMORY; 179 for(i = 0; i < plen_max; ++i) { 180 send_buf[i] = (char)('0' + ((int)i % 10)); 181 } 182 183 for(len = plen_min; len <= plen_max; ++len) { 184 res = send_binary(curl, send_buf, len); 185 if(res) 186 goto out; 187 res = recv_binary(curl, send_buf, len); 188 if(res) { 189 fprintf(stderr, "recv_data(len=%ld) -> %d\n", (long)len, res); 190 goto out; 191 } 192 } 193 194out: 195 if(!res) 196 websocket_close(curl); 197 free(send_buf); 198 return res; 199} 200 201#endif 202 203int main(int argc, char *argv[]) 204{ 205#ifdef USE_WEBSOCKETS 206 CURL *curl; 207 CURLcode res = CURLE_OK; 208 const char *url; 209 long l1, l2; 210 size_t plen_min, plen_max; 211 212 213 if(argc != 4) { 214 fprintf(stderr, "usage: ws-data url minlen maxlen\n"); 215 return 2; 216 } 217 url = argv[1]; 218 l1 = strtol(argv[2], NULL, 10); 219 if(l1 < 0) { 220 fprintf(stderr, "minlen must be >= 0, got %ld\n", l1); 221 return 2; 222 } 223 l2 = strtol(argv[3], NULL, 10); 224 if(l2 < 0) { 225 fprintf(stderr, "maxlen must be >= 0, got %ld\n", l2); 226 return 2; 227 } 228 plen_min = l1; 229 plen_max = l2; 230 if(plen_max < plen_min) { 231 fprintf(stderr, "maxlen must be >= minlen, got %ld-%ld\n", 232 (long)plen_min, (long)plen_max); 233 return 2; 234 } 235 236 curl_global_init(CURL_GLOBAL_ALL); 237 238 curl = curl_easy_init(); 239 if(curl) { 240 curl_easy_setopt(curl, CURLOPT_URL, url); 241 242 /* use the callback style */ 243 curl_easy_setopt(curl, CURLOPT_USERAGENT, "ws-data"); 244 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); 245 curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L); /* websocket style */ 246 res = curl_easy_perform(curl); 247 fprintf(stderr, "curl_easy_perform() returned %u\n", (int)res); 248 if(res == CURLE_OK) 249 res = data_echo(curl, plen_min, plen_max); 250 251 /* always cleanup */ 252 curl_easy_cleanup(curl); 253 } 254 curl_global_cleanup(); 255 return (int)res; 256 257#else /* USE_WEBSOCKETS */ 258 (void)argc; 259 (void)argv; 260 fprintf(stderr, "websockets not enabled in libcurl\n"); 261 return 1; 262#endif /* !USE_WEBSOCKETS */ 263} 264