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