| /* Copyright (c) 2014, Google Inc. |
| * |
| * Permission to use, copy, modify, and/or distribute this software for any |
| * purpose with or without fee is hereby granted, provided that the above |
| * copyright notice and this permission notice appear in all copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
| * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
| * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
| * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ |
| |
| #if !defined(_POSIX_C_SOURCE) |
| #define _POSIX_C_SOURCE 201410L |
| #endif |
| |
| #include <openssl/base.h> |
| |
| #if !defined(OPENSSL_WINDOWS) |
| #include <arpa/inet.h> |
| #include <fcntl.h> |
| #include <netinet/in.h> |
| #include <string.h> |
| #include <sys/socket.h> |
| #include <unistd.h> |
| #else |
| #include <io.h> |
| #include <WinSock2.h> |
| #include <WS2tcpip.h> |
| #endif |
| |
| #include <openssl/bio.h> |
| #include <openssl/crypto.h> |
| #include <openssl/err.h> |
| |
| #define MIN(a, b) ((a < b) ? a : b) |
| |
| #if !defined(OPENSSL_WINDOWS) |
| static int closesocket(int sock) { |
| return close(sock); |
| } |
| |
| static void print_socket_error(const char *func) { |
| perror(func); |
| } |
| #else |
| static void print_socket_error(const char *func) { |
| fprintf(stderr, "%s: %d\n", func, WSAGetLastError()); |
| } |
| #endif |
| |
| static int test_socket_connect(void) { |
| int listening_sock = socket(AF_INET, SOCK_STREAM, 0); |
| int sock; |
| struct sockaddr_in sin; |
| socklen_t sockaddr_len = sizeof(sin); |
| static const char kTestMessage[] = "test"; |
| char hostname[80], buf[5]; |
| BIO *bio; |
| |
| memset(&sin, 0, sizeof(sin)); |
| sin.sin_family = AF_INET; |
| if (!inet_pton(AF_INET, "127.0.0.1", &sin.sin_addr)) { |
| print_socket_error("inet_pton"); |
| return 0; |
| } |
| |
| if (bind(listening_sock, (struct sockaddr *)&sin, sizeof(sin)) != 0) { |
| print_socket_error("bind"); |
| return 0; |
| } |
| |
| if (listen(listening_sock, 1)) { |
| print_socket_error("listen"); |
| return 0; |
| } |
| |
| if (getsockname(listening_sock, (struct sockaddr *)&sin, &sockaddr_len) || |
| sockaddr_len != sizeof(sin)) { |
| print_socket_error("getsockname"); |
| return 0; |
| } |
| |
| BIO_snprintf(hostname, sizeof(hostname), "%s:%d", "127.0.0.1", |
| ntohs(sin.sin_port)); |
| bio = BIO_new_connect(hostname); |
| if (!bio) { |
| fprintf(stderr, "BIO_new_connect failed.\n"); |
| return 0; |
| } |
| |
| if (BIO_write(bio, kTestMessage, sizeof(kTestMessage)) != |
| sizeof(kTestMessage)) { |
| fprintf(stderr, "BIO_write failed.\n"); |
| BIO_print_errors_fp(stderr); |
| return 0; |
| } |
| |
| sock = accept(listening_sock, (struct sockaddr *) &sin, &sockaddr_len); |
| if (sock < 0) { |
| print_socket_error("accept"); |
| return 0; |
| } |
| |
| if (recv(sock, buf, sizeof(buf), 0) != sizeof(kTestMessage)) { |
| print_socket_error("read"); |
| return 0; |
| } |
| |
| if (memcmp(buf, kTestMessage, sizeof(kTestMessage))) { |
| return 0; |
| } |
| |
| closesocket(sock); |
| closesocket(listening_sock); |
| BIO_free(bio); |
| |
| return 1; |
| } |
| |
| |
| /* bio_read_zero_copy_wrapper is a wrapper around the zero-copy APIs to make |
| * testing easier. */ |
| static size_t bio_read_zero_copy_wrapper(BIO *bio, uint8_t *data, size_t len) { |
| uint8_t *read_buf; |
| size_t read_buf_offset; |
| size_t available_bytes; |
| size_t len_read = 0; |
| |
| do { |
| if (!BIO_zero_copy_get_read_buf(bio, &read_buf, &read_buf_offset, |
| &available_bytes)) { |
| return 0; |
| } |
| |
| available_bytes = MIN(available_bytes, len - len_read); |
| memmove(data + len_read, read_buf + read_buf_offset, available_bytes); |
| |
| BIO_zero_copy_get_read_buf_done(bio, available_bytes); |
| |
| len_read += available_bytes; |
| } while (len - len_read > 0 && available_bytes > 0); |
| |
| return len_read; |
| } |
| |
| /* bio_write_zero_copy_wrapper is a wrapper around the zero-copy APIs to make |
| * testing easier. */ |
| static size_t bio_write_zero_copy_wrapper(BIO *bio, const uint8_t *data, |
| size_t len) { |
| uint8_t *write_buf; |
| size_t write_buf_offset; |
| size_t available_bytes; |
| size_t len_written = 0; |
| |
| do { |
| if (!BIO_zero_copy_get_write_buf(bio, &write_buf, &write_buf_offset, |
| &available_bytes)) { |
| return 0; |
| } |
| |
| available_bytes = MIN(available_bytes, len - len_written); |
| memmove(write_buf + write_buf_offset, data + len_written, available_bytes); |
| |
| BIO_zero_copy_get_write_buf_done(bio, available_bytes); |
| |
| len_written += available_bytes; |
| } while (len - len_written > 0 && available_bytes > 0); |
| |
| return len_written; |
| } |
| |
| static int test_zero_copy_bio_pairs(void) { |
| /* Test read and write, especially triggering the ring buffer wrap-around.*/ |
| BIO* bio1; |
| BIO* bio2; |
| size_t i, j; |
| uint8_t bio1_application_send_buffer[1024]; |
| uint8_t bio2_application_recv_buffer[1024]; |
| size_t total_read = 0; |
| size_t total_write = 0; |
| uint8_t* write_buf; |
| size_t write_buf_offset; |
| size_t available_bytes; |
| size_t bytes_left; |
| |
| const size_t kLengths[] = {254, 255, 256, 257, 510, 511, 512, 513}; |
| |
| /* These trigger ring buffer wrap around. */ |
| const size_t kPartialLengths[] = {0, 1, 2, 3, 128, 255, 256, 257, 511, 512}; |
| |
| static const size_t kBufferSize = 512; |
| |
| srand(1); |
| for (i = 0; i < sizeof(bio1_application_send_buffer); i++) { |
| bio1_application_send_buffer[i] = rand() & 255; |
| } |
| |
| /* Transfer bytes from bio1_application_send_buffer to |
| * bio2_application_recv_buffer in various ways. */ |
| for (i = 0; i < sizeof(kLengths) / sizeof(kLengths[0]); i++) { |
| for (j = 0; j < sizeof(kPartialLengths) / sizeof(kPartialLengths[0]); j++) { |
| total_write = 0; |
| total_read = 0; |
| |
| BIO_new_bio_pair(&bio1, kBufferSize, &bio2, kBufferSize); |
| |
| total_write += bio_write_zero_copy_wrapper( |
| bio1, bio1_application_send_buffer, kLengths[i]); |
| |
| /* This tests interleaved read/write calls. Do a read between zero copy |
| * write calls. */ |
| if (!BIO_zero_copy_get_write_buf(bio1, &write_buf, &write_buf_offset, |
| &available_bytes)) { |
| return 0; |
| } |
| |
| /* Free kPartialLengths[j] bytes in the beginning of bio1 write buffer. |
| * This enables ring buffer wrap around for the next write. */ |
| total_read += BIO_read(bio2, bio2_application_recv_buffer + total_read, |
| kPartialLengths[j]); |
| |
| size_t interleaved_write_len = MIN(kPartialLengths[j], available_bytes); |
| |
| /* Write the data for the interleaved write call. If the buffer becomes |
| * empty after a read, the write offset is normally set to 0. Check that |
| * this does not happen for interleaved read/write and that |
| * |write_buf_offset| is still valid. */ |
| memcpy(write_buf + write_buf_offset, |
| bio1_application_send_buffer + total_write, interleaved_write_len); |
| if (BIO_zero_copy_get_write_buf_done(bio1, interleaved_write_len)) { |
| total_write += interleaved_write_len; |
| } |
| |
| /* Do another write in case |write_buf_offset| was wrapped */ |
| total_write += bio_write_zero_copy_wrapper( |
| bio1, bio1_application_send_buffer + total_write, |
| kPartialLengths[j] - interleaved_write_len); |
| |
| /* Drain the rest. */ |
| bytes_left = BIO_pending(bio2); |
| total_read += bio_read_zero_copy_wrapper( |
| bio2, bio2_application_recv_buffer + total_read, bytes_left); |
| |
| BIO_free(bio1); |
| BIO_free(bio2); |
| |
| if (total_read != total_write) { |
| fprintf(stderr, "Lengths not equal in round (%u, %u)\n", (unsigned)i, |
| (unsigned)j); |
| return 0; |
| } |
| if (total_read > kLengths[i] + kPartialLengths[j]) { |
| fprintf(stderr, "Bad lengths in round (%u, %u)\n", (unsigned)i, |
| (unsigned)j); |
| return 0; |
| } |
| if (memcmp(bio1_application_send_buffer, bio2_application_recv_buffer, |
| total_read) != 0) { |
| fprintf(stderr, "Buffers not equal in round (%u, %u)\n", (unsigned)i, |
| (unsigned)j); |
| return 0; |
| } |
| } |
| } |
| |
| return 1; |
| } |
| |
| static int test_printf(void) { |
| /* Test a short output, a very long one, and various sizes around |
| * 256 (the size of the buffer) to ensure edge cases are correct. */ |
| static const size_t kLengths[] = { 5, 250, 251, 252, 253, 254, 1023 }; |
| BIO *bio; |
| char string[1024]; |
| int ret; |
| const uint8_t *contents; |
| size_t i, len; |
| |
| bio = BIO_new(BIO_s_mem()); |
| if (!bio) { |
| fprintf(stderr, "BIO_new failed\n"); |
| return 0; |
| } |
| |
| for (i = 0; i < sizeof(kLengths) / sizeof(kLengths[0]); i++) { |
| if (kLengths[i] >= sizeof(string)) { |
| fprintf(stderr, "Bad test string length\n"); |
| return 0; |
| } |
| memset(string, 'a', sizeof(string)); |
| string[kLengths[i]] = '\0'; |
| |
| ret = BIO_printf(bio, "test %s", string); |
| if (ret != 5 + kLengths[i]) { |
| fprintf(stderr, "BIO_printf failed: %d\n", ret); |
| return 0; |
| } |
| if (!BIO_mem_contents(bio, &contents, &len)) { |
| fprintf(stderr, "BIO_mem_contents failed\n"); |
| return 0; |
| } |
| if (len != 5 + kLengths[i] || |
| strncmp((const char *)contents, "test ", 5) != 0 || |
| strncmp((const char *)contents + 5, string, kLengths[i]) != 0) { |
| fprintf(stderr, "Contents did not match: %.*s\n", (int)len, contents); |
| return 0; |
| } |
| |
| if (!BIO_reset(bio)) { |
| fprintf(stderr, "BIO_reset failed\n"); |
| return 0; |
| } |
| } |
| |
| BIO_free(bio); |
| return 1; |
| } |
| |
| int main(void) { |
| #if defined(OPENSSL_WINDOWS) |
| WSADATA wsa_data; |
| WORD wsa_version; |
| int wsa_err; |
| #endif |
| |
| CRYPTO_library_init(); |
| ERR_load_crypto_strings(); |
| |
| #if defined(OPENSSL_WINDOWS) |
| /* Initialize Winsock. */ |
| wsa_version = MAKEWORD(2, 2); |
| wsa_err = WSAStartup(wsa_version, &wsa_data); |
| if (wsa_err != 0) { |
| fprintf(stderr, "WSAStartup failed: %d\n", wsa_err); |
| return 1; |
| } |
| if (wsa_data.wVersion != wsa_version) { |
| fprintf(stderr, "Didn't get expected version: %x\n", wsa_data.wVersion); |
| return 1; |
| } |
| #endif |
| |
| if (!test_socket_connect()) { |
| return 1; |
| } |
| |
| if (!test_printf()) { |
| return 1; |
| } |
| |
| if (!test_zero_copy_bio_pairs()) { |
| return 1; |
| } |
| |
| printf("PASS\n"); |
| return 0; |
| } |