xref: /5.5.2/kv_engine/tests/testapp/testapp.cc (revision 9d912744)
1/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
3#include "testapp.h"
4
5#include "ssl_impl.h"
6
7#include <JSON_checker.h>
8#include <gtest/gtest.h>
9#include <platform/backtrace.h>
10#include <platform/cb_malloc.h>
11#include <platform/dirutils.h>
12#include <platform/socket.h>
13#include <platform/strerror.h>
14#include <snappy-c.h>
15#include <gsl/gsl>
16
17#include <getopt.h>
18#include <platform/compress.h>
19#include <fstream>
20
21McdEnvironment* mcd_env = nullptr;
22
23using Testapp::MAX_CONNECTIONS;
24using Testapp::BACKLOG;
25
26/* test phases (bitmasks) */
27#define phase_plain 0x2
28#define phase_ssl 0x4
29
30#define phase_max 4
31static int current_phase = 0;
32
33pid_t server_pid = pid_t(-1);
34in_port_t port = -1;
35in_port_t ssl_port = -1;
36SOCKET sock = INVALID_SOCKET;
37SOCKET sock_ssl;
38static std::atomic<bool> allow_closed_read;
39static time_t server_start_time = 0;
40
41// used in embedded mode to shutdown memcached
42extern void shutdown_server();
43
44std::set<cb::mcbp::Feature> enabled_hello_features;
45
46int memcached_verbose = 0;
47// State variable if we're running the memcached server in a
48// thread in the same process or not
49static bool embedded_memcached_server;
50
51/* static storage for the different environment variables set by
52 * putenv().
53 *
54 * (These must be static as putenv() essentially 'takes ownership' of
55 * the provided array, so it is unsafe to use an automatic variable.
56 * However, if we use the result of cb_malloc() (i.e. the heap) then
57 * memory leak checkers (e.g. Valgrind) will report the memory as
58 * leaked as it's impossible to free it).
59 */
60static char mcd_parent_monitor_env[80];
61static char mcd_port_filename_env[80];
62
63static SOCKET connect_to_server_ssl(in_port_t ssl_port);
64
65void set_allow_closed_read(bool enabled) {
66    allow_closed_read = enabled;
67}
68
69bool sock_is_ssl() {
70    return current_phase == phase_ssl;
71}
72
73time_t get_server_start_time() {
74    return server_start_time;
75}
76
77std::ostream& operator<<(std::ostream& os, const TransportProtocols& t) {
78    os << to_string(t);
79    return os;
80}
81
82std::string to_string(const TransportProtocols& transport) {
83#ifdef JETBRAINS_CLION_IDE
84    // CLion don't properly parse the output when the
85    // output gets written as the string instead of the
86    // number. This makes it harder to debug the tests
87    // so let's just disable it while we're waiting
88    // for them to supply a fix.
89    // See https://youtrack.jetbrains.com/issue/CPP-6039
90    return std::to_string(static_cast<int>(transport));
91#else
92    switch (transport) {
93    case TransportProtocols::McbpPlain:
94        return "Mcbp";
95    case TransportProtocols::McbpIpv6Plain:
96        return "McbpIpv6";
97    case TransportProtocols::McbpSsl:
98        return "McbpSsl";
99    case TransportProtocols::McbpIpv6Ssl:
100        return "McbpIpv6Ssl";
101    }
102    throw std::logic_error("Unknown transport");
103#endif
104}
105
106std::string to_string(ClientJSONSupport json) {
107    switch (json) {
108    case ClientJSONSupport::Yes:
109        return "JsonYes";
110    case ClientJSONSupport::No:
111        return "JsonNo";
112    }
113    throw std::logic_error("Unknown JSON support");
114}
115
116std::string to_string(ClientSnappySupport snappy) {
117    switch (snappy) {
118    case ClientSnappySupport::Yes:
119        return "SnappyYes";
120    case ClientSnappySupport::No:
121        return "SnappyNo";
122    }
123    throw std::logic_error("Unknown ClientSnappySupport value: " +
124                           std::to_string(int(snappy)));
125}
126
127void TestappTest::CreateTestBucket()
128{
129    auto& conn = connectionMap.getConnection(false);
130
131    // Reconnect to the server so we know we're on a "fresh" connection
132    // to the server (and not one that might have been cached by the
133    // idle-timer, but not yet noticed on the client side)
134    conn.reconnect();
135    conn.authenticate("@admin", "password", "PLAIN");
136
137    mcd_env->getTestBucket().setUpBucket(bucketName, "", conn);
138
139    // Reconnect the object to avoid others to reuse the admin creds
140    conn.reconnect();
141}
142
143void TestappTest::DeleteTestBucket() {
144    current_phase = phase_plain;
145    sock = connect_to_server_plain(port);
146    ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, sasl_auth("@admin",
147                                                          "password"));
148    union {
149        protocol_binary_request_delete_bucket request;
150        protocol_binary_response_no_extras response;
151        char bytes[1024];
152    } buffer;
153
154    size_t plen = mcbp_raw_command(buffer.bytes,
155                                   sizeof(buffer.bytes),
156                                   PROTOCOL_BINARY_CMD_DELETE_BUCKET,
157                                   bucketName.c_str(),
158                                   bucketName.size(),
159                                   NULL,
160                                   0);
161
162    safe_send(buffer.bytes, plen, false);
163    safe_recv_packet(&buffer, sizeof(buffer));
164
165    mcbp_validate_response_header(&buffer.response,
166                                  PROTOCOL_BINARY_CMD_DELETE_BUCKET,
167                                  PROTOCOL_BINARY_RESPONSE_SUCCESS);
168}
169
170TestBucketImpl& TestappTest::GetTestBucket()
171{
172    return mcd_env->getTestBucket();
173}
174
175// Per-test-case set-up.
176// Called before the first test in this test case.
177void TestappTest::SetUpTestCase() {
178    token = 0xdeadbeef;
179    memcached_cfg = generate_config(0);
180    start_memcached_server(memcached_cfg.get());
181
182    if (HasFailure()) {
183        std::cerr << "Error in TestappTest::SetUpTestCase, terminating process"
184                  << std::endl;
185
186        exit(EXIT_FAILURE);
187    } else {
188        CreateTestBucket();
189    }
190}
191
192// Per-test-case tear-down.
193// Called after the last test in this test case.
194void TestappTest::TearDownTestCase() {
195    if (sock != INVALID_SOCKET) {
196        cb::net::closesocket(sock);
197    }
198
199    if (server_pid != reinterpret_cast<pid_t>(-1)) {
200        DeleteTestBucket();
201    }
202    stop_memcached_server();
203}
204
205void TestappTest::reconfigure_client_cert_auth(const std::string& state,
206                                               const std::string& path,
207                                               const std::string& prefix,
208                                               const std::string& delimiter) {
209    // Delete the old item from the array
210    cJSON_DeleteItemFromObject(memcached_cfg.get(), "client_cert_auth");
211
212    auto obj = cJSON_CreateObject();
213    cJSON_AddStringToObject(obj, "state", state.c_str());
214    cJSON_AddStringToObject(obj, "path", path.c_str());
215    cJSON_AddStringToObject(obj, "prefix", prefix.c_str());
216    cJSON_AddStringToObject(obj, "delimiter", delimiter.c_str());
217    cJSON_AddItemToObject(memcached_cfg.get(), "client_cert_auth", obj);
218
219    // update the server to use this!
220    reconfigure();
221}
222
223void TestappTest::setClientCertData(MemcachedConnection& connection) {
224    connection.setSslCertFile(SOURCE_ROOT +
225                              std::string("/tests/cert/client.pem"));
226    connection.setSslKeyFile(SOURCE_ROOT +
227                             std::string("/tests/cert/client.key"));
228}
229
230std::string get_sasl_mechs(void) {
231    union {
232        protocol_binary_request_no_extras request;
233        protocol_binary_response_no_extras response;
234        char bytes[1024];
235    } buffer;
236
237    size_t plen = mcbp_raw_command(buffer.bytes, sizeof(buffer.bytes),
238                                   PROTOCOL_BINARY_CMD_SASL_LIST_MECHS,
239                                   NULL, 0, NULL, 0);
240
241    safe_send(buffer.bytes, plen, false);
242    safe_recv_packet(&buffer, sizeof(buffer));
243    mcbp_validate_response_header(&buffer.response,
244                                  PROTOCOL_BINARY_CMD_SASL_LIST_MECHS,
245                                  PROTOCOL_BINARY_RESPONSE_SUCCESS);
246
247    std::string ret;
248    ret.assign(buffer.bytes + sizeof(buffer.response.bytes),
249               buffer.response.message.header.response.getBodylen());
250    return ret;
251}
252
253struct my_sasl_ctx {
254    const char *username;
255    cbsasl_secret_t *secret;
256};
257
258static int sasl_get_username(void *context, int id, const char **result,
259                             unsigned int *len)
260{
261    struct my_sasl_ctx *ctx = reinterpret_cast<struct my_sasl_ctx *>(context);
262    if (!context || !result || (id != CBSASL_CB_USER && id != CBSASL_CB_AUTHNAME)) {
263        return CBSASL_BADPARAM;
264    }
265
266    *result = ctx->username;
267    if (len) {
268        *len = (unsigned int)strlen(*result);
269    }
270
271    return CBSASL_OK;
272}
273
274static int sasl_get_password(cbsasl_conn_t *conn, void *context, int id,
275                             cbsasl_secret_t **psecret)
276{
277    struct my_sasl_ctx *ctx = reinterpret_cast<struct my_sasl_ctx *>(context);
278    if (!conn || ! psecret || id != CBSASL_CB_PASS || ctx == NULL) {
279        return CBSASL_BADPARAM;
280    }
281
282    *psecret = ctx->secret;
283    return CBSASL_OK;
284}
285
286uint16_t TestappTest::sasl_auth(const char *username, const char *password) {
287    cbsasl_error_t err;
288    const char *data;
289    unsigned int len;
290    const char *chosenmech;
291
292    struct my_sasl_ctx context;
293    cbsasl_callback_t sasl_callbacks[4];
294    cbsasl_conn_t *client;
295    std::string mech(get_sasl_mechs());
296
297    sasl_callbacks[0].id = CBSASL_CB_USER;
298    sasl_callbacks[0].proc = (int( *)(void)) &sasl_get_username;
299    sasl_callbacks[0].context = &context;
300    sasl_callbacks[1].id = CBSASL_CB_AUTHNAME;
301    sasl_callbacks[1].proc = (int( *)(void)) &sasl_get_username;
302    sasl_callbacks[1].context = &context;
303    sasl_callbacks[2].id = CBSASL_CB_PASS;
304    sasl_callbacks[2].proc = (int( *)(void)) &sasl_get_password;
305    sasl_callbacks[2].context = &context;
306    sasl_callbacks[3].id = CBSASL_CB_LIST_END;
307    sasl_callbacks[3].proc = NULL;
308    sasl_callbacks[3].context = NULL;
309
310    context.username = username;
311    context.secret = reinterpret_cast<cbsasl_secret_t*>(cb_calloc(1, 100));
312    memcpy(context.secret->data, password, strlen(password));
313    context.secret->len = (unsigned long)strlen(password);
314
315    err = cbsasl_client_new(NULL, NULL, NULL, NULL, sasl_callbacks, 0, &client);
316    EXPECT_EQ(CBSASL_OK, err);
317    err = cbsasl_client_start(client, mech.c_str(), NULL, &data, &len, &chosenmech);
318    EXPECT_EQ(CBSASL_OK, err);
319    if (::testing::Test::HasFailure()) {
320        // Can't continue if we didn't suceed in starting SASL auth.
321        cb_free(context.secret);
322        cbsasl_dispose(&client);
323        return PROTOCOL_BINARY_RESPONSE_EINTERNAL;
324    }
325
326    union {
327        protocol_binary_request_no_extras request;
328        protocol_binary_response_no_extras response;
329        char bytes[1024];
330    } buffer;
331
332    size_t plen = mcbp_raw_command(buffer.bytes, sizeof(buffer.bytes),
333                                   PROTOCOL_BINARY_CMD_SASL_AUTH,
334                                   chosenmech, strlen(chosenmech),
335                                   data, len);
336
337    safe_send(buffer.bytes, plen, false);
338    safe_recv_packet(&buffer, sizeof(buffer));
339
340    bool stepped = false;
341
342    while (buffer.response.message.header.response.status == PROTOCOL_BINARY_RESPONSE_AUTH_CONTINUE) {
343        stepped = true;
344        int datalen = buffer.response.message.header.response.getBodylen() -
345                      buffer.response.message.header.response.getKeylen() -
346                      buffer.response.message.header.response.extlen;
347
348        int dataoffset = sizeof(buffer.response.bytes) +
349                         buffer.response.message.header.response.getKeylen() +
350                         buffer.response.message.header.response.extlen;
351
352        err = cbsasl_client_step(client, buffer.bytes + dataoffset, datalen,
353                                 NULL, &data, &len);
354        EXPECT_EQ(CBSASL_CONTINUE, err);
355
356        plen = mcbp_raw_command(buffer.bytes, sizeof(buffer.bytes),
357                                PROTOCOL_BINARY_CMD_SASL_STEP,
358                                chosenmech, strlen(chosenmech), data, len);
359
360        safe_send(buffer.bytes, plen, false);
361
362        safe_recv_packet(&buffer, sizeof(buffer));
363    }
364
365    if (stepped) {
366        mcbp_validate_response_header(&buffer.response,
367                                      PROTOCOL_BINARY_CMD_SASL_STEP,
368                                      buffer.response.message.header.response.status);
369    } else {
370        mcbp_validate_response_header(&buffer.response,
371                                      PROTOCOL_BINARY_CMD_SASL_AUTH,
372                                      buffer.response.message.header.response.status);
373    }
374    cb_free(context.secret);
375    cbsasl_dispose(&client);
376
377    return buffer.response.message.header.response.status;
378}
379
380bool TestappTest::isJSON(cb::const_char_buffer value) {
381    JSON_checker::Validator validator;
382    const auto* ptr = reinterpret_cast<const uint8_t*>(value.data());
383    return validator.validate(ptr, value.size());
384}
385
386// per test setup function.
387void TestappTest::SetUp() {
388    verify_server_running();
389    current_phase = phase_plain;
390    sock = connect_to_server_plain(port);
391    ASSERT_NE(INVALID_SOCKET, sock);
392
393    // Set ewouldblock_engine test harness to default mode.
394    ewouldblock_engine_configure(ENGINE_EWOULDBLOCK, EWBEngineMode::First,
395                                 /*unused*/0);
396
397    enabled_hello_features.clear();
398
399    const auto* info = ::testing::UnitTest::GetInstance()->current_test_info();
400    name.assign(info->test_case_name());
401    name.append("_");
402    name.append(info->name());
403    std::replace(name.begin(), name.end(), '/', '_');
404}
405
406// per test tear-down function.
407void TestappTest::TearDown() {
408    cb::net::closesocket(sock);
409}
410
411const std::string TestappTest::bucketName = "default";
412
413// per test setup function.
414void McdTestappTest::SetUp() {
415    verify_server_running();
416    if (getProtocolParam() == TransportProtocols::McbpPlain) {
417        current_phase = phase_plain;
418        sock = connect_to_server_plain(port);
419        ASSERT_NE(INVALID_SOCKET, sock);
420    } else {
421        current_phase = phase_ssl;
422        sock_ssl = connect_to_server_ssl(ssl_port);
423        ASSERT_NE(INVALID_SOCKET, sock_ssl);
424    }
425
426    set_json_feature(hasJSONSupport() == ClientJSONSupport::Yes);
427
428    // Set ewouldblock_engine test harness to default mode.
429    ewouldblock_engine_configure(ENGINE_EWOULDBLOCK, EWBEngineMode::First,
430                                 /*unused*/0);
431
432    setCompressionMode("off");
433}
434
435// per test tear-down function.
436void McdTestappTest::TearDown() {
437    if (getProtocolParam() == TransportProtocols::McbpPlain) {
438        cb::net::closesocket(sock);
439    } else {
440        cb::net::closesocket(sock_ssl);
441        sock_ssl = INVALID_SOCKET;
442        destroy_ssl_socket();
443    }
444}
445
446ClientJSONSupport McdTestappTest::hasJSONSupport() const {
447    return getJSONParam();
448}
449
450void TestappTest::setCompressionMode(const std::string& compression_mode) {
451    mcd_env->getTestBucket().setCompressionMode(
452            getConnection(), bucketName, compression_mode);
453}
454
455void TestappTest::setMinCompressionRatio(float min_compression_ratio) {
456    mcd_env->getTestBucket().setMinCompressionRatio(
457            getConnection(), bucketName, std::to_string(min_compression_ratio));
458}
459
460std::string McdTestappTest::PrintToStringCombinedName(
461        const ::testing::TestParamInfo<
462                ::testing::tuple<TransportProtocols, ClientJSONSupport>>&
463                info) {
464    return to_string(::testing::get<0>(info.param)) + "_" +
465           to_string(::testing::get<1>(info.param));
466}
467
468std::string CERTIFICATE_PATH(const std::string& file) {
469#ifdef WIN32
470    return std::string("\\tests\\cert\\") + file;
471#else
472    return std::string("/tests/cert/") + file;
473#endif
474}
475static std::string get_errmaps_dir() {
476    std::string dir(SOURCE_ROOT);
477    dir += "/etc/couchbase/kv/error_maps";
478    cb::io::sanitizePath(dir);
479    return dir;
480}
481
482unique_cJSON_ptr TestappTest::generate_config(uint16_t ssl_port) {
483    unique_cJSON_ptr object(cJSON_CreateObject());
484    cJSON* root = object.get();
485    cJSON *array = cJSON_CreateArray();
486    cJSON *obj = nullptr;
487    cJSON *obj_ssl = nullptr;
488
489    const std::string cwd = cb::io::getcwd();
490    const std::string pem_path = cwd + CERTIFICATE_PATH("testapp.pem");
491    const std::string cert_path = cwd + CERTIFICATE_PATH("testapp.cert");
492
493    unique_cJSON_ptr logger(cJSON_CreateObject());
494    cJSON_AddTrueToObject(logger.get(), "unit_test");
495    if (memcached_verbose == 0) {
496        cJSON_AddFalseToObject(logger.get(), "console");
497    }
498    cJSON_AddItemToObject(root, "logger", logger.release());
499    cJSON_AddNumberToObject(root, "verbosity", memcached_verbose);
500    cJSON_AddFalseToObject(root, "stdin_listener");
501
502    // Build up the interface array
503    // One interface using the memcached binary protocol
504    obj = cJSON_CreateObject();
505    cJSON_AddNumberToObject(obj, "port", 0);
506    cJSON_AddTrueToObject(obj, "ipv4");
507    cJSON_AddTrueToObject(obj, "ipv6");
508    cJSON_AddInteger64ToObject(obj, "maxconn", MAX_CONNECTIONS);
509    cJSON_AddInteger64ToObject(obj, "backlog", BACKLOG);
510    cJSON_AddStringToObject(obj, "host", "*");
511    cJSON_AddStringToObject(obj, "protocol", "memcached");
512    cJSON_AddTrueToObject(obj, "management");
513    cJSON_AddItemToArray(array, obj);
514
515    // One interface using the memcached binary protocol over SSL
516    obj = cJSON_CreateObject();
517    cJSON_AddNumberToObject(obj, "port", ssl_port);
518    cJSON_AddInteger64ToObject(obj, "maxconn", MAX_CONNECTIONS);
519    cJSON_AddInteger64ToObject(obj, "backlog", BACKLOG);
520    cJSON_AddTrueToObject(obj, "ipv4");
521    cJSON_AddTrueToObject(obj, "ipv6");
522    cJSON_AddStringToObject(obj, "host", "*");
523    cJSON_AddStringToObject(obj, "protocol", "memcached");
524    obj_ssl = cJSON_CreateObject();
525    cJSON_AddStringToObject(obj_ssl, "key", pem_path.c_str());
526    cJSON_AddStringToObject(obj_ssl, "cert", cert_path.c_str());
527    cJSON_AddItemToObject(obj, "ssl", obj_ssl);
528    cJSON_AddItemToArray(array, obj);
529
530    cJSON_AddItemToObject(root, "interfaces", array);
531
532    cJSON_AddTrueToObject(root, "datatype_json");
533    cJSON_AddTrueToObject(root, "datatype_snappy");
534    cJSON_AddStringToObject(root, "audit_file",
535                            mcd_env->getAuditFilename().c_str());
536    cJSON_AddStringToObject(root, "error_maps_dir", get_errmaps_dir().c_str());
537    cJSON_AddTrueToObject(root, "xattr_enabled");
538    cJSON_AddStringToObject(root, "rbac_file",
539                            mcd_env->getRbacFilename().c_str());
540
541    // Add an opcode_attributes_override element so that we know we can
542    // parse it if ns_server starts applying one ;-)
543    unique_cJSON_ptr kvattr(cJSON_Parse(
544            R"({"version":1,"EWB_CTL": {"slow":50}})"));
545    cJSON_AddItemToObject(root, "opcode_attributes_override", kvattr.release());
546
547    cJSON_AddFalseToObject(root, "dedupe_nmvb_maps");
548
549    return object;
550}
551
552unique_cJSON_ptr TestappTest::generate_config() {
553    return generate_config(ssl_port);
554}
555
556void write_config_to_file(const std::string& config, const std::string& fname) {
557    FILE *fp = fopen(fname.c_str(), "w");
558
559    if (fp == nullptr) {
560        throw std::system_error(errno,
561                                std::system_category(),
562                                "Failed to open file \"" + fname + "\"");
563    } else {
564        fprintf(fp, "%s", config.c_str());
565        fclose(fp);
566    }
567}
568
569/**
570 * Load and parse the content of a file into a cJSON array
571 *
572 * @param file the name of the file
573 * @return the decoded cJSON representation
574 * @throw a string if something goes wrong
575 */
576unique_cJSON_ptr loadJsonFile(const std::string &file) {
577    unique_cJSON_ptr ret(cJSON_Parse(cb::io::loadFile(file).c_str()));
578    if (ret.get() == nullptr) {
579        throw std::logic_error("Failed to parse: " + file);
580    }
581
582    return ret;
583}
584
585void TestappTest::verify_server_running() {
586    if (embedded_memcached_server) {
587        // we don't monitor this thread...
588        return ;
589    }
590
591    if (reinterpret_cast<pid_t>(-1) == server_pid) {
592        std::cerr << "Server not running (server_pid == -1)" << std::endl;
593        exit(EXIT_FAILURE);
594    }
595
596#ifdef WIN32
597    DWORD status;
598    if (!GetExitCodeProcess(server_pid, &status)) {
599        std::cerr << "GetExitCodeProcess: failed: " << cb_strerror()
600                  << std::endl;
601        exit(EXIT_FAILURE);
602    }
603    if (status != STILL_ACTIVE) {
604        std::cerr << "memcached process is not active: Exit code " << status
605                  << std::endl;
606        exit(EXIT_FAILURE);
607    }
608#else
609    int status;
610    pid_t ret = waitpid(server_pid, &status, WNOHANG);
611
612    if (ret == static_cast<pid_t>(-1)) {
613        std::cerr << "waitpid() failed with: " << strerror(errno) << std::endl;
614        exit(EXIT_FAILURE);
615    }
616
617    if (server_pid == ret) {
618        std::cerr << "waitpid status     : " << status << std::endl
619                  << "WIFEXITED(status)  : " << WIFEXITED(status) << std::endl
620                  << "WEXITSTATUS(status): " << WEXITSTATUS(status) << std::endl
621                  << "WIFSIGNALED(status): " << WIFSIGNALED(status) << std::endl
622                  << "WTERMSIG(status)   : " << WTERMSIG(status) << std::endl
623                  << "WCOREDUMP(status)  : " << WCOREDUMP(status) << std::endl;
624        exit(EXIT_FAILURE);
625    }
626#endif
627}
628
629void TestappTest::parse_portnumber_file(in_port_t& port_out,
630                                        in_port_t& ssl_port_out) {
631    FILE* fp;
632    // I've seen that running under valgrind startup of the processes
633    // might be slow, and getting even worse if the machine is under
634    // load. Instead of having a "false timeout" just because the
635    // server is slow, lets set the deadline to a high value so that
636    // if we hit it we have a real problem and not just a loaded
637    // server (rebuilding all of the source one more time is just
638    // putting more load on the servers).
639    using std::chrono::minutes;
640    using std::chrono::seconds;
641    const auto timeout = seconds(minutes(5)).count();
642    const time_t deadline = time(NULL) + timeout;
643    // Wait up to timeout seconds for the port file to be created.
644    do {
645        std::this_thread::sleep_for(std::chrono::milliseconds(10));
646        fp = fopen(portnumber_file.c_str(), "r");
647        if (fp != nullptr) {
648            break;
649        }
650
651        verify_server_running();
652    } while (time(NULL) < deadline);
653
654    ASSERT_NE(nullptr, fp) << "Timed out after " << timeout
655                           << "s waiting for memcached port file '"
656                           << portnumber_file << "' to be created.";
657    fclose(fp);
658
659    port_out = (in_port_t)-1;
660    ssl_port_out = (in_port_t)-1;
661
662    unique_cJSON_ptr portnumbers;
663    portnumbers = loadJsonFile(portnumber_file);
664    ASSERT_NE(nullptr, portnumbers);
665    connectionMap.initialize(portnumbers.get());
666
667    cJSON* array = cJSON_GetObjectItem(portnumbers.get(), "ports");
668    ASSERT_NE(nullptr, array) << "ports not found in portnumber file";
669
670    auto numEntries = cJSON_GetArraySize(array);
671    for (int ii = 0; ii < numEntries; ++ii) {
672        auto obj = cJSON_GetArrayItem(array, ii);
673
674        auto protocol = cJSON_GetObjectItem(obj, "protocol");
675        if (strcmp(protocol->valuestring, "memcached") != 0) {
676            // the new test use the connectionmap
677            continue;
678        }
679
680        auto family = cJSON_GetObjectItem(obj, "family");
681        if (strcmp(family->valuestring, "AF_INET") != 0) {
682            // For now we don't test IPv6
683            continue;
684        }
685        auto ssl = cJSON_GetObjectItem(obj, "ssl");
686        ASSERT_NE(nullptr, ssl);
687        auto port = cJSON_GetObjectItem(obj, "port");
688        ASSERT_NE(nullptr, port);
689
690        in_port_t* out_port;
691        if (ssl->type == cJSON_True) {
692            out_port = &ssl_port_out;
693        } else {
694            out_port = &port_out;
695        }
696        *out_port = static_cast<in_port_t>(port->valueint);
697    }
698
699    EXPECT_EQ(0, remove(portnumber_file.c_str()));
700}
701
702extern "C" int memcached_main(int argc, char** argv);
703
704extern "C" void memcached_server_thread_main(void *arg) {
705    char *argv[4];
706    int argc = 0;
707    argv[argc++] = const_cast<char*>("./memcached");
708    argv[argc++] = const_cast<char*>("-C");
709    argv[argc++] = reinterpret_cast<char*>(arg);
710
711    memcached_main(argc, argv);
712
713
714}
715
716void TestappTest::spawn_embedded_server() {
717    char *filename= mcd_port_filename_env + strlen("MEMCACHED_PORT_FILENAME=");
718    snprintf(mcd_port_filename_env,
719             sizeof(mcd_port_filename_env),
720             "MEMCACHED_PORT_FILENAME=memcached_ports.%lu.%lu",
721             (long)cb_getpid(),
722             (unsigned long)time(NULL));
723    remove(filename);
724    portnumber_file.assign(filename);
725    putenv(mcd_port_filename_env);
726
727    ASSERT_EQ(0, cb_create_thread(&memcached_server_thread,
728                                  memcached_server_thread_main,
729                                  const_cast<char*>(config_file.c_str()),
730                                  0));
731}
732
733
734void TestappTest::start_external_server() {
735    char *filename= mcd_port_filename_env + strlen("MEMCACHED_PORT_FILENAME=");
736    snprintf(mcd_parent_monitor_env,
737             sizeof(mcd_parent_monitor_env),
738             "MEMCACHED_PARENT_MONITOR=%lu",
739             (unsigned long)cb_getpid());
740    putenv(mcd_parent_monitor_env);
741
742    snprintf(mcd_port_filename_env,
743             sizeof(mcd_port_filename_env),
744             "MEMCACHED_PORT_FILENAME=memcached_ports.%lu.%lu",
745             (long)cb_getpid(),
746             (unsigned long)time(NULL));
747    remove(filename);
748    portnumber_file.assign(filename);
749    static char topkeys_env[] = "MEMCACHED_TOP_KEYS=10";
750    putenv(topkeys_env);
751
752#ifdef WIN32
753    STARTUPINFO sinfo;
754    PROCESS_INFORMATION pinfo;
755    memset(&sinfo, 0, sizeof(sinfo));
756    memset(&pinfo, 0, sizeof(pinfo));
757    sinfo.cb = sizeof(sinfo);
758
759    char commandline[1024];
760    sprintf(commandline, "memcached.exe -C %s", config_file.c_str());
761
762    putenv(mcd_port_filename_env);
763
764    if (!CreateProcess("memcached.exe", commandline,
765                       NULL, NULL, /*bInheritHandles*/FALSE,
766                       0,
767                       NULL, NULL, &sinfo, &pinfo)) {
768
769        std::cerr << "Failed to start process: " << cb_strerror() << std::endl;
770        exit(EXIT_FAILURE);
771    }
772
773    server_pid = pinfo.hProcess;
774#else
775    server_pid = fork();
776    ASSERT_NE(reinterpret_cast<pid_t>(-1), server_pid);
777
778    if (server_pid == 0) {
779        /* Child */
780        const char *argv[20];
781        int arg = 0;
782        putenv(mcd_port_filename_env);
783
784        if (getenv("RUN_UNDER_VALGRIND") != nullptr) {
785            argv[arg++] = "valgrind";
786            argv[arg++] = "--log-file=valgrind.%p.log";
787            argv[arg++] = "--leak-check=full";
788    #if defined(__APPLE__)
789            /* Needed to ensure debugging symbols are up-to-date. */
790            argv[arg++] = "--dsymutil=yes";
791    #endif
792        }
793
794        if (getenv("RUN_UNDER_PERF") != nullptr) {
795            argv[arg++] = "perf";
796            argv[arg++] = "record";
797            argv[arg++] = "--call-graph";
798            argv[arg++] = "dwarf";
799        }
800
801        argv[arg++] = "./memcached";
802        argv[arg++] = "-C";
803        argv[arg++] = config_file.c_str();
804
805        argv[arg++] = nullptr;
806        cb_assert(execvp(argv[0], const_cast<char **>(argv)) != -1);
807    }
808#endif // !WIN32
809}
810
811SOCKET create_connect_plain_socket(in_port_t port)
812{
813    auto sock = cb::net::new_socket("", port, AF_INET);
814    if (sock == INVALID_SOCKET) {
815        ADD_FAILURE() << "Failed to connect socket to 127.0.0.1:" << port;
816    }
817    return sock;
818}
819
820SOCKET connect_to_server_plain(in_port_t port) {
821    return create_connect_plain_socket(port);
822}
823
824static SOCKET connect_to_server_ssl(in_port_t ssl_port) {
825    SOCKET sock = create_connect_ssl_socket(ssl_port);
826    if (sock == INVALID_SOCKET) {
827        ADD_FAILURE() << "Failed to connect SSL socket to port" << ssl_port;
828        return INVALID_SOCKET;
829    }
830
831    return sock;
832}
833
834/*
835    re-connect to server.
836    Uses global port and ssl_port values.
837    New socket-fd written to global "sock" and "ssl_bio"
838*/
839void reconnect_to_server() {
840    if (current_phase == phase_ssl) {
841        cb::net::closesocket(sock_ssl);
842        destroy_ssl_socket();
843
844        sock_ssl = connect_to_server_ssl(ssl_port);
845        ASSERT_NE(INVALID_SOCKET, sock_ssl);
846    } else {
847        cb::net::closesocket(sock);
848        sock = connect_to_server_plain(port);
849        ASSERT_NE(INVALID_SOCKET, sock);
850    }
851}
852
853static void set_feature(cb::mcbp::Feature feature, bool enable) {
854    // First update the currently enabled features.
855    if (enable) {
856        enabled_hello_features.insert(feature);
857    } else {
858        enabled_hello_features.erase(feature);
859    }
860
861    // Now send the new HELLO message to the server.
862    union {
863        protocol_binary_request_hello request;
864        protocol_binary_response_hello response;
865        char bytes[1024];
866    } buffer;
867    const char *useragent = "testapp";
868    const size_t agentlen = strlen(useragent);
869
870    // Calculate body location and populate the body.
871    char* const body_ptr = buffer.bytes + sizeof(buffer.request.message.header);
872    memcpy(body_ptr, useragent, agentlen);
873
874    size_t bodylen = agentlen;
875    for (auto feature : enabled_hello_features) {
876        const uint16_t wire_feature = htons(uint16_t(feature));
877        memcpy(body_ptr + bodylen,
878               reinterpret_cast<const char*>(&wire_feature), sizeof(wire_feature));
879        bodylen += sizeof(wire_feature);
880    }
881
882    // Fill in the header at the start of the buffer.
883    memset(buffer.bytes, 0, sizeof(buffer.request.message.header));
884    buffer.request.message.header.request.magic = PROTOCOL_BINARY_REQ;
885    buffer.request.message.header.request.opcode = PROTOCOL_BINARY_CMD_HELLO;
886    buffer.request.message.header.request.keylen = htons((uint16_t)agentlen);
887    buffer.request.message.header.request.bodylen =
888            htonl(gsl::narrow<uint32_t>(bodylen));
889
890    safe_send(buffer.bytes,
891              sizeof(buffer.request.message.header) + bodylen, false);
892
893    safe_recv(&buffer.response, sizeof(buffer.response));
894    const size_t response_bodylen =
895            buffer.response.message.header.response.getBodylen();
896    EXPECT_EQ(bodylen - agentlen, response_bodylen);
897    for (auto feature : enabled_hello_features) {
898        uint16_t wire_feature;
899        safe_recv(&wire_feature, sizeof(wire_feature));
900        wire_feature = ntohs(wire_feature);
901        EXPECT_EQ(uint16_t(feature), wire_feature);
902    }
903}
904
905void set_datatype_feature(bool enable) {
906    set_feature(cb::mcbp::Feature::JSON, enable);
907    set_feature(cb::mcbp::Feature::SNAPPY, enable);
908}
909
910std::pair<protocol_binary_response_status, std::string>
911fetch_value(const std::string& key) {
912    union {
913        protocol_binary_request_no_extras request;
914        protocol_binary_response_no_extras response;
915        char bytes[1024];
916    } send, receive;
917    const size_t len = mcbp_raw_command(send.bytes, sizeof(send.bytes),
918                                        PROTOCOL_BINARY_CMD_GET,
919                                        key.data(), key.size(), NULL, 0);
920    safe_send(send.bytes, len, false);
921    EXPECT_TRUE(safe_recv_packet(receive.bytes, sizeof(receive.bytes)));
922
923    protocol_binary_response_status status =
924            protocol_binary_response_status(receive.response.message.header.response.status);
925    if (status == PROTOCOL_BINARY_RESPONSE_SUCCESS) {
926        const char* ptr = receive.bytes + sizeof(receive.response) + 4;
927        const size_t vallen =
928                receive.response.message.header.response.getBodylen() - 4;
929        return std::make_pair(PROTOCOL_BINARY_RESPONSE_SUCCESS,
930                              std::string(ptr, vallen));
931    } else {
932        return std::make_pair(status, "");
933    }
934}
935
936void validate_object(const char *key, const std::string& expected_value) {
937    union {
938        protocol_binary_request_no_extras request;
939        char bytes[1024];
940    } send;
941    size_t len = mcbp_raw_command(send.bytes, sizeof(send.bytes),
942                                  PROTOCOL_BINARY_CMD_GET,
943                                  key, strlen(key), NULL, 0);
944    safe_send(send.bytes, len, false);
945
946    std::vector<char> receive;
947    safe_recv_packet(receive);
948
949    auto* response = reinterpret_cast<protocol_binary_response_no_extras*>(receive.data());
950    mcbp_validate_response_header(response, PROTOCOL_BINARY_CMD_GET,
951                                  PROTOCOL_BINARY_RESPONSE_SUCCESS);
952    char* ptr = receive.data() + sizeof(*response) + 4;
953    if (response->message.header.response.getStatus() ==
954        PROTOCOL_BINARY_RESPONSE_SUCCESS) {
955        size_t vallen = response->message.header.response.getBodylen() - 4;
956        std::string actual(ptr, vallen);
957        EXPECT_EQ(expected_value, actual);
958    }
959}
960
961void validate_flags(const char *key, uint32_t expected_flags) {
962    union {
963        protocol_binary_request_no_extras request;
964        char bytes[1024];
965    } send;
966    size_t len = mcbp_raw_command(send.bytes, sizeof(send.bytes),
967                                  PROTOCOL_BINARY_CMD_GET,
968                                  key, strlen(key), NULL, 0);
969    safe_send(send.bytes, len, false);
970
971    std::vector<char> receive(4096);
972    safe_recv_packet(receive.data(), receive.size());
973
974    auto* response = reinterpret_cast<protocol_binary_response_no_extras*>(receive.data());
975    mcbp_validate_response_header(response, PROTOCOL_BINARY_CMD_GET,
976                                  PROTOCOL_BINARY_RESPONSE_SUCCESS);
977    const auto* get_response =
978            reinterpret_cast<protocol_binary_response_get*>(receive.data());
979    const uint32_t actual_flags = ntohl(get_response->message.body.flags);
980    EXPECT_EQ(expected_flags, actual_flags);
981}
982
983void delete_object(const char* key, bool ignore_missing) {
984    union {
985        protocol_binary_request_no_extras request;
986        protocol_binary_response_no_extras response;
987        char bytes[1024];
988    } send, receive;
989    size_t len = mcbp_raw_command(send.bytes, sizeof(send.bytes),
990                                  PROTOCOL_BINARY_CMD_DELETE, key, strlen(key),
991                                  NULL, 0);
992    safe_send(send.bytes, len, false);
993    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
994    if (ignore_missing && receive.response.message.header.response.status ==
995            PROTOCOL_BINARY_RESPONSE_KEY_ENOENT) {
996        /* Ignore. Just using this for cleanup then */
997        return;
998    }
999    mcbp_validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_DELETE,
1000                                  PROTOCOL_BINARY_RESPONSE_SUCCESS);
1001}
1002
1003void TestappTest::start_memcached_server(cJSON* config) {
1004    config_file = cb::io::mktemp("memcached_testapp.json");
1005    write_config_to_file(to_string(config), config_file);
1006
1007    server_start_time = time(0);
1008
1009    if (embedded_memcached_server) {
1010        spawn_embedded_server();
1011    } else {
1012        start_external_server();
1013    }
1014    parse_portnumber_file(port, ssl_port);
1015}
1016
1017void store_object_w_datatype(const std::string& key,
1018                             cb::const_char_buffer value,
1019                             uint32_t flags,
1020                             uint32_t expiration,
1021                             cb::mcbp::Datatype datatype) {
1022    protocol_binary_request_set request = {};
1023
1024    request.message.header.request.magic = PROTOCOL_BINARY_REQ;
1025    request.message.header.request.opcode = PROTOCOL_BINARY_CMD_SET;
1026    request.message.header.request.datatype = uint8_t(datatype);
1027    request.message.header.request.extlen = 8;
1028    request.message.header.request.keylen = htons(uint16_t(key.size()));
1029    request.message.header.request.bodylen =
1030            htonl((uint32_t)(key.size() + value.size() + 8));
1031    request.message.header.request.opaque = 0xdeadbeef;
1032    request.message.body.expiration = htonl(expiration);
1033    request.message.body.flags = htonl(flags);
1034
1035    safe_send(&request.bytes, sizeof(request.bytes), false);
1036    safe_send(key.data(), key.size(), false);
1037    safe_send(value.data(), value.size(), false);
1038
1039    union {
1040        protocol_binary_response_no_extras response;
1041        char bytes[1024];
1042    } receive;
1043
1044    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
1045    mcbp_validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_SET,
1046                                  PROTOCOL_BINARY_RESPONSE_SUCCESS);
1047}
1048
1049void store_document(const std::string& key,
1050                    const std::string& value,
1051                    uint32_t flags,
1052                    uint32_t exptime,
1053                    bool compress) {
1054    if (compress) {
1055        bool disable_snappy = false;
1056        if (enabled_hello_features.count(cb::mcbp::Feature::SNAPPY) == 0) {
1057            // We need to enable snappy
1058            set_feature(cb::mcbp::Feature::SNAPPY, true);
1059            disable_snappy = true;
1060        }
1061        cb::compression::Buffer deflated;
1062        cb::compression::deflate(
1063                cb::compression::Algorithm::Snappy, value, deflated);
1064
1065        store_object_w_datatype(key.c_str(),
1066                                deflated,
1067                                flags,
1068                                exptime,
1069                                cb::mcbp::Datatype::Snappy);
1070        if (disable_snappy) {
1071            set_feature(cb::mcbp::Feature::SNAPPY, false);
1072        }
1073    } else {
1074        store_object_w_datatype(
1075                key.c_str(), value, flags, exptime, cb::mcbp::Datatype::Raw);
1076    }
1077}
1078
1079void set_json_feature(bool enable) {
1080    set_feature(cb::mcbp::Feature::JSON, enable);
1081}
1082
1083void set_mutation_seqno_feature(bool enable) {
1084    set_feature(cb::mcbp::Feature::MUTATION_SEQNO, enable);
1085}
1086
1087void TestappTest::waitForShutdown(bool killed) {
1088#ifdef WIN32
1089    ASSERT_EQ(WAIT_OBJECT_0, WaitForSingleObject(server_pid, 60000));
1090    DWORD exit_code = NULL;
1091    GetExitCodeProcess(server_pid, &exit_code);
1092    EXPECT_EQ(0, exit_code);
1093#else
1094    int status;
1095    pid_t ret;
1096    while (true) {
1097        ret = waitpid(server_pid, &status, 0);
1098        if (ret == reinterpret_cast<pid_t>(-1) && errno == EINTR) {
1099            // Just loop again
1100            continue;
1101        }
1102        break;
1103    }
1104    ASSERT_NE(reinterpret_cast<pid_t>(-1), ret)
1105        << "waitpid failed: " << strerror(errno);
1106    bool correctShutdown = killed ? WIFSIGNALED(status) : WIFEXITED(status);
1107    EXPECT_TRUE(correctShutdown)
1108        << "waitpid status     : " << status << std::endl
1109        << "WIFEXITED(status)  : " << WIFEXITED(status) << std::endl
1110        << "WEXITSTATUS(status): " << WEXITSTATUS(status) << std::endl
1111        << "WIFSIGNALED(status): " << WIFSIGNALED(status) << std::endl
1112        << "WTERMSIG(status)   : " << WTERMSIG(status) << " ("
1113        << strsignal(WTERMSIG(status)) << ")" << std::endl
1114        << "WCOREDUMP(status)  : " << WCOREDUMP(status) << std::endl;
1115    EXPECT_EQ(0, WEXITSTATUS(status));
1116#endif
1117    server_pid = reinterpret_cast<pid_t>(-1);
1118}
1119
1120
1121void TestappTest::stop_memcached_server() {
1122
1123    connectionMap.invalidate();
1124    if (sock != INVALID_SOCKET) {
1125        cb::net::closesocket(sock);
1126        sock = INVALID_SOCKET;
1127    }
1128
1129    if (embedded_memcached_server) {
1130        shutdown_server();
1131        cb_join_thread(memcached_server_thread);
1132    }
1133
1134    if (server_pid != reinterpret_cast<pid_t>(-1)) {
1135#ifdef WIN32
1136        TerminateProcess(server_pid, 0);
1137        waitForShutdown();
1138#else
1139        if (kill(server_pid, SIGTERM) == 0) {
1140            waitForShutdown();
1141        }
1142#endif
1143    }
1144
1145    if (!config_file.empty()) {
1146        EXPECT_NE(-1, remove(config_file.c_str()));
1147        config_file.clear();
1148    }
1149}
1150
1151
1152ssize_t socket_send(SOCKET s, const char *buf, size_t len)
1153{
1154    return cb::net::send(s, buf, len, 0);
1155}
1156
1157static ssize_t phase_send(const void *buf, size_t len) {
1158    if (current_phase == phase_ssl) {
1159        return phase_send_ssl(buf, len);
1160    } else {
1161        return socket_send(sock, reinterpret_cast<const char*>(buf), len);
1162    }
1163}
1164
1165ssize_t socket_recv(SOCKET s, char *buf, size_t len)
1166{
1167    return cb::net::recv(s, buf, len, 0);
1168}
1169
1170ssize_t phase_recv(void *buf, size_t len) {
1171    if (current_phase == phase_ssl) {
1172        return phase_recv_ssl(buf, len);
1173    } else {
1174        return socket_recv(sock, reinterpret_cast<char*>(buf), len);
1175    }
1176}
1177
1178static const bool dump_socket_traffic =
1179        getenv("TESTAPP_PACKET_DUMP") != nullptr;
1180
1181static const std::string phase_get_errno() {
1182    if (current_phase == phase_ssl) {
1183        /* could do with more work here, but so far this has sufficed */
1184        return "SSL error";
1185    }
1186    return cb_strerror();
1187}
1188
1189void safe_send(const void* buf, size_t len, bool hickup)
1190{
1191    size_t offset = 0;
1192    const char* ptr = reinterpret_cast<const char*>(buf);
1193    do {
1194        size_t num_bytes = len - offset;
1195        ssize_t nw;
1196        if (hickup) {
1197            if (num_bytes > 1024) {
1198                num_bytes = (rand() % 1023) + 1;
1199            }
1200        }
1201
1202        nw = phase_send(ptr + offset, num_bytes);
1203
1204        if (nw == -1) {
1205            if (errno != EINTR) {
1206                fprintf(stderr,
1207                        "Failed to write: %s\n",
1208                        phase_get_errno().c_str());
1209                print_backtrace_to_file(stderr);
1210                abort();
1211            }
1212        } else {
1213            if (hickup) {
1214                std::this_thread::sleep_for(std::chrono::microseconds(100));
1215            }
1216
1217            if (dump_socket_traffic) {
1218                if (current_phase == phase_ssl) {
1219                    std::cerr << "SSL";
1220                } else {
1221                    std::cerr << "PLAIN";
1222                }
1223                std::cerr << "> ";
1224                for (ssize_t ii = 0; ii < nw; ++ii) {
1225                    std::cerr << "0x" << std::hex << std::setfill('0')
1226                              << std::setw(2)
1227                              << uint32_t(*(uint8_t*)(ptr + offset + ii))
1228                              << ", ";
1229                }
1230                std::cerr << std::dec << std::endl;
1231            }
1232            offset += nw;
1233        }
1234    } while (offset < len);
1235}
1236
1237bool safe_recv(void *buf, size_t len) {
1238    size_t offset = 0;
1239    if (len == 0) {
1240        return true;
1241    }
1242    do {
1243
1244        ssize_t nr = phase_recv(((char*)buf) + offset, len - offset);
1245
1246        if (nr == -1) {
1247            EXPECT_EQ(EINTR, errno) << "Failed to read: " << phase_get_errno();
1248        } else {
1249            if (nr == 0 && allow_closed_read) {
1250                return false;
1251            }
1252            EXPECT_NE(0u, nr);
1253            offset += nr;
1254        }
1255
1256        // Give up if we encountered an error.
1257        if (::testing::Test::HasFailure()) {
1258            return false;
1259        }
1260    } while (offset < len);
1261
1262    return true;
1263}
1264
1265/**
1266 * Internal function which receives a packet. The type parameter should have
1267 * these three functions:
1268 * - resize(n) ->: ensure the buffer has a total capacity for at least n bytes.
1269 *   This function will usually be called once for the header size (i.e. 24)
1270 *   and then another time for the total packet size (i.e. 24 + bodylen).
1271 * - data() -> char*: get the entire buffer
1272 * - size() -> size_t: get the current size of the buffer
1273 *
1274 * @param info an object conforming to the above.
1275 *
1276 * Once the function has completed, it will return true if no read errors
1277 * occurred. The actual size of the packet can be determined by parsing the
1278 * packet header.
1279 *
1280 * See StaticBufInfo which is an implementation that uses a fixed buffer.
1281 * std::vector naturally conforms to the interface.
1282 */
1283template <typename T>
1284bool safe_recv_packetT(T& info) {
1285    info.resize(sizeof(protocol_binary_response_header));
1286    auto *header = reinterpret_cast<protocol_binary_response_header*>(info.data());
1287
1288    if (!safe_recv(header, sizeof(*header))) {
1289        return false;
1290    }
1291
1292    if (dump_socket_traffic) {
1293        if (current_phase == phase_ssl) {
1294            std::cerr << "SSL";
1295        } else {
1296            std::cerr << "PLAIN";
1297        }
1298        std::cerr << "< ";
1299        for (size_t ii = 0; ii < sizeof(*header); ++ii) {
1300            std::cerr << "0x" << std::hex << std::setfill('0') << std::setw(2)
1301                      << uint32_t(header->bytes[ii]) << ", ";
1302        }
1303    }
1304
1305    header->response.status = ntohs(header->response.status);
1306    auto bodylen = header->response.getBodylen();
1307
1308    // Set response to NULL, because the underlying buffer may change.
1309    header = nullptr;
1310
1311    info.resize(sizeof(*header) + bodylen);
1312    auto ret = safe_recv(info.data() + sizeof(*header), bodylen);
1313
1314    if (dump_socket_traffic) {
1315        uint8_t* ptr = (uint8_t*)(info.data() + sizeof(*header));
1316        for (size_t ii = 0; ii < bodylen; ++ii) {
1317            std::cerr << "0x" << std::hex << std::setfill('0') << std::setw(2)
1318                      << uint32_t(ptr[ii]) << ", ";
1319        }
1320        std::cerr << std::endl;
1321    }
1322    return ret;
1323}
1324
1325/**
1326 * Wrapper for a existing buffer which exposes an API suitable for use with
1327 * safe_recv_packetT()
1328 */
1329struct StaticBufInfo {
1330    StaticBufInfo(void *buf_, size_t len_)
1331        : buf(reinterpret_cast<char*>(buf_)), len(len_) {
1332    }
1333    size_t size(size_t) const { return len; }
1334    char *data() { return buf; }
1335
1336    void resize(size_t n) {
1337        if (n > len) {
1338            throw std::runtime_error("Cannot enlarge buffer!");
1339        }
1340    }
1341
1342    char *buf;
1343    const size_t len;
1344};
1345
1346bool safe_recv_packet(void *buf, size_t size) {
1347    StaticBufInfo info(buf, size);
1348    return safe_recv_packetT(info);
1349}
1350
1351bool safe_recv_packet(std::vector<uint8_t>& buf) {
1352    return safe_recv_packetT(buf);
1353}
1354
1355bool safe_recv_packet(std::vector<char>& buf) {
1356    return safe_recv_packetT(buf);
1357}
1358
1359// Configues the ewouldblock_engine to use the given mode; value
1360// is a mode-specific parameter.
1361void TestappTest::ewouldblock_engine_configure(ENGINE_ERROR_CODE err_code,
1362                                               const EWBEngineMode& mode,
1363                                               uint32_t value,
1364                                               const std::string& key) {
1365    union {
1366        request_ewouldblock_ctl request;
1367        protocol_binary_response_no_extras response;
1368        char bytes[1024];
1369    } buffer;
1370
1371    size_t len = mcbp_raw_command(buffer.bytes, sizeof(buffer.bytes),
1372                                  PROTOCOL_BINARY_CMD_EWOULDBLOCK_CTL,
1373                                  key.c_str(), key.size(), NULL, 0);
1374    buffer.request.message.body.mode = htonl(static_cast<uint32_t>(mode));
1375    buffer.request.message.body.value = htonl(value);
1376    buffer.request.message.body.inject_error = htonl(err_code);
1377
1378    safe_send(buffer.bytes, len, false);
1379
1380    safe_recv_packet(buffer.bytes, sizeof(buffer.bytes));
1381    mcbp_validate_response_header(&buffer.response,
1382                                  PROTOCOL_BINARY_CMD_EWOULDBLOCK_CTL,
1383                                  PROTOCOL_BINARY_RESPONSE_SUCCESS);
1384}
1385
1386void TestappTest::ewouldblock_engine_disable() {
1387    // Value for err_code doesn't matter...
1388    ewouldblock_engine_configure(ENGINE_EWOULDBLOCK, EWBEngineMode::Next_N, 0);
1389}
1390
1391void TestappTest::reconfigure() {
1392    write_config_to_file(to_string(memcached_cfg, true), config_file);
1393    auto& conn = getAdminConnection();
1394
1395    BinprotGenericCommand req{PROTOCOL_BINARY_CMD_CONFIG_RELOAD, {}, {}};
1396    BinprotResponse resp;
1397    conn.executeCommand(req, resp);
1398    ASSERT_TRUE(resp.isSuccess()) << "Failed to reconfigure the server";
1399    conn.reconnect();
1400}
1401
1402void TestappTest::runCreateXattr(
1403        const std::string& path,
1404        const std::string& value,
1405        bool macro,
1406        protocol_binary_response_status expectedStatus) {
1407    auto& connection = getConnection();
1408
1409    BinprotSubdocCommand cmd;
1410    cmd.setOp(PROTOCOL_BINARY_CMD_SUBDOC_DICT_ADD);
1411    cmd.setKey(name);
1412    cmd.setPath(path);
1413    cmd.setValue(value);
1414    if (macro) {
1415        cmd.addPathFlags(SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_EXPAND_MACROS |
1416                         SUBDOC_FLAG_MKDIR_P);
1417    } else {
1418        cmd.addPathFlags(SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_MKDIR_P);
1419    }
1420
1421    connection.sendCommand(cmd);
1422
1423    BinprotResponse resp;
1424    connection.recvResponse(resp);
1425    EXPECT_EQ(expectedStatus, resp.getStatus());
1426}
1427
1428void TestappTest::createXattr(const std::string& path,
1429                              const std::string& value,
1430                              bool macro) {
1431    runCreateXattr(path, value, macro, PROTOCOL_BINARY_RESPONSE_SUCCESS);
1432}
1433
1434BinprotSubdocResponse TestappTest::runGetXattr(
1435        const std::string& path,
1436        bool deleted,
1437        protocol_binary_response_status expectedStatus) {
1438    auto& connection = getConnection();
1439
1440    BinprotSubdocCommand cmd;
1441    cmd.setOp(PROTOCOL_BINARY_CMD_SUBDOC_GET);
1442    cmd.setKey(name);
1443    cmd.setPath(path);
1444    if (deleted) {
1445        cmd.addPathFlags(SUBDOC_FLAG_XATTR_PATH);
1446        cmd.addDocFlags(mcbp::subdoc::doc_flag::AccessDeleted);
1447    } else {
1448        cmd.addPathFlags(SUBDOC_FLAG_XATTR_PATH);
1449    }
1450    connection.sendCommand(cmd);
1451
1452    BinprotSubdocResponse resp;
1453    connection.recvResponse(resp);
1454    auto status = resp.getStatus();
1455    if (deleted && status == PROTOCOL_BINARY_RESPONSE_SUBDOC_SUCCESS_DELETED) {
1456        status = PROTOCOL_BINARY_RESPONSE_SUCCESS;
1457    }
1458
1459    if (status != expectedStatus) {
1460        throw ConnectionError("runGetXattr() failed: ", resp);
1461    }
1462    return resp;
1463}
1464
1465BinprotSubdocResponse TestappTest::getXattr(const std::string& path,
1466                                            bool deleted) {
1467    return runGetXattr(path, deleted, PROTOCOL_BINARY_RESPONSE_SUCCESS);
1468}
1469
1470int TestappTest::getResponseCount(protocol_binary_response_status statusCode) {
1471    unique_cJSON_ptr stats(cJSON_Parse(
1472            cJSON_GetObjectItem(
1473                    getConnection().stats("responses detailed").get(),
1474                    "responses")
1475                    ->valuestring));
1476    std::stringstream stream;
1477    stream << std::hex << statusCode;
1478    const auto *obj = cJSON_GetObjectItem(stats.get(), stream.str().c_str());
1479    if (obj == nullptr) {
1480        return 0;
1481    }
1482
1483    return gsl::narrow<int>(obj->valueint);
1484}
1485
1486cb::mcbp::Datatype TestappTest::expectedJSONDatatype() const {
1487    return hasJSONSupport() == ClientJSONSupport::Yes ? cb::mcbp::Datatype::JSON
1488                                                      : cb::mcbp::Datatype::Raw;
1489}
1490
1491MemcachedConnection& TestappTest::getConnection() {
1492    // The basic tests should use a plain IPv4 unless something else is
1493    // required (this makes the return value of getConnection predictable
1494    // (rather than returning whatever happened to be stored in "front" of
1495    // the map.
1496    return prepare(connectionMap.getConnection(false, AF_INET));
1497}
1498
1499MemcachedConnection& TestappTest::getAdminConnection() {
1500    auto& conn = getConnection();
1501    conn.authenticate("@admin", "password", "PLAIN");
1502    return conn;
1503}
1504
1505MemcachedConnection& TestappTest::prepare(MemcachedConnection& connection) {
1506    std::vector<cb::mcbp::Feature> features = {
1507            {cb::mcbp::Feature::MUTATION_SEQNO,
1508             cb::mcbp::Feature::XATTR,
1509             cb::mcbp::Feature::XERROR,
1510             cb::mcbp::Feature::SELECT_BUCKET}};
1511    if (hasSnappySupport() == ClientSnappySupport::Yes) {
1512        features.push_back(cb::mcbp::Feature::SNAPPY);
1513    }
1514
1515    if (hasJSONSupport() == ClientJSONSupport::Yes) {
1516        features.push_back(cb::mcbp::Feature::JSON);
1517    }
1518
1519    connection.reconnect();
1520    connection.setFeatures("testapp", features);
1521    return connection;
1522}
1523
1524unique_cJSON_ptr TestappTest::memcached_cfg;
1525std::string TestappTest::portnumber_file;
1526std::string TestappTest::config_file;
1527ConnectionMap TestappTest::connectionMap;
1528uint64_t TestappTest::token;
1529cb_thread_t TestappTest::memcached_server_thread;
1530
1531int main(int argc, char **argv) {
1532    // We need to set MEMCACHED_UNIT_TESTS to enable the use of
1533    // the ewouldblock engine..
1534    static char envvar[80];
1535    snprintf(envvar, sizeof(envvar), "MEMCACHED_UNIT_TESTS=true");
1536    putenv(envvar);
1537
1538    ::testing::InitGoogleTest(&argc, argv);
1539
1540#ifndef WIN32
1541    /*
1542    ** When running the tests from within CLion it starts the test in
1543    ** another directory than pwd. This cause us to fail to locate the
1544    ** memcached binary to start. To work around that lets just do a
1545    ** chdir(dirname(argv[0])).
1546    */
1547    auto testdir = cb::io::dirname(argv[0]);
1548    if (chdir(testdir.c_str()) != 0) {
1549        std::cerr << "Failed to change directory to " << testdir << std::endl;
1550        exit(EXIT_FAILURE);
1551    }
1552#endif
1553
1554
1555#ifdef __sun
1556    {
1557        // Use coreadm to set up a corefile pattern to ensure that the corefiles
1558        // created from the unit tests (of testapp or memcached) don't
1559        // overwrite each other
1560        std::string coreadm =
1561                "coreadm -p core.%%f.%%p " + std::to_string(cb_getpid());
1562        system(coreadm.c_str());
1563    }
1564#endif
1565
1566    std::string engine_name("default");
1567    std::string engine_config;
1568
1569    int cmd;
1570    while ((cmd = getopt(argc, argv, "vc:eE:")) != EOF) {
1571        switch (cmd) {
1572        case 'v':
1573            memcached_verbose++;
1574            break;
1575        case 'c':
1576            engine_config = optarg;
1577            break;
1578        case 'e':
1579            embedded_memcached_server = true;
1580            break;
1581        case 'E':
1582            engine_name.assign(optarg);
1583            break;
1584        default:
1585            std::cerr << "Usage: " << argv[0] << " [-v] [-e]" << std::endl
1586                      << std::endl
1587                      << "  -v Verbose - Print verbose memcached output "
1588                      << "to stderr." << std::endl
1589                      << "               (use multiple times to increase the"
1590                      << " verbosity level." << std::endl
1591                      << "  -c CONFIG - Additional configuration to pass to "
1592                      << "bucket creation." << std::endl
1593                      << "  -e Embedded - Run the memcached daemon in the "
1594                      << "same process (for debugging only..)" << std::endl
1595                      << "  -E ENGINE engine type to use. <default|ep>"
1596                      << std::endl;
1597            return 1;
1598        }
1599    }
1600
1601    /*
1602     * If not running in embedded mode we need the McdEnvironment to manageSSL
1603     * initialization and shutdown.
1604     */
1605    mcd_env = new McdEnvironment(
1606            !embedded_memcached_server, engine_name, engine_config);
1607
1608    ::testing::AddGlobalTestEnvironment(mcd_env);
1609
1610    cb_initialize_sockets();
1611
1612#if !defined(WIN32)
1613    /*
1614     * When shutting down SSL connections the SSL layer may attempt to
1615     * write to the underlying socket. If the socket has been closed
1616     * on the server side then this will raise a SIGPIPE (and
1617     * terminate the test program). This is Bad.
1618     * Therefore ignore SIGPIPE signals; we can use errno == EPIPE if
1619     * we need that information.
1620     */
1621    if (sigignore(SIGPIPE) == -1) {
1622        std::cerr << "Fatal: failed to ignore SIGPIPE; sigaction" << std::endl;
1623        return 1;
1624    }
1625#endif
1626
1627    return RUN_ALL_TESTS();
1628}
1629
1630/* Request stats
1631 * @return a map of stat key & values in the server response.
1632 */
1633stats_response_t request_stats() {
1634    union {
1635        protocol_binary_request_no_extras request;
1636        protocol_binary_response_no_extras response;
1637        char bytes[1024];
1638    } buffer;
1639    stats_response_t result;
1640
1641    size_t len = mcbp_raw_command(buffer.bytes, sizeof(buffer.bytes),
1642                                  PROTOCOL_BINARY_CMD_STAT,
1643                                  NULL, 0, NULL, 0);
1644
1645    safe_send(buffer.bytes, len, false);
1646    while (true) {
1647        safe_recv_packet(buffer.bytes, sizeof(buffer.bytes));
1648        mcbp_validate_response_header(&buffer.response,
1649                                      PROTOCOL_BINARY_CMD_STAT,
1650                                      PROTOCOL_BINARY_RESPONSE_SUCCESS);
1651
1652        const char* key_ptr(buffer.bytes + sizeof(buffer.response) +
1653                            buffer.response.message.header.response.extlen);
1654        const size_t key_len(
1655                buffer.response.message.header.response.getKeylen());
1656
1657        // key length zero indicates end of the stats.
1658        if (key_len == 0) {
1659            break;
1660        }
1661
1662        const char* val_ptr(key_ptr + key_len);
1663        const size_t val_len(
1664                buffer.response.message.header.response.getBodylen() - key_len -
1665                buffer.response.message.header.response.extlen);
1666
1667        result.insert(std::make_pair(std::string(key_ptr, key_len),
1668                                     std::string(val_ptr, val_len)));
1669    }
1670
1671    return result;
1672}
1673
1674// Extracts a single statistic from the set of stats, returning as
1675// a uint64_t
1676uint64_t extract_single_stat(const stats_response_t& stats,
1677                                      const char* name) {
1678    auto iter = stats.find(name);
1679    EXPECT_NE(stats.end(), iter);
1680    uint64_t result = 0;
1681    result = std::stoul(iter->second);
1682    return result;
1683}
1684
1685/*
1686    Using a memcached protocol extesnsion, shift the time
1687*/
1688void adjust_memcached_clock(int64_t clock_shift, TimeType timeType) {
1689    union {
1690        protocol_binary_adjust_time request;
1691        protocol_binary_adjust_time_response response;
1692        char bytes[1024];
1693    } buffer;
1694
1695    auto extlen = sizeof(uint64_t) + sizeof(uint8_t);
1696    size_t len = mcbp_raw_command(buffer.bytes,
1697                                  sizeof(buffer.bytes),
1698                                  PROTOCOL_BINARY_CMD_ADJUST_TIMEOFDAY,
1699                                  NULL,
1700                                  0,
1701                                  NULL,
1702                                  extlen);
1703
1704    buffer.request.message.header.request.extlen =
1705            gsl::narrow_cast<uint8_t>(extlen);
1706    buffer.request.message.body.offset = htonll(clock_shift);
1707    buffer.request.message.body.timeType = timeType;
1708
1709    safe_send(buffer.bytes, len, false);
1710    safe_recv_packet(buffer.bytes, sizeof(buffer.bytes));
1711    mcbp_validate_response_header(&buffer.response,
1712                                  PROTOCOL_BINARY_CMD_ADJUST_TIMEOFDAY,
1713                                  PROTOCOL_BINARY_RESPONSE_SUCCESS);
1714}
1715