1/* @(#) implementation of data-stream cache for udpxy
2 *
3 * Copyright 2008-2011 Pavel V. Cherenkov (pcherenkov@gmail.com)
4 *
5 *  This file is part of udpxy.
6 *
7 *  udpxy is free software: you can redistribute it and/or modify
8 *  it under the terms of the GNU General Public License as published by
9 *  the Free Software Foundation, either version 3 of the License, or
10 *  (at your option) any later version.
11 *
12 *  udpxy is distributed in the hope that it will be useful,
13 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 *  GNU General Public License for more details.
16 *
17 *  You should have received a copy of the GNU General Public License
18 *  along with udpxy.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#include <assert.h>
22#include <stdlib.h>
23#include <sys/types.h>
24
25#include "mtrace.h"
26#include "osdef.h"
27#include "util.h"
28#include "dpkt.h"
29#include "cache.h"
30
31static int ltrace_on = 1;
32
33#define LTRACE( expr )      if( ltrace_on ) TRACE( (expr) )
34
35struct dscache {
36    struct chunk    *head, *tail;       /* cached data chunks           */
37    struct chunk    *read_p, *write_p;  /* read/write-from chunk        */
38
39    size_t          chunklen;           /* length of an individual chunk */
40    size_t          max_totlen;         /* max combined size of chunks   */
41    size_t          totlen;             /* combined size of chunks       */
42
43    struct chunk    *free_head,
44                    *free_tail;         /* free (released) chunks       */
45    struct dstream_ctx
46                    tpl_spc;            /* template of a stream context */
47};
48
49
50/* clone dstream context
51 */
52static int
53clone_spc( const struct dstream_ctx* src, struct dstream_ctx* dst )
54{
55    assert( src && dst );
56
57    dst->stype = src->stype;
58    dst->flags = src->flags;
59    dst->mtu   = src->mtu;
60
61
62    dst->pkt = calloc( src->max_pkt, sizeof(dst->pkt[0]) );
63    if( NULL == dst->pkt ) {
64        (void) tmfprintf( g_flog, "%s: calloc(%ld, %ld)",
65                (long)src->max_pkt, (long)sizeof(dst->pkt[0]) );
66        return -1;
67    }
68
69    dst->max_pkt = src->max_pkt;
70    dst->pkt_count = 0;
71
72    return 0;
73}
74
75
76/* allocate from free-chunk list
77 */
78static struct chunk*
79freelist_alloc( struct dscache* cache, size_t chunklen )
80{
81    struct chunk *chnk, *p;
82
83    assert( cache && (chunklen > 0) );
84
85    for( chnk = cache->free_head, p = NULL; chnk; ) {
86        if( chnk->length >= chunklen ) {
87            break;
88        }
89        else {
90            p = chnk;
91            chnk = chnk->next;
92        }
93    }
94
95    if( !chnk ) return NULL;
96
97    /* remove from the list */
98    chnk->used_length = (size_t)0;
99
100    if( cache->free_tail == chnk )
101        cache->free_tail = p;
102
103    if( cache->free_head == chnk )
104        cache->free_head = chnk->next;
105
106    if( p )
107        p->next = chnk->next;
108
109    /* reset the context */
110    clone_spc( &cache->tpl_spc, &chnk->spc );
111
112    chnk->next = NULL;
113
114    LTRACE( (void)tmfprintf( g_flog,
115                "Freelist-allocated chunk(%p) of len=[%ld]\n",
116                (const void*)chnk, (long)chnk->length ) );
117    return chnk;
118}
119
120
121/* allocate chunk on the heap
122 */
123static struct chunk*
124heap_alloc( struct dscache* cache, size_t chunklen )
125{
126    struct chunk *chnk = NULL;
127    int rc = 0;
128
129    assert( cache && (chunklen > 0) );
130
131    /* allocate on the heap */
132    if( (cache->totlen + chunklen) > cache->max_totlen ) {
133        (void) tmfprintf( g_flog, "%s: cannot add [%ld] bytes to cache, "
134                    "current size=[%ld], max size=[%ld]\n",
135                    __func__, (long)chunklen, (long)cache->totlen,
136                    (long)cache->max_totlen );
137        return NULL;
138    }
139
140    rc = -1;
141    do {
142        chnk = malloc( sizeof(struct chunk) );
143        if( NULL == chnk ) {
144            (void) tmfprintf( g_flog,
145                    "%s: not enough memory for chunk structure size=[%ld]\n",
146                    __func__, (long)sizeof(struct chunk) );
147            break;
148        }
149
150        if( NULL == (chnk->data = malloc( chunklen )) ) {
151            (void) tmfprintf( g_flog, "%s: not enough memory for chunk "
152                    "data of size=[%ld]\n", __func__, chunklen );
153            break;
154        }
155
156        chnk->length = chunklen;
157        chnk->used_length = (size_t)0;
158
159        if( 0 != clone_spc( &cache->tpl_spc, &chnk->spc ) )
160            break;
161
162        rc = 0;
163    } while(0);
164
165    /* error: cleanup */
166    if( 0 != rc ) {
167        if( !chnk ) return NULL;
168
169        if( chnk->data ) free( chnk->data );
170        free( chnk );
171
172        return NULL;
173    }
174
175    cache->totlen += chnk->length;
176    LTRACE( (void)tmfprintf( g_flog,
177                "Heap-allocated chunk=[%p] of len=[%ld]\n",
178                (const void*)chnk, (long)chnk->length ) );
179
180    return chnk;
181}
182
183
184/* add buffer (chunk) of specific size to cache
185 */
186static struct chunk*
187dscache_alloc( struct dscache* cache, size_t chunklen )
188{
189    struct chunk *chnk = freelist_alloc( cache, chunklen );
190    if( !chnk )
191        chnk = heap_alloc( cache, chunklen );
192
193    if( !chnk ) {
194        LTRACE( (void)tmfprintf( g_flog,
195                    "%s: cannot allocate chunk", __func__ ) );
196        return NULL;
197    }
198
199    chnk->next = NULL;
200
201    if( NULL == cache->head )
202        cache->head = chnk;
203
204    if( NULL != cache->tail )
205        cache->tail->next = chnk;
206
207    cache->tail = chnk;
208
209    return chnk;
210}
211
212
213/* initialize the cache
214 */
215struct dscache*
216dscache_init( size_t maxlen, struct dstream_ctx* spc,
217              size_t chunklen, unsigned nchunks )
218{
219    struct dscache* cache = NULL;
220    unsigned i;
221
222    assert( (maxlen > 0) && spc && chunklen );
223    assert( spc->mtu && spc->max_pkt );
224
225    cache = malloc( sizeof(struct dscache) );
226    if( NULL == cache )
227        return NULL;
228
229    cache->head = cache->tail = NULL;
230    cache->read_p = cache->write_p = NULL;
231    cache->max_totlen = maxlen;
232    cache->totlen = 0;
233    cache->chunklen = chunklen;
234
235    cache->free_head = cache->free_tail = NULL;
236    cache->tpl_spc = *spc;
237
238    for( i = 0; i < nchunks; ++i ) {
239        if( NULL == dscache_alloc( cache, cache->chunklen ) ) {
240            dscache_free( cache );
241            return NULL;
242        }
243    }
244
245    cache->read_p = cache->write_p = NULL;
246
247    return cache;
248}
249
250
251/* get chunk at R-head, allocate new one if needed
252 */
253struct chunk*
254dscache_rget( struct dscache* cache, int* error )
255{
256    struct chunk* chnk = NULL;
257
258    assert( cache && error );
259
260    if( NULL == cache->head ) {
261        LTRACE( (void)tmfprintf( g_flog, "%s: cache is empty\n",
262                __func__ ) );
263        *error = ERR_CACHE_END;
264        return NULL;
265    }
266
267    chnk = cache->read_p ? cache->read_p->next : cache->head;
268    if( NULL == chnk ) {
269        chnk = dscache_alloc( cache, cache->chunklen );
270        if( !chnk ) {
271            *error = ERR_CACHE_ALLOC;
272            return NULL;
273        }
274    }
275
276    /* check that next chunk is unused */
277    assert( 0 == chnk->used_length );
278
279    cache->read_p = chnk;
280    LTRACE( (void)tmfprintf( g_flog, "Cache R-head moved to [%p]\n",
281                (const void*)cache->read_p ) );
282
283    return cache->read_p;
284}
285
286
287/* specify length of the used chunk's portion
288 */
289int
290dscache_rset( struct dscache* cache, size_t usedlen )
291{
292    assert( cache );
293
294    if( !cache->read_p ) {
295        LTRACE( (void) tmfprintf( g_flog,
296                    "%s: R-head is not positioned\n", __func__ ) );
297        return ERR_CACHE_END;
298    }
299
300    cache->read_p->used_length = usedlen;
301
302    return 0;
303}
304
305
306
307/* free up data chunk: move it from data- to free-chunk list */
308static int
309dscache_freechunk( struct dscache* cache, struct chunk* chnk )
310{
311    struct chunk *p = NULL;
312
313    assert( chnk && cache->head );
314
315    /* remove chunk from data-chunk list */
316    if( cache->head == chnk ) {
317        p = NULL;   /* no 'previous' */
318        cache->head = chnk->next;
319    }
320    else {
321        for( p = cache->head; p; ) {
322            if( p->next == chnk )
323                break;
324        }
325        if( !p )        /* not found */
326            return -1;
327
328        p->next = chnk->next;
329    }
330
331    /* adjust head positions */
332    if( cache->read_p == chnk )
333        cache->read_p = NULL;
334    if( cache->write_p == chnk )
335        cache->write_p = NULL;
336
337    /* adjust the tail if we shortened it */
338    if( cache->tail == chnk )
339        cache->tail = p;
340
341    chnk->next = NULL;
342    free_dstream_ctx( &chnk->spc );
343
344    /* add chunk to the free-chunk list */
345    if( !cache->free_head )
346        cache->free_head = chnk;
347
348    if( cache->free_tail )
349        cache->free_tail->next = chnk;
350
351    cache->free_tail = chnk;
352
353    LTRACE( (void)tmfprintf( g_flog, "Free-listed chunk(%p)\n",
354                (const void*)chnk ) );
355    return 0;
356}
357
358
359/* advance get a chunk at W-head
360 */
361struct chunk*
362dscache_wget( struct dscache* cache, int* error )
363{
364    struct chunk *chnk = NULL, *old_chnk = NULL;
365
366    assert( cache && error );
367
368    chnk = cache->write_p;
369    if( chnk ) {
370        if( NULL == chnk->next ) {
371            LTRACE( (void)tmfprintf( g_flog,
372                    "%s: W-head cannot advance beyond end of cache\n",
373                    __func__ ) );
374
375            *error = ERR_CACHE_END;
376            return NULL;
377        }
378
379        /* free up the chunk behind */
380        old_chnk = chnk;
381        chnk = chnk->next;
382
383        if( 0 != dscache_freechunk( cache, old_chnk ) ) {
384            LTRACE( (void)tmfprintf( g_flog, "%s: Cannot free chunk(%p)\n",
385                        __func__, (const void*)old_chnk ) );
386            *error = ERR_CACHE_INTRNL;
387            return NULL;
388        }
389    }
390    else {
391        chnk = cache->head;
392        LTRACE( (void)tmfprintf( g_flog, "%s: W-head moved to cache-head [%p]\n",
393                    __func__, (const void*)chnk ) );
394        if( NULL == chnk )
395            *error = ERR_CACHE_END;
396    }
397
398    if( (size_t)0 == chnk->used_length ) {
399        LTRACE( (void)tmfprintf( g_flog,
400            "%s: W-head cannot move to unpopulated data\n", __func__ ) );
401        *error = ERR_CACHE_BADDATA;
402        return NULL;
403    }
404
405    cache->write_p = chnk;
406    return chnk;
407}
408
409
410/* free all chunks in a list
411 */
412static void
413free_chunklist( struct chunk* head )
414{
415    struct chunk *chnk, *p;
416
417    for( chnk = head; chnk; ) {
418        p = chnk->next;
419
420        free( chnk->data );
421        free_dstream_ctx( &chnk->spc );
422
423        free( chnk );
424        LTRACE( (void)tmfprintf( g_flog, "Heap-freed chunk(%p)\n",
425                    (const void*)chnk ) );
426
427        chnk = p;
428    }
429
430    return;
431}
432
433
434/* release all resources from cache
435 */
436void
437dscache_free( struct dscache* cache )
438{
439    assert( cache );
440
441    LTRACE( (void)tmfprintf( g_flog, "Purging chunk list\n" ) );
442    free_chunklist( cache->head );
443
444    LTRACE( (void)tmfprintf( g_flog, "Purging free list\n" ) );
445    free_chunklist( cache->free_head );
446
447    free( cache );
448    LTRACE( (void)tmfprintf( g_flog, "Cache freed up\n" ) );
449
450
451    return;
452}
453
454/* __EOF__ */
455
456