1#include "config.h"
2#include <stdlib.h>
3#include <string.h>
4#include <time.h>
5#include <memcached/engine.h>
6#include <memcached/extension.h>
7#include <memcached/extension_loggers.h>
8#include <memcached/allocator_hooks.h>
9#include "mock_server.h"
10
11#define REALTIME_MAXDELTA 60*60*24*3
12#define CONN_MAGIC 0xbeefcafe
13
14struct mock_extensions {
15    EXTENSION_DAEMON_DESCRIPTOR *daemons;
16    EXTENSION_LOGGER_DESCRIPTOR *logger;
17};
18
19struct mock_callbacks *mock_event_handlers[MAX_ENGINE_EVENT_TYPE + 1];
20time_t process_started;     /* when the mock server was started */
21rel_time_t time_travel_offset;
22rel_time_t current_time;
23struct mock_connstruct *connstructs;
24struct mock_extensions extensions;
25EXTENSION_LOGGER_DESCRIPTOR *null_logger = NULL;
26EXTENSION_LOGGER_DESCRIPTOR *stderr_logger = NULL;
27ENGINE_HANDLE *engine = NULL;
28
29/**
30 * Session cas elements
31 */
32uint64_t session_cas;
33uint8_t session_ctr;
34cb_mutex_t session_mutex;
35
36/**
37 * SERVER CORE API FUNCTIONS
38 */
39
40static void mock_get_auth_data(const void *cookie, auth_data_t *data) {
41    struct mock_connstruct *c = (struct mock_connstruct *)cookie;
42    if (c != NULL) {
43        data->username = c->uname;
44        data->config = c->config;
45    }
46}
47
48static void mock_store_engine_specific(const void *cookie, void *engine_data) {
49    if (cookie) {
50        struct mock_connstruct *c = (struct mock_connstruct *)cookie;
51        cb_assert(c->magic == CONN_MAGIC);
52        c->engine_data = engine_data;
53    }
54}
55
56static void *mock_get_engine_specific(const void *cookie) {
57    struct mock_connstruct *c = (struct mock_connstruct *)cookie;
58    cb_assert(c == NULL || c->magic == CONN_MAGIC);
59    return c ? c->engine_data : NULL;
60}
61
62static bool mock_is_datatype_supported(const void *cookie) {
63    struct mock_connstruct *c = (struct mock_connstruct *)cookie;
64    cb_assert(c == NULL || c->magic == CONN_MAGIC);
65    return true;
66}
67
68static uint8_t mock_get_opcode_if_ewouldblock_set(const void *cookie) {
69    struct mock_connstruct *c = (struct mock_connstruct *)cookie;
70    cb_assert(c == NULL || c->magic == CONN_MAGIC);
71    return 0x00;
72}
73
74static bool mock_validate_session_cas(const uint64_t cas) {
75    bool ret = true;
76    cb_mutex_enter(&(session_mutex));
77    if (cas != 0) {
78        if (session_cas != cas) {
79            ret = false;
80        } else {
81            session_ctr++;
82        }
83    } else {
84        session_ctr++;
85    }
86    cb_mutex_exit(&(session_mutex));
87    return ret;
88}
89
90static void mock_decrement_session_ctr() {
91    cb_mutex_enter(&(session_mutex));
92    cb_assert(session_ctr != 0);
93    session_ctr--;
94    cb_mutex_exit(&(session_mutex));
95}
96
97static SOCKET mock_get_socket_fd(const void *cookie) {
98    struct mock_connstruct *c = (struct mock_connstruct *)cookie;
99    return c->sfd;
100}
101
102static ENGINE_ERROR_CODE mock_cookie_reserve(const void *cookie) {
103    struct mock_connstruct *c = (struct mock_connstruct *)cookie;
104    c->references++;
105    return ENGINE_SUCCESS;
106}
107
108static ENGINE_ERROR_CODE mock_cookie_release(const void *cookie) {
109    struct mock_connstruct *c = (struct mock_connstruct *)cookie;
110
111    c->references--;
112    if (c->references == 0) {
113        free(c);
114    }
115    return ENGINE_SUCCESS;
116}
117
118static void mock_set_priority(const void* cookie, CONN_PRIORITY priority) {
119    (void)cookie;
120    (void)priority;
121}
122
123static const char *mock_get_server_version(void) {
124    return "mock server";
125}
126
127static uint32_t mock_hash( const void *key, size_t length, const uint32_t initval) {
128    /*this is a very stupid hash indeed */
129    return 1;
130}
131
132/* time-sensitive callers can call it by hand with this, outside the
133   normal ever-1-second timer */
134static rel_time_t mock_get_current_time(void) {
135#ifdef WIN32
136    current_time = (rel_time_t)(time(NULL) - process_started + time_travel_offset);
137#else
138    struct timeval timer;
139    gettimeofday(&timer, NULL);
140    current_time = (rel_time_t) (timer.tv_sec - process_started + time_travel_offset);
141#endif
142
143    return current_time;
144}
145
146static rel_time_t mock_realtime(const time_t exptime) {
147    /* no. of seconds in 30 days - largest possible delta exptime */
148
149    if (exptime == 0) return 0; /* 0 means never expire */
150
151    if (exptime > REALTIME_MAXDELTA) {
152        /* if item expiration is at/before the server started, give it an
153           expiration time of 1 second after the server started.
154           (because 0 means don't expire).  without this, we'd
155           underflow and wrap around to some large value way in the
156           future, effectively making items expiring in the past
157           really expiring never */
158        if (exptime <= process_started)
159            return (rel_time_t)1;
160        return (rel_time_t)(exptime - process_started);
161    } else {
162        return (rel_time_t)(exptime + mock_get_current_time());
163    }
164}
165
166static void mock_notify_io_complete(const void *cookie, ENGINE_ERROR_CODE status) {
167    if (cookie) {
168        struct mock_connstruct *c = (struct mock_connstruct *)cookie;
169        cb_mutex_enter(&c->mutex);
170        c->status = status;
171        cb_cond_signal(&c->cond);
172        cb_mutex_exit(&c->mutex);
173    }
174}
175
176static time_t mock_abstime(const rel_time_t exptime)
177{
178    return process_started + exptime;
179}
180
181void mock_time_travel(int by) {
182    time_travel_offset += by;
183}
184
185static int mock_parse_config(const char *str, struct config_item items[], FILE *error) {
186    return parse_config(str, items, error);
187}
188
189/**
190 * SERVER STAT API FUNCTIONS
191 */
192
193static void *mock_new_independent_stats(void) {
194    struct mock_stats *mockstats = calloc(sizeof(*mockstats), 1);
195    return mockstats;
196}
197
198static void mock_release_independent_stats(void *stats) {
199    struct mock_stats *mockstats = stats;
200    free(mockstats);
201}
202
203static void mock_count_eviction(const void *cookie, const void *key, const int nkey) {
204    struct mock_connstruct *c = (struct mock_connstruct *)cookie;
205    c->evictions++;
206}
207
208/**
209 * SERVER STAT API FUNCTIONS
210 */
211
212static bool mock_register_extension(extension_type_t type, void *extension)
213{
214    if (extension == NULL) {
215        return false;
216    }
217
218    switch (type) {
219    case EXTENSION_DAEMON:
220        {
221            EXTENSION_DAEMON_DESCRIPTOR *ptr;
222            for (ptr =  extensions.daemons; ptr != NULL; ptr = ptr->next) {
223                if (ptr == extension) {
224                    return false;
225                }
226            }
227            ((EXTENSION_DAEMON_DESCRIPTOR *)(extension))->next = extensions.daemons;
228            extensions.daemons = extension;
229        }
230        return true;
231    case EXTENSION_LOGGER:
232        extensions.logger = extension;
233        return true;
234    default:
235        return false;
236    }
237}
238
239static void mock_unregister_extension(extension_type_t type, void *extension)
240{
241    switch (type) {
242    case EXTENSION_DAEMON:
243        {
244            EXTENSION_DAEMON_DESCRIPTOR *prev = NULL;
245            EXTENSION_DAEMON_DESCRIPTOR *ptr = extensions.daemons;
246
247            while (ptr != NULL && ptr != extension) {
248                prev = ptr;
249                ptr = ptr->next;
250            }
251
252            if (ptr != NULL && prev != NULL) {
253                prev->next = ptr->next;
254            }
255
256            if (ptr != NULL && extensions.daemons == ptr) {
257                extensions.daemons = ptr->next;
258            }
259        }
260        break;
261    case EXTENSION_LOGGER:
262        if (extensions.logger == extension) {
263            if (stderr_logger == extension) {
264                extensions.logger = null_logger;
265            } else {
266                extensions.logger = stderr_logger;
267            }
268        }
269        break;
270
271    default:
272        ;
273    }
274
275}
276
277static void* mock_get_extension(extension_type_t type)
278{
279    switch (type) {
280    case EXTENSION_DAEMON:
281        return extensions.daemons;
282
283    case EXTENSION_LOGGER:
284        return extensions.logger;
285
286    default:
287        return NULL;
288    }
289}
290
291/**
292 * SERVER CALLBACK API FUNCTIONS
293 */
294
295static void mock_register_callback(ENGINE_HANDLE *eh,
296                                   ENGINE_EVENT_TYPE type,
297                                   EVENT_CALLBACK cb,
298                                   const void *cb_data) {
299    struct mock_callbacks *h =
300        calloc(sizeof(struct mock_callbacks), 1);
301    cb_assert(h);
302    h->cb = cb;
303    h->cb_data = cb_data;
304    h->next = mock_event_handlers[type];
305    mock_event_handlers[type] = h;
306}
307
308static void mock_perform_callbacks(ENGINE_EVENT_TYPE type,
309                                   const void *data,
310                                   const void *c) {
311    struct mock_callbacks *h;
312    for (h = mock_event_handlers[type]; h; h = h->next) {
313        h->cb(c, type, data, h->cb_data);
314    }
315}
316
317static bool mock_add_new_hook(void (*hook)(const void* ptr, size_t size)) {
318    return false;
319}
320
321static bool mock_remove_new_hook(void (*hook)(const void* ptr, size_t size)) {
322    return false;
323}
324
325static bool mock_add_delete_hook(void (*hook)(const void* ptr)) {
326    return false;
327}
328
329static bool mock_remove_delete_hook(void (*hook)(const void* ptr)) {
330    return false;
331}
332
333static int mock_get_extra_stats_size(void) {
334    return 0;
335}
336
337static void mock_get_allocator_stats(allocator_stats* stats) {
338    (void) stats;
339    return;
340}
341
342static size_t mock_get_allocation_size(const void* ptr) {
343    return 0;
344}
345
346static void mock_get_detailed_stats(char* buffer, int size) {
347    (void) buffer;
348    (void) size;
349}
350
351SERVER_HANDLE_V1 *get_mock_server_api(void)
352{
353   static SERVER_CORE_API core_api;
354   static SERVER_COOKIE_API server_cookie_api;
355   static SERVER_STAT_API server_stat_api;
356   static SERVER_EXTENSION_API extension_api;
357   static SERVER_CALLBACK_API callback_api;
358   static ALLOCATOR_HOOKS_API hooks_api;
359   static SERVER_HANDLE_V1 rv;
360   static int init;
361   if (!init) {
362      init = 1;
363      core_api.server_version = mock_get_server_version;
364      core_api.hash = mock_hash;
365      core_api.realtime = mock_realtime;
366      core_api.get_current_time = mock_get_current_time;
367      core_api.abstime = mock_abstime;
368      core_api.parse_config = mock_parse_config;
369
370      server_cookie_api.get_auth_data = mock_get_auth_data;
371      server_cookie_api.store_engine_specific = mock_store_engine_specific;
372      server_cookie_api.get_engine_specific = mock_get_engine_specific;
373      server_cookie_api.is_datatype_supported = mock_is_datatype_supported;
374      server_cookie_api.get_opcode_if_ewouldblock_set = mock_get_opcode_if_ewouldblock_set;
375      server_cookie_api.validate_session_cas = mock_validate_session_cas;
376      server_cookie_api.decrement_session_ctr = mock_decrement_session_ctr;
377      server_cookie_api.get_socket_fd = mock_get_socket_fd;
378      server_cookie_api.notify_io_complete = mock_notify_io_complete;
379      server_cookie_api.reserve = mock_cookie_reserve;
380      server_cookie_api.release = mock_cookie_release;
381      server_cookie_api.set_priority = mock_set_priority;
382
383      server_stat_api.new_stats = mock_new_independent_stats;
384      server_stat_api.release_stats = mock_release_independent_stats;
385      server_stat_api.evicting = mock_count_eviction;
386
387      extension_api.register_extension = mock_register_extension;
388      extension_api.unregister_extension = mock_unregister_extension;
389      extension_api.get_extension = mock_get_extension;
390
391      callback_api.register_callback = mock_register_callback;
392      callback_api.perform_callbacks = mock_perform_callbacks;
393
394      hooks_api.add_new_hook = mock_add_new_hook;
395      hooks_api.remove_new_hook = mock_remove_new_hook;
396      hooks_api.add_delete_hook = mock_add_delete_hook;
397      hooks_api.remove_delete_hook = mock_remove_delete_hook;
398      hooks_api.get_extra_stats_size = mock_get_extra_stats_size;
399      hooks_api.get_allocator_stats = mock_get_allocator_stats;
400      hooks_api.get_allocation_size = mock_get_allocation_size;
401      hooks_api.get_detailed_stats = mock_get_detailed_stats;
402
403      rv.interface = 1;
404      rv.core = &core_api;
405      rv.stat = &server_stat_api;
406      rv.extension = &extension_api;
407      rv.callback = &callback_api;
408      rv.cookie = &server_cookie_api;
409      rv.alloc_hooks = &hooks_api;
410   }
411
412   return &rv;
413}
414
415void init_mock_server(ENGINE_HANDLE *server_engine) {
416    process_started = time(0);
417    null_logger = get_null_logger();
418    stderr_logger = get_stderr_logger();
419    engine = server_engine;
420    extensions.logger = null_logger;
421    session_cas = 0x0102030405060708;
422    session_ctr = 0;
423    cb_mutex_initialize(&session_mutex);
424}
425
426struct mock_connstruct *mk_mock_connection(const char *user, const char *config) {
427    struct mock_connstruct *rv = calloc(sizeof(struct mock_connstruct), 1);
428    auth_data_t ad;
429    cb_assert(rv);
430    rv->magic = CONN_MAGIC;
431    rv->uname = user ? strdup(user) : NULL;
432    rv->config = config ? strdup(config) : NULL;
433    rv->connected = true;
434    rv->next = connstructs;
435    rv->evictions = 0;
436    rv->sfd = 0; /* TODO make this more realistic */
437    rv->status = ENGINE_SUCCESS;
438    connstructs = rv;
439    mock_perform_callbacks(ON_CONNECT, NULL, rv);
440    if (rv->uname) {
441        mock_get_auth_data(rv, &ad);
442        mock_perform_callbacks(ON_AUTH, (const void*)&ad, rv);
443    }
444
445    cb_mutex_initialize(&rv->mutex);
446    cb_cond_initialize(&rv->cond);
447
448    return rv;
449}
450
451const void *create_mock_cookie(void) {
452    struct mock_connstruct *rv = calloc(sizeof(struct mock_connstruct), 1);
453    cb_assert(rv);
454    rv->magic = CONN_MAGIC;
455    rv->connected = true;
456    rv->status = ENGINE_SUCCESS;
457    rv->handle_ewouldblock = true;
458    rv->references = 1;
459    cb_mutex_initialize(&rv->mutex);
460    cb_cond_initialize(&rv->cond);
461
462    return rv;
463}
464
465void destroy_mock_cookie(const void *cookie) {
466    struct mock_connstruct *c = (struct mock_connstruct *)cookie;
467    disconnect_mock_connection(c);
468    if (c->references == 0) {
469        free((void*)cookie);
470    }
471}
472
473void mock_set_ewouldblock_handling(const void *cookie, bool enable) {
474    struct mock_connstruct *v = (void*)cookie;
475    v->handle_ewouldblock = enable;
476}
477
478void lock_mock_cookie(const void *cookie) {
479   struct mock_connstruct *c = (void*)cookie;
480   cb_mutex_enter(&c->mutex);
481}
482
483void unlock_mock_cookie(const void *cookie) {
484   struct mock_connstruct *c = (void*)cookie;
485   cb_mutex_exit(&c->mutex);
486}
487
488void waitfor_mock_cookie(const void *cookie) {
489   struct mock_connstruct *c = (void*)cookie;
490   cb_cond_wait(&c->cond, &c->mutex);
491}
492
493void disconnect_mock_connection(struct mock_connstruct *c) {
494    c->connected = false;
495    c->references--;
496    mock_perform_callbacks(ON_DISCONNECT, NULL, c);
497}
498
499void disconnect_all_mock_connections(struct mock_connstruct *c) {
500    if (c) {
501        disconnect_mock_connection(c);
502        disconnect_all_mock_connections(c->next);
503        free((void*)c->uname);
504        free((void*)c->config);
505        free(c);
506    }
507}
508
509void destroy_mock_event_callbacks_rec(struct mock_callbacks *h) {
510    if (h) {
511        destroy_mock_event_callbacks_rec(h->next);
512        free(h);
513    }
514}
515
516void destroy_mock_event_callbacks(void) {
517    int i = 0;
518    for (i = 0; i < MAX_ENGINE_EVENT_TYPE; i++) {
519        destroy_mock_event_callbacks_rec(mock_event_handlers[i]);
520        mock_event_handlers[i] = NULL;
521    }
522}
523