| 295 | } |
| 296 | |
| 297 | std::string Sock::RecvUntilTerminator(uint8_t terminator, |
| 298 | std::chrono::milliseconds timeout, |
| 299 | CThreadInterrupt& interrupt, |
| 300 | size_t max_data) const |
| 301 | { |
| 302 | const auto deadline = GetTime<std::chrono::milliseconds>() + timeout; |
| 303 | std::string data; |
| 304 | bool terminator_found{false}; |
| 305 | |
| 306 | // We must not consume any bytes past the terminator from the socket. |
| 307 | // One option is to read one byte at a time and check if we have read a terminator. |
| 308 | // However that is very slow. Instead, we peek at what is in the socket and only read |
| 309 | // as many bytes as possible without crossing the terminator. |
| 310 | // Reading 64 MiB of random data with 262526 terminator chars takes 37 seconds to read |
| 311 | // one byte at a time VS 0.71 seconds with the "peek" solution below. Reading one byte |
| 312 | // at a time is about 50 times slower. |
| 313 | |
| 314 | for (;;) { |
| 315 | if (data.size() >= max_data) { |
| 316 | throw std::runtime_error( |
| 317 | strprintf("Received too many bytes without a terminator (%u)", data.size())); |
| 318 | } |
| 319 | |
| 320 | char buf[512]; |
| 321 | |
| 322 | const ssize_t peek_ret{Recv(buf, std::min(sizeof(buf), max_data - data.size()), MSG_PEEK)}; |
| 323 | |
| 324 | switch (peek_ret) { |
| 325 | case -1: { |
| 326 | const int err{WSAGetLastError()}; |
| 327 | if (IOErrorIsPermanent(err)) { |
| 328 | throw std::runtime_error(strprintf("recv(): %s", NetworkErrorString(err))); |
| 329 | } |
| 330 | break; |
| 331 | } |
| 332 | case 0: |
| 333 | throw std::runtime_error("Connection unexpectedly closed by peer"); |
| 334 | default: |
| 335 | auto end = buf + peek_ret; |
| 336 | auto terminator_pos = std::find(buf, end, terminator); |
| 337 | terminator_found = terminator_pos != end; |
| 338 | |
| 339 | const size_t try_len{terminator_found ? terminator_pos - buf + 1 : |
| 340 | static_cast<size_t>(peek_ret)}; |
| 341 | |
| 342 | const ssize_t read_ret{Recv(buf, try_len, 0)}; |
| 343 | |
| 344 | if (read_ret < 0 || static_cast<size_t>(read_ret) != try_len) { |
| 345 | throw std::runtime_error( |
| 346 | strprintf("recv() returned %u bytes on attempt to read %u bytes but previous " |
| 347 | "peek claimed %u bytes are available", |
| 348 | read_ret, try_len, peek_ret)); |
| 349 | } |
| 350 | |
| 351 | // Don't include the terminator in the output. |
| 352 | const size_t append_len{terminator_found ? try_len - 1 : try_len}; |
| 353 | |
| 354 | data.append(buf, buf + append_len); |