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