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