xref: /6.0.3/kv_engine/tests/testapp/testapp.cc (revision b6f27f57)
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_AddStringToObject(obj, "ipv4", "optional");
507    cJSON_AddStringToObject(obj, "ipv6", "optional");
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_AddStringToObject(obj, "ipv4", "optional");
521    cJSON_AddStringToObject(obj, "ipv6", "optional");
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_datatype_is_json(const std::string& key, bool isJson) {
937    std::vector<uint8_t> blob;
938    BinprotGetCommand cmd;
939    cmd.setKey(key);
940    cmd.encode(blob);
941    safe_send(blob.data(), blob.size(), false);
942
943    blob.resize(0);
944    safe_recv_packet(blob);
945    BinprotGetResponse rsp;
946    rsp.assign(std::move(blob));
947    ASSERT_EQ(uint16_t(cb::mcbp::Status::Success), rsp.getStatus());
948    EXPECT_EQ(isJson, rsp.getDatatype() & PROTOCOL_BINARY_DATATYPE_JSON);
949}
950
951void validate_object(const char *key, const std::string& expected_value) {
952    union {
953        protocol_binary_request_no_extras request;
954        char bytes[1024];
955    } send;
956    size_t len = mcbp_raw_command(send.bytes, sizeof(send.bytes),
957                                  PROTOCOL_BINARY_CMD_GET,
958                                  key, strlen(key), NULL, 0);
959    safe_send(send.bytes, len, false);
960
961    std::vector<char> receive;
962    safe_recv_packet(receive);
963
964    auto* response = reinterpret_cast<protocol_binary_response_no_extras*>(receive.data());
965    mcbp_validate_response_header(response, PROTOCOL_BINARY_CMD_GET,
966                                  PROTOCOL_BINARY_RESPONSE_SUCCESS);
967    char* ptr = receive.data() + sizeof(*response) + 4;
968    if (response->message.header.response.getStatus() ==
969        PROTOCOL_BINARY_RESPONSE_SUCCESS) {
970        size_t vallen = response->message.header.response.getBodylen() - 4;
971        std::string actual(ptr, vallen);
972        EXPECT_EQ(expected_value, actual);
973    }
974}
975
976void validate_flags(const char *key, uint32_t expected_flags) {
977    union {
978        protocol_binary_request_no_extras request;
979        char bytes[1024];
980    } send;
981    size_t len = mcbp_raw_command(send.bytes, sizeof(send.bytes),
982                                  PROTOCOL_BINARY_CMD_GET,
983                                  key, strlen(key), NULL, 0);
984    safe_send(send.bytes, len, false);
985
986    std::vector<char> receive(4096);
987    safe_recv_packet(receive.data(), receive.size());
988
989    auto* response = reinterpret_cast<protocol_binary_response_no_extras*>(receive.data());
990    mcbp_validate_response_header(response, PROTOCOL_BINARY_CMD_GET,
991                                  PROTOCOL_BINARY_RESPONSE_SUCCESS);
992    const auto* get_response =
993            reinterpret_cast<protocol_binary_response_get*>(receive.data());
994    const uint32_t actual_flags = ntohl(get_response->message.body.flags);
995    EXPECT_EQ(expected_flags, actual_flags);
996}
997
998void delete_object(const char* key, bool ignore_missing) {
999    union {
1000        protocol_binary_request_no_extras request;
1001        protocol_binary_response_no_extras response;
1002        char bytes[1024];
1003    } send, receive;
1004    size_t len = mcbp_raw_command(send.bytes, sizeof(send.bytes),
1005                                  PROTOCOL_BINARY_CMD_DELETE, key, strlen(key),
1006                                  NULL, 0);
1007    safe_send(send.bytes, len, false);
1008    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
1009    if (ignore_missing && receive.response.message.header.response.status ==
1010            PROTOCOL_BINARY_RESPONSE_KEY_ENOENT) {
1011        /* Ignore. Just using this for cleanup then */
1012        return;
1013    }
1014    mcbp_validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_DELETE,
1015                                  PROTOCOL_BINARY_RESPONSE_SUCCESS);
1016}
1017
1018void TestappTest::start_memcached_server(cJSON* config) {
1019    config_file = cb::io::mktemp("memcached_testapp.json");
1020    write_config_to_file(to_string(config), config_file);
1021
1022    server_start_time = time(0);
1023
1024    if (embedded_memcached_server) {
1025        spawn_embedded_server();
1026    } else {
1027        start_external_server();
1028    }
1029    parse_portnumber_file(port, ssl_port);
1030}
1031
1032void store_object_w_datatype(const std::string& key,
1033                             cb::const_char_buffer value,
1034                             uint32_t flags,
1035                             uint32_t expiration,
1036                             cb::mcbp::Datatype datatype) {
1037    protocol_binary_request_set request = {};
1038
1039    request.message.header.request.magic = PROTOCOL_BINARY_REQ;
1040    request.message.header.request.opcode = PROTOCOL_BINARY_CMD_SET;
1041    request.message.header.request.datatype = uint8_t(datatype);
1042    request.message.header.request.extlen = 8;
1043    request.message.header.request.keylen = htons(uint16_t(key.size()));
1044    request.message.header.request.bodylen =
1045            htonl((uint32_t)(key.size() + value.size() + 8));
1046    request.message.header.request.opaque = 0xdeadbeef;
1047    request.message.body.expiration = htonl(expiration);
1048    request.message.body.flags = htonl(flags);
1049
1050    safe_send(&request.bytes, sizeof(request.bytes), false);
1051    safe_send(key.data(), key.size(), false);
1052    safe_send(value.data(), value.size(), false);
1053
1054    union {
1055        protocol_binary_response_no_extras response;
1056        char bytes[1024];
1057    } receive;
1058
1059    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
1060    mcbp_validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_SET,
1061                                  PROTOCOL_BINARY_RESPONSE_SUCCESS);
1062}
1063
1064void store_document(const std::string& key,
1065                    const std::string& value,
1066                    uint32_t flags,
1067                    uint32_t exptime,
1068                    bool compress) {
1069    if (compress) {
1070        bool disable_snappy = false;
1071        if (enabled_hello_features.count(cb::mcbp::Feature::SNAPPY) == 0) {
1072            // We need to enable snappy
1073            set_feature(cb::mcbp::Feature::SNAPPY, true);
1074            disable_snappy = true;
1075        }
1076        cb::compression::Buffer deflated;
1077        cb::compression::deflate(
1078                cb::compression::Algorithm::Snappy, value, deflated);
1079
1080        store_object_w_datatype(key.c_str(),
1081                                deflated,
1082                                flags,
1083                                exptime,
1084                                cb::mcbp::Datatype::Snappy);
1085        if (disable_snappy) {
1086            set_feature(cb::mcbp::Feature::SNAPPY, false);
1087        }
1088    } else {
1089        store_object_w_datatype(
1090                key.c_str(), value, flags, exptime, cb::mcbp::Datatype::Raw);
1091    }
1092}
1093
1094void set_json_feature(bool enable) {
1095    set_feature(cb::mcbp::Feature::JSON, enable);
1096}
1097
1098void set_mutation_seqno_feature(bool enable) {
1099    set_feature(cb::mcbp::Feature::MUTATION_SEQNO, enable);
1100}
1101
1102void TestappTest::waitForShutdown(bool killed) {
1103#ifdef WIN32
1104    ASSERT_EQ(WAIT_OBJECT_0, WaitForSingleObject(server_pid, 60000));
1105    DWORD exit_code = NULL;
1106    GetExitCodeProcess(server_pid, &exit_code);
1107    EXPECT_EQ(0, exit_code);
1108#else
1109    int status;
1110    pid_t ret;
1111    while (true) {
1112        ret = waitpid(server_pid, &status, 0);
1113        if (ret == reinterpret_cast<pid_t>(-1) && errno == EINTR) {
1114            // Just loop again
1115            continue;
1116        }
1117        break;
1118    }
1119    ASSERT_NE(reinterpret_cast<pid_t>(-1), ret)
1120        << "waitpid failed: " << strerror(errno);
1121    bool correctShutdown = killed ? WIFSIGNALED(status) : WIFEXITED(status);
1122    EXPECT_TRUE(correctShutdown)
1123        << "waitpid status     : " << status << std::endl
1124        << "WIFEXITED(status)  : " << WIFEXITED(status) << std::endl
1125        << "WEXITSTATUS(status): " << WEXITSTATUS(status) << std::endl
1126        << "WIFSIGNALED(status): " << WIFSIGNALED(status) << std::endl
1127        << "WTERMSIG(status)   : " << WTERMSIG(status) << " ("
1128        << strsignal(WTERMSIG(status)) << ")" << std::endl
1129        << "WCOREDUMP(status)  : " << WCOREDUMP(status) << std::endl;
1130    EXPECT_EQ(0, WEXITSTATUS(status));
1131#endif
1132    server_pid = reinterpret_cast<pid_t>(-1);
1133}
1134
1135
1136void TestappTest::stop_memcached_server() {
1137
1138    connectionMap.invalidate();
1139    if (sock != INVALID_SOCKET) {
1140        cb::net::closesocket(sock);
1141        sock = INVALID_SOCKET;
1142    }
1143
1144    if (embedded_memcached_server) {
1145        shutdown_server();
1146        cb_join_thread(memcached_server_thread);
1147    }
1148
1149    if (server_pid != reinterpret_cast<pid_t>(-1)) {
1150#ifdef WIN32
1151        TerminateProcess(server_pid, 0);
1152        waitForShutdown();
1153#else
1154        if (kill(server_pid, SIGTERM) == 0) {
1155            waitForShutdown();
1156        }
1157#endif
1158    }
1159
1160    if (!config_file.empty()) {
1161        EXPECT_NE(-1, remove(config_file.c_str()));
1162        config_file.clear();
1163    }
1164}
1165
1166
1167ssize_t socket_send(SOCKET s, const char *buf, size_t len)
1168{
1169    return cb::net::send(s, buf, len, 0);
1170}
1171
1172static ssize_t phase_send(const void *buf, size_t len) {
1173    if (current_phase == phase_ssl) {
1174        return phase_send_ssl(buf, len);
1175    } else {
1176        return socket_send(sock, reinterpret_cast<const char*>(buf), len);
1177    }
1178}
1179
1180ssize_t socket_recv(SOCKET s, char *buf, size_t len)
1181{
1182    return cb::net::recv(s, buf, len, 0);
1183}
1184
1185ssize_t phase_recv(void *buf, size_t len) {
1186    if (current_phase == phase_ssl) {
1187        return phase_recv_ssl(buf, len);
1188    } else {
1189        return socket_recv(sock, reinterpret_cast<char*>(buf), len);
1190    }
1191}
1192
1193static const bool dump_socket_traffic =
1194        getenv("TESTAPP_PACKET_DUMP") != nullptr;
1195
1196static const std::string phase_get_errno() {
1197    if (current_phase == phase_ssl) {
1198        /* could do with more work here, but so far this has sufficed */
1199        return "SSL error";
1200    }
1201    return cb_strerror();
1202}
1203
1204void safe_send(const void* buf, size_t len, bool hickup)
1205{
1206    size_t offset = 0;
1207    const char* ptr = reinterpret_cast<const char*>(buf);
1208    do {
1209        size_t num_bytes = len - offset;
1210        ssize_t nw;
1211        if (hickup) {
1212            if (num_bytes > 1024) {
1213                num_bytes = (rand() % 1023) + 1;
1214            }
1215        }
1216
1217        nw = phase_send(ptr + offset, num_bytes);
1218
1219        if (nw == -1) {
1220            if (errno != EINTR) {
1221                fprintf(stderr,
1222                        "Failed to write: %s\n",
1223                        phase_get_errno().c_str());
1224                print_backtrace_to_file(stderr);
1225                abort();
1226            }
1227        } else {
1228            if (hickup) {
1229                std::this_thread::sleep_for(std::chrono::microseconds(100));
1230            }
1231
1232            if (dump_socket_traffic) {
1233                if (current_phase == phase_ssl) {
1234                    std::cerr << "SSL";
1235                } else {
1236                    std::cerr << "PLAIN";
1237                }
1238                std::cerr << "> ";
1239                for (ssize_t ii = 0; ii < nw; ++ii) {
1240                    std::cerr << "0x" << std::hex << std::setfill('0')
1241                              << std::setw(2)
1242                              << uint32_t(*(uint8_t*)(ptr + offset + ii))
1243                              << ", ";
1244                }
1245                std::cerr << std::dec << std::endl;
1246            }
1247            offset += nw;
1248        }
1249    } while (offset < len);
1250}
1251
1252bool safe_recv(void *buf, size_t len) {
1253    size_t offset = 0;
1254    if (len == 0) {
1255        return true;
1256    }
1257    do {
1258
1259        ssize_t nr = phase_recv(((char*)buf) + offset, len - offset);
1260
1261        if (nr == -1) {
1262            EXPECT_EQ(EINTR, errno) << "Failed to read: " << phase_get_errno();
1263        } else {
1264            if (nr == 0 && allow_closed_read) {
1265                return false;
1266            }
1267            EXPECT_NE(0u, nr);
1268            offset += nr;
1269        }
1270
1271        // Give up if we encountered an error.
1272        if (::testing::Test::HasFailure()) {
1273            return false;
1274        }
1275    } while (offset < len);
1276
1277    return true;
1278}
1279
1280/**
1281 * Internal function which receives a packet. The type parameter should have
1282 * these three functions:
1283 * - resize(n) ->: ensure the buffer has a total capacity for at least n bytes.
1284 *   This function will usually be called once for the header size (i.e. 24)
1285 *   and then another time for the total packet size (i.e. 24 + bodylen).
1286 * - data() -> char*: get the entire buffer
1287 * - size() -> size_t: get the current size of the buffer
1288 *
1289 * @param info an object conforming to the above.
1290 *
1291 * Once the function has completed, it will return true if no read errors
1292 * occurred. The actual size of the packet can be determined by parsing the
1293 * packet header.
1294 *
1295 * See StaticBufInfo which is an implementation that uses a fixed buffer.
1296 * std::vector naturally conforms to the interface.
1297 */
1298template <typename T>
1299bool safe_recv_packetT(T& info) {
1300    info.resize(sizeof(protocol_binary_response_header));
1301    auto *header = reinterpret_cast<protocol_binary_response_header*>(info.data());
1302
1303    if (!safe_recv(header, sizeof(*header))) {
1304        return false;
1305    }
1306
1307    if (dump_socket_traffic) {
1308        if (current_phase == phase_ssl) {
1309            std::cerr << "SSL";
1310        } else {
1311            std::cerr << "PLAIN";
1312        }
1313        std::cerr << "< ";
1314        for (size_t ii = 0; ii < sizeof(*header); ++ii) {
1315            std::cerr << "0x" << std::hex << std::setfill('0') << std::setw(2)
1316                      << uint32_t(header->bytes[ii]) << ", ";
1317        }
1318    }
1319
1320    header->response.status = ntohs(header->response.status);
1321    auto bodylen = header->response.getBodylen();
1322
1323    // Set response to NULL, because the underlying buffer may change.
1324    header = nullptr;
1325
1326    info.resize(sizeof(*header) + bodylen);
1327    auto ret = safe_recv(info.data() + sizeof(*header), bodylen);
1328
1329    if (dump_socket_traffic) {
1330        uint8_t* ptr = (uint8_t*)(info.data() + sizeof(*header));
1331        for (size_t ii = 0; ii < bodylen; ++ii) {
1332            std::cerr << "0x" << std::hex << std::setfill('0') << std::setw(2)
1333                      << uint32_t(ptr[ii]) << ", ";
1334        }
1335        std::cerr << std::endl;
1336    }
1337    return ret;
1338}
1339
1340/**
1341 * Wrapper for a existing buffer which exposes an API suitable for use with
1342 * safe_recv_packetT()
1343 */
1344struct StaticBufInfo {
1345    StaticBufInfo(void *buf_, size_t len_)
1346        : buf(reinterpret_cast<char*>(buf_)), len(len_) {
1347    }
1348    size_t size(size_t) const { return len; }
1349    char *data() { return buf; }
1350
1351    void resize(size_t n) {
1352        if (n > len) {
1353            throw std::runtime_error("Cannot enlarge buffer!");
1354        }
1355    }
1356
1357    char *buf;
1358    const size_t len;
1359};
1360
1361bool safe_recv_packet(void *buf, size_t size) {
1362    StaticBufInfo info(buf, size);
1363    return safe_recv_packetT(info);
1364}
1365
1366bool safe_recv_packet(std::vector<uint8_t>& buf) {
1367    return safe_recv_packetT(buf);
1368}
1369
1370bool safe_recv_packet(std::vector<char>& buf) {
1371    return safe_recv_packetT(buf);
1372}
1373
1374// Configues the ewouldblock_engine to use the given mode; value
1375// is a mode-specific parameter.
1376void TestappTest::ewouldblock_engine_configure(ENGINE_ERROR_CODE err_code,
1377                                               const EWBEngineMode& mode,
1378                                               uint32_t value,
1379                                               const std::string& key) {
1380    union {
1381        request_ewouldblock_ctl request;
1382        protocol_binary_response_no_extras response;
1383        char bytes[1024];
1384    } buffer;
1385
1386    size_t len = mcbp_raw_command(buffer.bytes, sizeof(buffer.bytes),
1387                                  PROTOCOL_BINARY_CMD_EWOULDBLOCK_CTL,
1388                                  key.c_str(), key.size(), NULL, 0);
1389    buffer.request.message.body.mode = htonl(static_cast<uint32_t>(mode));
1390    buffer.request.message.body.value = htonl(value);
1391    buffer.request.message.body.inject_error = htonl(err_code);
1392
1393    safe_send(buffer.bytes, len, false);
1394
1395    safe_recv_packet(buffer.bytes, sizeof(buffer.bytes));
1396    mcbp_validate_response_header(&buffer.response,
1397                                  PROTOCOL_BINARY_CMD_EWOULDBLOCK_CTL,
1398                                  PROTOCOL_BINARY_RESPONSE_SUCCESS);
1399}
1400
1401void TestappTest::ewouldblock_engine_disable() {
1402    // Value for err_code doesn't matter...
1403    ewouldblock_engine_configure(ENGINE_EWOULDBLOCK, EWBEngineMode::Next_N, 0);
1404}
1405
1406void TestappTest::reconfigure() {
1407    write_config_to_file(to_string(memcached_cfg, true), config_file);
1408    auto& conn = getAdminConnection();
1409
1410    BinprotGenericCommand req{PROTOCOL_BINARY_CMD_CONFIG_RELOAD, {}, {}};
1411    BinprotResponse resp;
1412    conn.executeCommand(req, resp);
1413    ASSERT_TRUE(resp.isSuccess()) << "Failed to reconfigure the server";
1414    conn.reconnect();
1415}
1416
1417void TestappTest::runCreateXattr(
1418        const std::string& path,
1419        const std::string& value,
1420        bool macro,
1421        protocol_binary_response_status expectedStatus) {
1422    auto& connection = getConnection();
1423
1424    BinprotSubdocCommand cmd;
1425    cmd.setOp(PROTOCOL_BINARY_CMD_SUBDOC_DICT_ADD);
1426    cmd.setKey(name);
1427    cmd.setPath(path);
1428    cmd.setValue(value);
1429    if (macro) {
1430        cmd.addPathFlags(SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_EXPAND_MACROS |
1431                         SUBDOC_FLAG_MKDIR_P);
1432    } else {
1433        cmd.addPathFlags(SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_MKDIR_P);
1434    }
1435
1436    connection.sendCommand(cmd);
1437
1438    BinprotResponse resp;
1439    connection.recvResponse(resp);
1440    EXPECT_EQ(expectedStatus, resp.getStatus());
1441}
1442
1443void TestappTest::createXattr(const std::string& path,
1444                              const std::string& value,
1445                              bool macro) {
1446    runCreateXattr(path, value, macro, PROTOCOL_BINARY_RESPONSE_SUCCESS);
1447}
1448
1449BinprotSubdocResponse TestappTest::runGetXattr(
1450        const std::string& path,
1451        bool deleted,
1452        protocol_binary_response_status expectedStatus) {
1453    auto& connection = getConnection();
1454
1455    BinprotSubdocCommand cmd;
1456    cmd.setOp(PROTOCOL_BINARY_CMD_SUBDOC_GET);
1457    cmd.setKey(name);
1458    cmd.setPath(path);
1459    if (deleted) {
1460        cmd.addPathFlags(SUBDOC_FLAG_XATTR_PATH);
1461        cmd.addDocFlags(mcbp::subdoc::doc_flag::AccessDeleted);
1462    } else {
1463        cmd.addPathFlags(SUBDOC_FLAG_XATTR_PATH);
1464    }
1465    connection.sendCommand(cmd);
1466
1467    BinprotSubdocResponse resp;
1468    connection.recvResponse(resp);
1469    auto status = resp.getStatus();
1470    if (deleted && status == PROTOCOL_BINARY_RESPONSE_SUBDOC_SUCCESS_DELETED) {
1471        status = PROTOCOL_BINARY_RESPONSE_SUCCESS;
1472    }
1473
1474    if (status != expectedStatus) {
1475        throw ConnectionError("runGetXattr() failed: ", resp);
1476    }
1477    return resp;
1478}
1479
1480BinprotSubdocResponse TestappTest::getXattr(const std::string& path,
1481                                            bool deleted) {
1482    return runGetXattr(path, deleted, PROTOCOL_BINARY_RESPONSE_SUCCESS);
1483}
1484
1485int TestappTest::getResponseCount(protocol_binary_response_status statusCode) {
1486    unique_cJSON_ptr stats(cJSON_Parse(
1487            cJSON_GetObjectItem(
1488                    getConnection().stats("responses detailed").get(),
1489                    "responses")
1490                    ->valuestring));
1491    std::stringstream stream;
1492    stream << std::hex << statusCode;
1493    const auto *obj = cJSON_GetObjectItem(stats.get(), stream.str().c_str());
1494    if (obj == nullptr) {
1495        return 0;
1496    }
1497
1498    return gsl::narrow<int>(obj->valueint);
1499}
1500
1501cb::mcbp::Datatype TestappTest::expectedJSONDatatype() const {
1502    return hasJSONSupport() == ClientJSONSupport::Yes ? cb::mcbp::Datatype::JSON
1503                                                      : cb::mcbp::Datatype::Raw;
1504}
1505
1506MemcachedConnection& TestappTest::getConnection() {
1507    // The basic tests should use a plain IPv4 unless something else is
1508    // required (this makes the return value of getConnection predictable
1509    // (rather than returning whatever happened to be stored in "front" of
1510    // the map.
1511    return prepare(connectionMap.getConnection(false, AF_INET));
1512}
1513
1514MemcachedConnection& TestappTest::getAdminConnection() {
1515    auto& conn = getConnection();
1516    conn.authenticate("@admin", "password", "PLAIN");
1517    return conn;
1518}
1519
1520MemcachedConnection& TestappTest::prepare(MemcachedConnection& connection) {
1521    std::vector<cb::mcbp::Feature> features = {
1522            {cb::mcbp::Feature::MUTATION_SEQNO,
1523             cb::mcbp::Feature::XATTR,
1524             cb::mcbp::Feature::XERROR,
1525             cb::mcbp::Feature::SELECT_BUCKET}};
1526    if (hasSnappySupport() == ClientSnappySupport::Yes) {
1527        features.push_back(cb::mcbp::Feature::SNAPPY);
1528    }
1529
1530    if (hasJSONSupport() == ClientJSONSupport::Yes) {
1531        features.push_back(cb::mcbp::Feature::JSON);
1532    }
1533
1534    connection.reconnect();
1535    connection.setFeatures("testapp", features);
1536    return connection;
1537}
1538
1539unique_cJSON_ptr TestappTest::memcached_cfg;
1540std::string TestappTest::portnumber_file;
1541std::string TestappTest::config_file;
1542ConnectionMap TestappTest::connectionMap;
1543uint64_t TestappTest::token;
1544cb_thread_t TestappTest::memcached_server_thread;
1545
1546int main(int argc, char **argv) {
1547    // We need to set MEMCACHED_UNIT_TESTS to enable the use of
1548    // the ewouldblock engine..
1549    static char envvar[80];
1550    snprintf(envvar, sizeof(envvar), "MEMCACHED_UNIT_TESTS=true");
1551    putenv(envvar);
1552
1553    ::testing::InitGoogleTest(&argc, argv);
1554
1555#ifndef WIN32
1556    /*
1557    ** When running the tests from within CLion it starts the test in
1558    ** another directory than pwd. This cause us to fail to locate the
1559    ** memcached binary to start. To work around that lets just do a
1560    ** chdir(dirname(argv[0])).
1561    */
1562    auto testdir = cb::io::dirname(argv[0]);
1563    if (chdir(testdir.c_str()) != 0) {
1564        std::cerr << "Failed to change directory to " << testdir << std::endl;
1565        exit(EXIT_FAILURE);
1566    }
1567#endif
1568
1569
1570#ifdef __sun
1571    {
1572        // Use coreadm to set up a corefile pattern to ensure that the corefiles
1573        // created from the unit tests (of testapp or memcached) don't
1574        // overwrite each other
1575        std::string coreadm =
1576                "coreadm -p core.%%f.%%p " + std::to_string(cb_getpid());
1577        system(coreadm.c_str());
1578    }
1579#endif
1580
1581    std::string engine_name("default");
1582    std::string engine_config;
1583
1584    int cmd;
1585    while ((cmd = getopt(argc, argv, "vc:eE:")) != EOF) {
1586        switch (cmd) {
1587        case 'v':
1588            memcached_verbose++;
1589            break;
1590        case 'c':
1591            engine_config = optarg;
1592            break;
1593        case 'e':
1594            embedded_memcached_server = true;
1595            break;
1596        case 'E':
1597            engine_name.assign(optarg);
1598            break;
1599        default:
1600            std::cerr << "Usage: " << argv[0] << " [-v] [-e]" << std::endl
1601                      << std::endl
1602                      << "  -v Verbose - Print verbose memcached output "
1603                      << "to stderr." << std::endl
1604                      << "               (use multiple times to increase the"
1605                      << " verbosity level." << std::endl
1606                      << "  -c CONFIG - Additional configuration to pass to "
1607                      << "bucket creation." << std::endl
1608                      << "  -e Embedded - Run the memcached daemon in the "
1609                      << "same process (for debugging only..)" << std::endl
1610                      << "  -E ENGINE engine type to use. <default|ep>"
1611                      << std::endl;
1612            return 1;
1613        }
1614    }
1615
1616    /*
1617     * If not running in embedded mode we need the McdEnvironment to manageSSL
1618     * initialization and shutdown.
1619     */
1620    mcd_env = new McdEnvironment(
1621            !embedded_memcached_server, engine_name, engine_config);
1622
1623    ::testing::AddGlobalTestEnvironment(mcd_env);
1624
1625    cb_initialize_sockets();
1626
1627#if !defined(WIN32)
1628    /*
1629     * When shutting down SSL connections the SSL layer may attempt to
1630     * write to the underlying socket. If the socket has been closed
1631     * on the server side then this will raise a SIGPIPE (and
1632     * terminate the test program). This is Bad.
1633     * Therefore ignore SIGPIPE signals; we can use errno == EPIPE if
1634     * we need that information.
1635     */
1636    if (sigignore(SIGPIPE) == -1) {
1637        std::cerr << "Fatal: failed to ignore SIGPIPE; sigaction" << std::endl;
1638        return 1;
1639    }
1640#endif
1641
1642    return RUN_ALL_TESTS();
1643}
1644
1645/* Request stats
1646 * @return a map of stat key & values in the server response.
1647 */
1648stats_response_t request_stats() {
1649    union {
1650        protocol_binary_request_no_extras request;
1651        protocol_binary_response_no_extras response;
1652        char bytes[1024];
1653    } buffer;
1654    stats_response_t result;
1655
1656    size_t len = mcbp_raw_command(buffer.bytes, sizeof(buffer.bytes),
1657                                  PROTOCOL_BINARY_CMD_STAT,
1658                                  NULL, 0, NULL, 0);
1659
1660    safe_send(buffer.bytes, len, false);
1661    while (true) {
1662        safe_recv_packet(buffer.bytes, sizeof(buffer.bytes));
1663        mcbp_validate_response_header(&buffer.response,
1664                                      PROTOCOL_BINARY_CMD_STAT,
1665                                      PROTOCOL_BINARY_RESPONSE_SUCCESS);
1666
1667        const char* key_ptr(buffer.bytes + sizeof(buffer.response) +
1668                            buffer.response.message.header.response.extlen);
1669        const size_t key_len(
1670                buffer.response.message.header.response.getKeylen());
1671
1672        // key length zero indicates end of the stats.
1673        if (key_len == 0) {
1674            break;
1675        }
1676
1677        const char* val_ptr(key_ptr + key_len);
1678        const size_t val_len(
1679                buffer.response.message.header.response.getBodylen() - key_len -
1680                buffer.response.message.header.response.extlen);
1681
1682        result.insert(std::make_pair(std::string(key_ptr, key_len),
1683                                     std::string(val_ptr, val_len)));
1684    }
1685
1686    return result;
1687}
1688
1689// Extracts a single statistic from the set of stats, returning as
1690// a uint64_t
1691uint64_t extract_single_stat(const stats_response_t& stats,
1692                                      const char* name) {
1693    auto iter = stats.find(name);
1694    EXPECT_NE(stats.end(), iter);
1695    uint64_t result = 0;
1696    result = std::stoul(iter->second);
1697    return result;
1698}
1699
1700/*
1701    Using a memcached protocol extesnsion, shift the time
1702*/
1703void adjust_memcached_clock(int64_t clock_shift, TimeType timeType) {
1704    union {
1705        protocol_binary_adjust_time request;
1706        protocol_binary_adjust_time_response response;
1707        char bytes[1024];
1708    } buffer;
1709
1710    auto extlen = sizeof(uint64_t) + sizeof(uint8_t);
1711    size_t len = mcbp_raw_command(buffer.bytes,
1712                                  sizeof(buffer.bytes),
1713                                  PROTOCOL_BINARY_CMD_ADJUST_TIMEOFDAY,
1714                                  NULL,
1715                                  0,
1716                                  NULL,
1717                                  extlen);
1718
1719    buffer.request.message.header.request.extlen =
1720            gsl::narrow_cast<uint8_t>(extlen);
1721    buffer.request.message.body.offset = htonll(clock_shift);
1722    buffer.request.message.body.timeType = timeType;
1723
1724    safe_send(buffer.bytes, len, false);
1725    safe_recv_packet(buffer.bytes, sizeof(buffer.bytes));
1726    mcbp_validate_response_header(&buffer.response,
1727                                  PROTOCOL_BINARY_CMD_ADJUST_TIMEOFDAY,
1728                                  PROTOCOL_BINARY_RESPONSE_SUCCESS);
1729}
1730