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