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