The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
dbuff.c
Go to the documentation of this file.
1/*
2 * This library is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU Lesser General Public
4 * License as published by the Free Software Foundation; either
5 * version 2.1 of the License, or (at your option) any later version.
6 *
7 * This library is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * Lesser General Public License for more details.
11 *
12 * You should have received a copy of the GNU Lesser General Public
13 * License along with this library; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17 /** A generic data buffer structure for encoding and decoding
18 *
19 * Because doing manual length checks is error prone and a waste of everyone's time.
20 *
21 * @file src/lib/util/dbuff.c
22 *
23 * @copyright 2020 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
24 */
25
26RCSID("$Id: de5ce998e69f5ff8628c640cd21f668e7fc0fd1a $")
27
28#include <freeradius-devel/util/dbuff.h>
29#include <freeradius-devel/util/syserror.h>
30
31#if defined(STATIC_ANALYZER) || !defined(NDEBUG)
32# define CHECK_DBUFF_INIT(_sbuff) do { if (!(_sbuff)->extend && (unlikely(!(_sbuff)->buff) || unlikely(!(_sbuff)->start) || unlikely(!(_sbuff)->end) || unlikely(!(_sbuff)->p))) return 0; } while (0)
33#else
34# define CHECK_DBUFF_INIT(_sbuff)
35#endif
36
37DIAG_OFF(overlength-strings) /* Seems broken */
38/** Internal macro for defining dbuff move functions
39 * @private
40 */
41#define FR_DBUFF_MOVE_DEF(_out_type, _in_type) \
42size_t _fr_dbuff_move_##_in_type##_to_##_out_type(fr_##_out_type##_t *out, fr_##_in_type##_t *in, size_t len) \
43{ \
44 size_t ext_len, to_copy, remaining = len; \
45 while (remaining > 0) { \
46 to_copy = remaining; \
47 ext_len = fr_dbuff_extend_lowat(NULL, in, to_copy); \
48 if (ext_len < to_copy) to_copy = ext_len; \
49 ext_len = fr_dbuff_extend_lowat(NULL, out, to_copy); \
50 if (ext_len < to_copy) to_copy = ext_len; \
51 if (to_copy == 0) break; \
52 to_copy = _fr_dbuff_safecpy(fr_dbuff_current(out), fr_dbuff_end(out), \
53 fr_dbuff_current(in), fr_dbuff_current(in) + to_copy); \
54 fr_dbuff_advance(out, (size_t)fr_dbuff_advance(in, to_copy)); \
55 remaining -= to_copy; \
56 } \
57 return len - remaining; \
58}
59
60FR_DBUFF_MOVE_DEF(dbuff, dbuff)
61FR_DBUFF_MOVE_DEF(dbuff, dbuff_marker)
62FR_DBUFF_MOVE_DEF(dbuff_marker, dbuff)
63FR_DBUFF_MOVE_DEF(dbuff_marker, dbuff_marker)
64DIAG_ON(overlength-strings)
65
66static inline CC_HINT(always_inline) size_t min(size_t x, size_t y)
67{
68 return x < y ? x : y;
69}
70
71/** Update all markers and pointers in the set of dbuffs to point to new_buff
72 *
73 * This function should be used if the underlying buffer is realloced.
74 *
75 * @param[in] dbuff to update.
76 * @param[in] new_buff to assign to to sbuff.
77 * @param[in] new_len Length of the new buffer.
78 */
79void fr_dbuff_update(fr_dbuff_t *dbuff, uint8_t *new_buff, size_t new_len)
80{
81 fr_dbuff_t *dbuff_i;
82 uint8_t *old_buff; /* Current buff */
83
84 old_buff = dbuff->buff;
85
86 /*
87 * Update pointers to point to positions
88 * in new buffer based on their relative
89 * offsets in the old buffer... but not
90 * past the end of the new buffer.
91 */
92 for (dbuff_i = dbuff; dbuff_i; dbuff_i = dbuff_i->parent) {
94
95 dbuff_i->buff = new_buff;
96 dbuff_i->start = new_buff + min(new_len, dbuff_i->start - old_buff);
97 dbuff_i->end = dbuff_i->buff + new_len;
98 dbuff_i->p = new_buff + min(new_len, dbuff_i->p - old_buff);
99
100 for (m_i = dbuff_i->m; m_i; m_i = m_i->next) m_i->p = new_buff + min(new_len, m_i->p - old_buff);
101 }
102}
103
104/** Shift the contents of the dbuff, returning the number of bytes we managed to shift
105 *
106 * @param[in] dbuff to shift.
107 * @param[in] shift the number of bytes to shift
108 * (not necessarily the amount actually shifted, because
109 * one can't move pointers outside the buffer)
110 *
111 * @return
112 * - 0 the shift failed due to constraining pointers.
113 * - >0 the number of bytes we managed to shift pointers
114 * in the dbuff.
115 */
116size_t fr_dbuff_shift(fr_dbuff_t *dbuff, size_t shift)
117{
118 uint8_t *buff, *end; /* Current start */
119 fr_dbuff_t *dbuff_i;
120 size_t max_shift = shift;
121
122 CHECK_DBUFF_INIT(dbuff);
123
124 buff = dbuff->buff;
125 end = dbuff->end;
126
127 /*
128 * First pass: find the maximum shift, which is the minimum
129 * of the distances from buff to any of the current pointers
130 * or current pointers of markers of dbuff and its ancestors.
131 * (We're also constrained by the requested shift count.)
132 */
133 for (dbuff_i = dbuff; dbuff_i; dbuff_i = dbuff_i->parent) {
135
136 max_shift = min(max_shift, dbuff_i->p - buff);
137 if (!max_shift) return 0;
138
139 for (m_i = dbuff_i->m; m_i; m_i = m_i->next) {
140 max_shift = min(max_shift, m_i->p - buff);
141 if (!max_shift) return 0;
142 }
143 }
144
145 /*
146 * Second pass: adjust pointers.
147 * The first pass means we need only subtract shift from current pointers.
148 * Start pointers can't constrain shift, or we'd never free any space, so
149 * they require the added check.
150 * For d->end, d->buff <= d->p - shift, and d->p shouldn't exceed d->end,
151 * so d->p - shift <= d->end - shift, hence d->buff <= d->end - shift.
152 */
153 for (dbuff_i = dbuff; dbuff_i; dbuff_i = dbuff_i->parent) {
155 uint8_t *start = dbuff_i->start;
156
157 dbuff_i->start -= min(max_shift, dbuff_i->start - buff);
158 dbuff_i->p -= max_shift;
159 dbuff_i->end -= max_shift;
160 dbuff_i->shifted += (max_shift - (start - dbuff_i->start));
161 for (m_i = dbuff_i->m; m_i; m_i = m_i->next) m_i->p -= max_shift;
162 }
163
164 /*
165 * Move data to track moved pointers.
166 */
167 if ((buff + max_shift) < end) memmove(buff, buff + max_shift, end - (buff + max_shift));
168
169 return max_shift;
170}
171
172/** Refresh the buffer with more data from the file
173 *
174 */
175size_t _fr_dbuff_extend_fd(fr_dbuff_t *dbuff, size_t extension)
176{
177 ssize_t bytes_read;
178 size_t available, total_read;
179 fr_dbuff_uctx_fd_t *fctx;
180
181 CHECK_DBUFF_INIT(dbuff);
182
183 fctx = dbuff->uctx;
184
185 if (extension == SIZE_MAX) extension = 0;
186
187 total_read = dbuff->shifted + (dbuff->end - dbuff->buff);
188 if (total_read >= fctx->max) {
189 fr_strerror_printf("Can't satisfy extension request for %zu bytes", extension);
190 return 0; /* There's no way we could satisfy the extension request */
191 }
192
193 if (fr_dbuff_used(dbuff)) {
194 /*
195 * Try and shift as much as we can out
196 * of the buffer to make space.
197 *
198 * Note: p and markers are constraints here.
199 */
200 fr_dbuff_shift(dbuff, fr_dbuff_used(dbuff));
201 }
202
203 available = min(fctx->buff_end - dbuff->end, fctx->max - total_read);
204 if (available < extension) {
205 fr_strerror_printf("Can't satisfy extension request for %zu bytes", extension);
206 return 0; /* There's no way we could satisfy the extension request */
207 }
208
209 bytes_read = read(fctx->fd, dbuff->end, available);
210
211 /** Check for errors
212 */
213 if (bytes_read < 0) {
214 fr_strerror_printf("Error extending buffer: %s", fr_syserror(errno));
215 return 0;
216 }
217
218 /* Question: should extensible ancestors have their ends adjusted? */
219 dbuff->end += bytes_read; /* Advance end, which increases fr_dbuff_remaining() */
220
221 return bytes_read;
222}
223
224/** Reallocate the current buffer
225 *
226 * @private
227 *
228 * @param[in] dbuff to be extended.
229 * @param[in] extension How many additional bytes should be allocated
230 * in the buffer.
231 * @return
232 * - 0 the extension operation failed.
233 * - >0 the number of bytes the buffer was extended by.
234 */
235size_t _fr_dbuff_extend_talloc(fr_dbuff_t *dbuff, size_t extension)
236{
237 fr_dbuff_uctx_talloc_t *tctx = dbuff->uctx;
238 size_t clen, nlen, elen = extension;
239 uint8_t *new_buff;
240
241 CHECK_DBUFF_INIT(dbuff);
242
243 clen = dbuff->buff ? talloc_array_length(dbuff->buff) : 0;
244 /*
245 * If the current buffer size + the extension
246 * is less than init, extend the buffer to init.
247 *
248 * This can happen if the buffer has been
249 * trimmed, and then additional data is added.
250 */
251 if ((clen + elen) < tctx->init) {
252 elen = tctx->init - clen;
253 /*
254 * Double the buffer size if it's more than the
255 * requested amount.
256 */
257 } else if (elen < clen){
258 elen = clen;
259 }
260
261 /*
262 * Check we don't exceed the maximum buffer
263 * length.
264 */
265 if (tctx->max && ((clen + elen) > tctx->max)) {
266 elen = tctx->max - clen;
267 if (elen == 0) {
268 fr_strerror_printf("Failed extending buffer by %zu bytes to "
269 "%zu bytes, max is %zu bytes",
270 extension, clen + extension, tctx->max);
271 return 0;
272 }
273 }
274 nlen = clen + elen;
275
276 new_buff = talloc_realloc(tctx->ctx, dbuff->buff, uint8_t, nlen);
277 if (unlikely(!new_buff)) {
278 fr_strerror_printf("Failed extending buffer by %zu bytes to %zu bytes", elen, nlen);
279 return 0;
280 }
281
282 (void)fr_dbuff_update(dbuff, new_buff, nlen); /* Shouldn't fail as we're extending */
283
284 return elen;
285}
286
287/** Trim a talloced dbuff to the minimum length required to represent the contained string
288 *
289 * @param[in] dbuff to trim.
290 * @param[in] len Length to trim to. Passing SIZE_MAX will
291 * result in the buffer being trimmed to the
292 * length of the content.
293 * @return
294 * - 0 on success.
295 * - -1 on failure - markers present pointing past the end of string data.
296 */
297int fr_dbuff_trim_talloc(fr_dbuff_t *dbuff, size_t len)
298{
299 size_t clen = 0, nlen = 0;
300 uint8_t *new_buff;
301 fr_dbuff_uctx_talloc_t *tctx = dbuff->uctx;
302
303 CHECK_DBUFF_INIT(dbuff);
304
305 if (dbuff->buff) clen = talloc_array_length(dbuff->buff);
306
307 if (len != SIZE_MAX) {
308 nlen += len;
309 } else if (dbuff->buff){
310 nlen += (dbuff->p - dbuff->start);
311 }
312
313 if (nlen != clen) {
314 new_buff = talloc_realloc(tctx->ctx, dbuff->buff, uint8_t, nlen);
315 if (!new_buff) {
316 fr_strerror_printf("Failed trimming buffer from %zu to %zu", clen, nlen);
317 return -1;
318 }
319 fr_dbuff_update(dbuff, new_buff, nlen);
320 }
321
322 return 0;
323}
324
325/** Reset a talloced buffer to its initial length, clearing any data stored
326 *
327 * @param[in] dbuff to reset.
328 * @return
329 * - 0 on success.
330 * - -1 on failure - markers present pointing past the end of string data.
331 */
333{
334 fr_dbuff_uctx_talloc_t *tctx = dbuff->uctx;
335
336 CHECK_DBUFF_INIT(dbuff);
337
338 fr_dbuff_set_to_start(dbuff); /* Clear data */
339
340 if (fr_dbuff_used(dbuff) != tctx->init) {
341 uint8_t *new_buff;
342
343 new_buff = talloc_realloc(tctx->ctx, dbuff->buff, uint8_t, tctx->init);
344 if (!new_buff) {
345 fr_strerror_printf("Failed reallocing from %zu to %zu",
346 talloc_array_length(dbuff->buff), tctx->init);
347 return -1;
348 }
349 dbuff->buff = new_buff;
350 fr_dbuff_update(dbuff, new_buff, tctx->init);
351 }
352
353 return 0;
354}
#define RCSID(id)
Definition build.h:483
#define DIAG_ON(_x)
Definition build.h:458
#define unlikely(_x)
Definition build.h:381
#define DIAG_OFF(_x)
Definition build.h:457
int fr_dbuff_reset_talloc(fr_dbuff_t *dbuff)
Reset a talloced buffer to its initial length, clearing any data stored.
Definition dbuff.c:332
static size_t min(size_t x, size_t y)
Definition dbuff.c:66
int fr_dbuff_trim_talloc(fr_dbuff_t *dbuff, size_t len)
Trim a talloced dbuff to the minimum length required to represent the contained string.
Definition dbuff.c:297
#define CHECK_DBUFF_INIT(_sbuff)
Definition dbuff.c:32
size_t _fr_dbuff_extend_fd(fr_dbuff_t *dbuff, size_t extension)
Refresh the buffer with more data from the file.
Definition dbuff.c:175
void fr_dbuff_update(fr_dbuff_t *dbuff, uint8_t *new_buff, size_t new_len)
Update all markers and pointers in the set of dbuffs to point to new_buff.
Definition dbuff.c:79
#define FR_DBUFF_MOVE_DEF(_out_type, _in_type)
Internal macro for defining dbuff move functions.
Definition dbuff.c:41
size_t fr_dbuff_shift(fr_dbuff_t *dbuff, size_t shift)
Shift the contents of the dbuff, returning the number of bytes we managed to shift.
Definition dbuff.c:116
#define fr_dbuff_used(_dbuff_or_marker)
Return the number of bytes remaining between the start of the dbuff or marker and the current positio...
Definition dbuff.h:767
struct fr_dbuff_marker_s fr_dbuff_marker_t
A position marker associated with a dbuff.
Definition dbuff.h:81
#define fr_dbuff_set_to_start(_dbuff_or_marker)
Reset the 'current' position of the dbuff or marker to the 'start' of the buffer.
Definition dbuff.h:1155
uint8_t * p
long int ssize_t
unsigned char uint8_t
static char buff[sizeof("18446744073709551615")+3]
Definition size_tests.c:41
PRIVATE void strings()
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition syserror.c:243
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition strerror.h:64