-
Notifications
You must be signed in to change notification settings - Fork 0
/
fbuf.c
217 lines (168 loc) · 4.56 KB
/
fbuf.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
/* fbuf.c
*
* Copyright (c) 2015 Eric Chai <electromatter@gmail.com>
* All rights reserved.
*
* This software may be modified and distributed under the terms
* of the ISC license. See the LICENSE file for details.
*/
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <mcp_base/fbuf.h>
/* verify fbuf invariants */
static inline void assert_valid_fbuf(struct fbuf *buf)
{
/* valid pointer */
assert(buf);
/* base = NULL <=> buf->size = 0*/
assert((buf->base != NULL) == (buf->size > 0));
/* start < size and end <= size and start <= end */
assert(buf->end <= buf->size);
/* start = end = 0 or start < end */
assert(buf->start < buf->end || (buf->start == 0 && buf->end == 0));
/* limit invariant */
assert(buf->size <= buf->max_size);
}
#define FBUF_INITIAL_SIZE (1024)
#define FBUF_EXPAND_COEFF (2)
void fbuf_free(struct fbuf *buf)
{
assert_valid_fbuf(buf);
/* if we have a non-zero object then free it's buffer */
if (buf->base)
free(buf->base);
/* and ensure it is cleared */
buf->base = 0;
buf->size = 0;
fbuf_clear(buf);
}
unsigned char *fbuf_wptr(struct fbuf *buf, size_t require)
{
/* check if we need to expand, then if expand failed return null */
if (fbuf_wavail(buf) < require && fbuf_expand(buf, require) < require)
return NULL;
return buf->base + buf->end;
}
void fbuf_produce(struct fbuf *buf, size_t sz)
{
assert_valid_fbuf(buf);
/* overflow check */
assert(sz <= fbuf_wavail(buf));
buf->end += sz;
}
void fbuf_unproduce(struct fbuf *buf, size_t sz)
{
assert_valid_fbuf(buf);
/* underflow check */
assert(sz <= fbuf_avail(buf));
buf->end -= sz;
}
void fbuf_consume(struct fbuf *buf, size_t sz)
{
assert_valid_fbuf(buf);
/* overflow check */
assert(sz <= fbuf_avail(buf));
buf->start += sz;
/* if our buffer is empty, clear it */
if (fbuf_avail(buf) == 0)
fbuf_clear(buf);
}
static size_t next_size(size_t required_size, size_t max_size)
{
size_t size = FBUF_INITIAL_SIZE;
/* exponentialy expand, with overflow check */
while (size < required_size && size < FBUF_MAX / FBUF_EXPAND_COEFF)
size *= FBUF_EXPAND_COEFF;
/* overflow! clamp to max value */
if (size < required_size)
size = FBUF_MAX;
/* clamp to max value */
if (size > max_size)
size = max_size;
return size;
}
size_t fbuf_expand(struct fbuf *buf, size_t requested_size)
{
size_t new_size;
unsigned char *new_base;
assert_valid_fbuf(buf);
/* check if we can already satisfy this request */
if (fbuf_wavail(buf) >= requested_size)
return fbuf_wavail(buf);
/* compute the required size of the buffer */
requested_size += fbuf_avail(buf);
/* check if we can ever satisfy this request */
if (buf->max_size < requested_size)
return fbuf_wavail(buf);
/* check if we can just compact the buffer to satisfy the request */
if (buf->size >= requested_size) {
fbuf_compact(buf);
return fbuf_wavail(buf);
}
/* allocate new space */
new_size = next_size(requested_size, buf->max_size);
new_base = realloc(buf->base, new_size);
/* check if realloc failed */
if (new_base == NULL)
return fbuf_wavail(buf);
/* update the pointers*/
buf->base = new_base;
buf->size = new_size;
/* turns out that buf needs to be compacted to satisfy the request */
if (new_size - buf->start < requested_size)
fbuf_compact(buf);
return fbuf_wavail(buf);
}
void fbuf_compact(struct fbuf *buf)
{
assert_valid_fbuf(buf);
/* rotate the buffer so that base points to the begining */
memmove(buf->base, fbuf_ptr(buf), fbuf_avail(buf));
/* update the pointers */
buf->end -= buf->start;
buf->start = 0;
}
int fbuf_shrink(struct fbuf *buf, size_t new_max)
{
void *new_base;
assert_valid_fbuf(buf);
/* check if new_max can hold the data currently in the buffer */
if (fbuf_avail(buf) > new_max)
return 1;
/* check if we need to resize the buffer */
if (buf->max_size <= new_max || buf->size <= new_max) {
buf->max_size = new_max;
return 0;
}
/* avoid calling realloc with size=0 */
if (new_max == 0) {
free(buf->base);
buf->base = NULL;
buf->size = 0;
buf->max_size = 0;
return 0;
}
/* compact and realloc */
fbuf_compact(buf);
new_base = realloc(buf->base, new_max);
/* check that realloc succeeded */
if (new_base != NULL)
buf->base = new_base;
/* update pointers */
buf->size = new_max;
buf->max_size = new_max;
return 0;
}
int fbuf_copy(struct fbuf *dest, const void *src, size_t size)
{
void *ptr = fbuf_wptr(dest, size);
/* check that we can actually write this block */
if (ptr == NULL)
return 1;
/* copy the data into the buffer */
memcpy(ptr, src, size);
/* and commit it */
fbuf_produce(dest, size);
return 0;
}