/src/dovecot/src/lib/buffer.c
Line | Count | Source |
1 | | /* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ |
2 | | |
3 | | /* @UNSAFE: whole file */ |
4 | | |
5 | | #include "lib.h" |
6 | | #include "safe-memset.h" |
7 | | #include "buffer.h" |
8 | | |
9 | | #define BUFFER_TRAILING_SENTRY_CHR 0xAB |
10 | | |
11 | | /* Disable our memcpy() safety wrapper. This file is very performance sensitive |
12 | | and it's been checked to work correctly with memcpy(). */ |
13 | | #undef memcpy |
14 | | |
15 | | struct real_buffer { |
16 | | union { |
17 | | struct buffer buf; |
18 | | struct { |
19 | | /* public: */ |
20 | | const void *r_buffer; |
21 | | size_t used; |
22 | | /* private: */ |
23 | | unsigned char *w_buffer; |
24 | | size_t dirty, alloc, writable_size, max_size; |
25 | | |
26 | | pool_t pool; |
27 | | |
28 | | bool alloced:1; |
29 | | bool dynamic:1; |
30 | | }; |
31 | | }; |
32 | | }; |
33 | | typedef int buffer_check_sizes[COMPILE_ERROR_IF_TRUE(sizeof(struct real_buffer) > sizeof(buffer_t)) ?1:1]; |
34 | | |
35 | | static void buffer_alloc(struct real_buffer *buf, size_t size) |
36 | 0 | { |
37 | 0 | i_assert(buf->w_buffer == NULL || buf->alloced); |
38 | | |
39 | 0 | if (size == buf->alloc) |
40 | 0 | return; |
41 | | |
42 | 0 | i_assert(size > buf->alloc); |
43 | | |
44 | 0 | if (buf->w_buffer == NULL) |
45 | 0 | buf->w_buffer = p_malloc(buf->pool, size); |
46 | 0 | else |
47 | 0 | buf->w_buffer = p_realloc(buf->pool, buf->w_buffer, buf->alloc, size); |
48 | 0 | buf->alloc = size; |
49 | 0 | buf->writable_size = size-1; /* -1 for str_c() NUL */ |
50 | |
|
51 | 0 | buf->r_buffer = buf->w_buffer; |
52 | 0 | buf->alloced = TRUE; |
53 | 0 | } |
54 | | |
55 | | static inline void |
56 | | buffer_check_limits(struct real_buffer *buf, size_t pos, size_t data_size) |
57 | 0 | { |
58 | 0 | size_t new_size; |
59 | |
|
60 | 0 | if (unlikely(pos > buf->max_size || buf->max_size - pos < data_size)) |
61 | 0 | i_panic("Buffer write out of range (%zu + %zu)", pos, data_size); |
62 | | |
63 | 0 | new_size = pos + data_size; |
64 | |
|
65 | 0 | if (new_size > buf->used && buf->used < buf->dirty) { |
66 | | /* clear used..dirty area */ |
67 | 0 | size_t max = I_MIN(I_MIN(buf->alloc, buf->dirty), new_size); |
68 | |
|
69 | 0 | memset(buf->w_buffer + buf->used, 0, max - buf->used); |
70 | 0 | } |
71 | | |
72 | | /* Use buf->writable_size instead of buf->alloc to always keep +1 byte |
73 | | available in case str_c() is called for this buffer. This is mainly |
74 | | for cases where the buffer is allocated from data stack, and str_c() |
75 | | is called in a separate stack frame. */ |
76 | 0 | if (new_size > buf->writable_size) { |
77 | 0 | if (unlikely(!buf->dynamic)) { |
78 | 0 | i_panic("Buffer full (%zu > %zu, pool %s)", |
79 | 0 | pos + data_size, buf->alloc, |
80 | 0 | buf->pool == NULL ? "<none>" : |
81 | 0 | pool_get_name(buf->pool)); |
82 | 0 | } |
83 | | |
84 | 0 | size_t new_alloc_size = |
85 | 0 | pool_get_exp_grown_size(buf->pool, buf->alloc, |
86 | 0 | new_size + 1); |
87 | 0 | if (new_alloc_size > buf->max_size) { |
88 | | /* limit to max_size, but do include +1 for |
89 | | str_c() NUL */ |
90 | 0 | new_alloc_size = buf->max_size + 1; |
91 | 0 | } |
92 | 0 | buffer_alloc(buf, new_alloc_size); |
93 | 0 | } |
94 | | #if 0 |
95 | | else if (new_size > buf->used && buf->alloced && |
96 | | !buf->pool->alloconly_pool && !buf->pool->datastack_pool) { |
97 | | void *new_buf; |
98 | | |
99 | | /* buffer's size increased: move the buffer's memory elsewhere. |
100 | | this should help catch bugs where old pointers are tried to |
101 | | be used to access the buffer's memory */ |
102 | | new_buf = p_malloc(buf->pool, buf->alloc); |
103 | | memcpy(new_buf, buf->w_buffer, buf->alloc); |
104 | | p_free(buf->pool, buf->w_buffer); |
105 | | |
106 | | buf->w_buffer = new_buf; |
107 | | buf->r_buffer = new_buf; |
108 | | } |
109 | | #endif |
110 | | |
111 | 0 | if (new_size > buf->used) |
112 | 0 | buf->used = new_size; |
113 | 0 | i_assert(buf->used <= buf->alloc); |
114 | 0 | i_assert(buf->w_buffer != NULL); |
115 | |
|
116 | | #ifdef DEBUG_FAST |
117 | | if (new_size == buf->used && new_size < buf->alloc && |
118 | | buf->dirty <= new_size && buf->w_buffer != NULL) { |
119 | | buf->w_buffer[new_size] = BUFFER_TRAILING_SENTRY_CHR; |
120 | | buf->dirty = new_size + 1; |
121 | | } |
122 | | #endif |
123 | 0 | } |
124 | | |
125 | | static inline void |
126 | | buffer_check_append_limits(struct real_buffer *buf, size_t data_size) |
127 | 0 | { |
128 | | /* Fast path: See if data to be appended fits into allocated buffer. |
129 | | If it does, we don't even need to memset() the dirty buffer since |
130 | | it's going to be filled with the newly appended data. */ |
131 | 0 | #ifndef DEBUG_FAST |
132 | 0 | i_assert(buf->used <= buf->writable_size); |
133 | 0 | if (buf->writable_size - buf->used < data_size) |
134 | 0 | buffer_check_limits(buf, buf->used, data_size); |
135 | 0 | else |
136 | 0 | buf->used += data_size; |
137 | | #else |
138 | | /* Always add trailing sentry with debug checks */ |
139 | | buffer_check_limits(buf, buf->used, data_size); |
140 | | #endif |
141 | 0 | } |
142 | | |
143 | | #undef buffer_create_from_data |
144 | | void buffer_create_from_data(buffer_t *buffer, void *data, size_t size) |
145 | 0 | { |
146 | 0 | struct real_buffer *buf; |
147 | |
|
148 | 0 | i_assert(sizeof(*buffer) >= sizeof(struct real_buffer)); |
149 | | |
150 | 0 | buf = container_of(buffer, struct real_buffer, buf); |
151 | 0 | i_zero(buf); |
152 | 0 | buf->alloc = buf->writable_size = buf->max_size = size; |
153 | 0 | buf->r_buffer = buf->w_buffer = data; |
154 | | /* clear the whole memory area. unnecessary usually, but if the |
155 | | buffer is used by e.g. str_c() it tries to access uninitialized |
156 | | memory */ |
157 | 0 | memset(data, 0, size); |
158 | 0 | } |
159 | | |
160 | | #undef buffer_create_from_const_data |
161 | | void buffer_create_from_const_data(buffer_t *buffer, |
162 | | const void *data, size_t size) |
163 | 0 | { |
164 | 0 | struct real_buffer *buf; |
165 | |
|
166 | 0 | i_assert(sizeof(*buffer) >= sizeof(struct real_buffer)); |
167 | | |
168 | 0 | buf = container_of(buffer, struct real_buffer, buf); |
169 | 0 | i_zero(buf); |
170 | |
|
171 | 0 | buf->used = buf->alloc = buf->writable_size = buf->max_size = size; |
172 | 0 | buf->r_buffer = data; |
173 | 0 | i_assert(buf->w_buffer == NULL); |
174 | 0 | } |
175 | | |
176 | | buffer_t *buffer_create_dynamic(pool_t pool, size_t init_size) |
177 | 0 | { |
178 | 0 | return buffer_create_dynamic_max(pool, init_size, SIZE_MAX); |
179 | 0 | } |
180 | | |
181 | | buffer_t *buffer_create_dynamic_max(pool_t pool, size_t init_size, |
182 | | size_t max_size) |
183 | 0 | { |
184 | 0 | struct real_buffer *buf; |
185 | |
|
186 | | #ifdef DEBUG_FAST |
187 | | /* we increment this by 1 later on, so if it's SIZE_MAX |
188 | | it turns into 0 and hides a potential bug. |
189 | | |
190 | | Too scary to use in production for now, though. This |
191 | | can change in future. */ |
192 | | i_assert(init_size < SIZE_MAX); |
193 | | #endif |
194 | |
|
195 | 0 | buf = p_new(pool, struct real_buffer, 1); |
196 | 0 | buf->pool = pool; |
197 | 0 | buf->dynamic = TRUE; |
198 | 0 | buf->max_size = max_size; |
199 | | /* buffer_alloc() reserves +1 for str_c() NIL, so add +1 here to |
200 | | init_size so we can actually write that much to the buffer without |
201 | | realloc */ |
202 | 0 | buffer_alloc(buf, init_size+1); |
203 | 0 | return &buf->buf; |
204 | 0 | } |
205 | | |
206 | | void buffer_free(buffer_t **_buf) |
207 | 0 | { |
208 | 0 | if (*_buf == NULL) |
209 | 0 | return; |
210 | 0 | struct real_buffer *buf = container_of(*_buf, struct real_buffer, buf); |
211 | |
|
212 | 0 | *_buf = NULL; |
213 | 0 | if (buf->alloced) |
214 | 0 | p_free(buf->pool, buf->w_buffer); |
215 | 0 | if (buf->pool != NULL) |
216 | 0 | p_free(buf->pool, buf); |
217 | 0 | } |
218 | | |
219 | | void *buffer_free_without_data(buffer_t **_buf) |
220 | 0 | { |
221 | 0 | struct real_buffer *buf = container_of(*_buf, struct real_buffer, buf); |
222 | 0 | void *data; |
223 | |
|
224 | 0 | *_buf = NULL; |
225 | |
|
226 | 0 | data = buf->w_buffer; |
227 | 0 | p_free(buf->pool, buf); |
228 | 0 | return data; |
229 | 0 | } |
230 | | |
231 | | pool_t buffer_get_pool(const buffer_t *_buf) |
232 | 0 | { |
233 | 0 | const struct real_buffer *buf = |
234 | 0 | container_of(_buf, const struct real_buffer, buf); |
235 | |
|
236 | 0 | return buf->pool; |
237 | 0 | } |
238 | | |
239 | | void buffer_write(buffer_t *_buf, size_t pos, |
240 | | const void *data, size_t data_size) |
241 | 0 | { |
242 | 0 | struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); |
243 | |
|
244 | 0 | buffer_check_limits(buf, pos, data_size); |
245 | 0 | if (data_size > 0) |
246 | 0 | memcpy(buf->w_buffer + pos, data, data_size); |
247 | 0 | } |
248 | | |
249 | | void buffer_write_array(buffer_t *buf, size_t pos, |
250 | | const void *data, size_t count, size_t size) |
251 | 0 | { |
252 | 0 | buffer_write(buf, pos, data, MALLOC_MULTIPLY(count, size)); |
253 | 0 | } |
254 | | |
255 | | void buffer_append(buffer_t *_buf, const void *data, size_t data_size) |
256 | 0 | { |
257 | 0 | struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); |
258 | |
|
259 | 0 | if (data_size > 0) { |
260 | 0 | size_t pos = buf->used; |
261 | 0 | buffer_check_append_limits(buf, data_size); |
262 | 0 | memcpy(buf->w_buffer + pos, data, data_size); |
263 | 0 | } |
264 | 0 | } |
265 | | |
266 | | void buffer_append_array(buffer_t *buf, const void *data, |
267 | | size_t count, size_t size) |
268 | 0 | { |
269 | 0 | buffer_append(buf, data, MALLOC_MULTIPLY(count, size)); |
270 | 0 | } |
271 | | |
272 | | void buffer_append_c(buffer_t *_buf, unsigned char chr) |
273 | 0 | { |
274 | 0 | struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); |
275 | 0 | size_t pos = buf->used; |
276 | |
|
277 | 0 | buffer_check_append_limits(buf, 1); |
278 | 0 | buf->w_buffer[pos] = chr; |
279 | 0 | } |
280 | | |
281 | | void buffer_insert(buffer_t *_buf, size_t pos, |
282 | | const void *data, size_t data_size) |
283 | 0 | { |
284 | 0 | struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); |
285 | |
|
286 | 0 | if (pos >= buf->used) |
287 | 0 | buffer_write(_buf, pos, data, data_size); |
288 | 0 | else if (data_size > 0) { |
289 | 0 | buffer_copy(_buf, pos + data_size, _buf, pos, SIZE_MAX); |
290 | 0 | memcpy(buf->w_buffer + pos, data, data_size); |
291 | 0 | } |
292 | 0 | } |
293 | | |
294 | | void buffer_insert_array(buffer_t *buf, size_t pos, |
295 | | const void *data, size_t count, size_t size) |
296 | 0 | { |
297 | 0 | buffer_insert(buf, pos, data, MALLOC_MULTIPLY(count, size)); |
298 | 0 | } |
299 | | |
300 | | void buffer_delete(buffer_t *_buf, size_t pos, size_t size) |
301 | 0 | { |
302 | 0 | struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); |
303 | 0 | size_t end_size; |
304 | |
|
305 | 0 | if (pos >= buf->used) |
306 | 0 | return; |
307 | 0 | end_size = buf->used - pos; |
308 | |
|
309 | 0 | if (size < end_size) { |
310 | | /* delete from between */ |
311 | 0 | end_size -= size; |
312 | 0 | memmove(buf->w_buffer + pos, |
313 | 0 | buf->w_buffer + pos + size, end_size); |
314 | 0 | } else { |
315 | | /* delete the rest of the buffer */ |
316 | 0 | end_size = 0; |
317 | 0 | } |
318 | |
|
319 | 0 | buffer_set_used_size(_buf, pos + end_size); |
320 | 0 | } |
321 | | |
322 | | void buffer_replace(buffer_t *_buf, size_t pos, size_t size, |
323 | | const void *data, size_t data_size) |
324 | 0 | { |
325 | 0 | struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); |
326 | 0 | size_t end_size; |
327 | |
|
328 | 0 | if (pos >= buf->used) { |
329 | 0 | buffer_write(_buf, pos, data, data_size); |
330 | 0 | return; |
331 | 0 | } |
332 | 0 | end_size = buf->used - pos; |
333 | |
|
334 | 0 | if (size < end_size) { |
335 | 0 | end_size -= size; |
336 | 0 | if (data_size == 0) { |
337 | | /* delete from between */ |
338 | 0 | memmove(buf->w_buffer + pos, |
339 | 0 | buf->w_buffer + pos + size, end_size); |
340 | 0 | } else { |
341 | | /* insert */ |
342 | 0 | buffer_copy(_buf, pos + data_size, _buf, pos + size, |
343 | 0 | SIZE_MAX); |
344 | 0 | memcpy(buf->w_buffer + pos, data, data_size); |
345 | 0 | } |
346 | 0 | } else { |
347 | | /* overwrite the end */ |
348 | 0 | end_size = 0; |
349 | 0 | buffer_write(_buf, pos, data, data_size); |
350 | 0 | } |
351 | |
|
352 | 0 | buffer_set_used_size(_buf, pos + data_size + end_size); |
353 | 0 | } |
354 | | |
355 | | |
356 | | void buffer_write_zero(buffer_t *_buf, size_t pos, size_t data_size) |
357 | 0 | { |
358 | 0 | struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); |
359 | |
|
360 | 0 | buffer_check_limits(buf, pos, data_size); |
361 | 0 | memset(buf->w_buffer + pos, 0, data_size); |
362 | 0 | } |
363 | | |
364 | | void buffer_append_zero(buffer_t *_buf, size_t data_size) |
365 | 0 | { |
366 | 0 | struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); |
367 | | |
368 | | /* NOTE: When appending it's enough to check that the limits are |
369 | | valid, because the data is already guaranteed to be zero-filled. */ |
370 | 0 | buffer_check_limits(buf, buf->used, data_size); |
371 | 0 | } |
372 | | |
373 | | void buffer_insert_zero(buffer_t *_buf, size_t pos, size_t data_size) |
374 | 0 | { |
375 | 0 | struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); |
376 | |
|
377 | 0 | if (pos >= buf->used) |
378 | 0 | buffer_write_zero(_buf, pos, data_size); |
379 | 0 | else { |
380 | 0 | buffer_copy(_buf, pos + data_size, _buf, pos, SIZE_MAX); |
381 | 0 | memset(buf->w_buffer + pos, 0, data_size); |
382 | 0 | } |
383 | 0 | } |
384 | | |
385 | | void buffer_nul_terminate(buffer_t *_buf) |
386 | 0 | { |
387 | 0 | struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); |
388 | | |
389 | | /* +1 extra byte is always kept for NUL termination */ |
390 | 0 | i_assert(buf->used < buf->alloc); |
391 | | /* Buffer might not be writable, so check first if the NUL is |
392 | | already there. */ |
393 | 0 | if (((const char *)buf->r_buffer)[buf->used] != '\0') |
394 | 0 | buf->w_buffer[buf->used] = '\0'; |
395 | 0 | } |
396 | | |
397 | | void buffer_copy(buffer_t *_dest, size_t dest_pos, |
398 | | const buffer_t *_src, size_t src_pos, size_t copy_size) |
399 | 0 | { |
400 | 0 | struct real_buffer *dest = container_of(_dest, struct real_buffer, buf); |
401 | 0 | const struct real_buffer *src = |
402 | 0 | container_of(_src, const struct real_buffer, buf); |
403 | 0 | size_t max_size; |
404 | |
|
405 | 0 | i_assert(src_pos <= src->used); |
406 | | |
407 | 0 | max_size = src->used - src_pos; |
408 | 0 | if (copy_size > max_size) |
409 | 0 | copy_size = max_size; |
410 | |
|
411 | 0 | buffer_check_limits(dest, dest_pos, copy_size); |
412 | 0 | i_assert(src->r_buffer != NULL); |
413 | | |
414 | 0 | if (src == dest) { |
415 | 0 | memmove(dest->w_buffer + dest_pos, |
416 | 0 | CONST_PTR_OFFSET(src->r_buffer, src_pos), copy_size); |
417 | 0 | } else { |
418 | 0 | memcpy(dest->w_buffer + dest_pos, |
419 | 0 | CONST_PTR_OFFSET(src->r_buffer, src_pos), copy_size); |
420 | 0 | } |
421 | 0 | } |
422 | | |
423 | | void buffer_append_buf(buffer_t *dest, const buffer_t *src, |
424 | | size_t src_pos, size_t copy_size) |
425 | 0 | { |
426 | 0 | buffer_copy(dest, dest->used, src, src_pos, copy_size); |
427 | 0 | } |
428 | | |
429 | | void *buffer_get_space_unsafe(buffer_t *_buf, size_t pos, size_t size) |
430 | 0 | { |
431 | 0 | struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); |
432 | |
|
433 | 0 | buffer_check_limits(buf, pos, size); |
434 | 0 | return buf->w_buffer + pos; |
435 | 0 | } |
436 | | |
437 | | void *buffer_append_space_unsafe(buffer_t *buf, size_t size) |
438 | 0 | { |
439 | | /* NOTE: can't use buffer_check_append_limits() here because it doesn't |
440 | | guarantee that the buffer is zero-filled. */ |
441 | 0 | return buffer_get_space_unsafe(buf, buf->used, size); |
442 | 0 | } |
443 | | |
444 | | void *buffer_get_modifiable_data(const buffer_t *_buf, size_t *used_size_r) |
445 | 0 | { |
446 | 0 | const struct real_buffer *buf = |
447 | 0 | container_of(_buf, const struct real_buffer, buf); |
448 | |
|
449 | 0 | if (used_size_r != NULL) |
450 | 0 | *used_size_r = buf->used; |
451 | 0 | i_assert(buf->used == 0 || buf->w_buffer != NULL); |
452 | 0 | return buf->w_buffer; |
453 | 0 | } |
454 | | |
455 | | void buffer_set_used_size(buffer_t *_buf, size_t used_size) |
456 | 0 | { |
457 | 0 | struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); |
458 | |
|
459 | 0 | i_assert(used_size <= buf->alloc); |
460 | | |
461 | 0 | if (buf->used > buf->dirty) |
462 | 0 | buf->dirty = buf->used; |
463 | |
|
464 | 0 | buf->used = used_size; |
465 | 0 | } |
466 | | |
467 | | void buffer_clear_safe(buffer_t *_buf) |
468 | 0 | { |
469 | 0 | struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); |
470 | | |
471 | | /* Can be NULL with const data based buffers */ |
472 | 0 | if (buf->w_buffer != NULL) |
473 | 0 | safe_memset(buf->w_buffer, 0, I_MAX(buf->used, buf->dirty)); |
474 | 0 | buffer_clear(_buf); |
475 | 0 | } |
476 | | |
477 | | size_t buffer_get_size(const buffer_t *_buf) |
478 | 0 | { |
479 | 0 | const struct real_buffer *buf = |
480 | 0 | container_of(_buf, const struct real_buffer, buf); |
481 | |
|
482 | 0 | return buf->alloc; |
483 | 0 | } |
484 | | |
485 | | size_t buffer_get_writable_size(const buffer_t *_buf) |
486 | 0 | { |
487 | 0 | const struct real_buffer *buf = |
488 | 0 | container_of(_buf, const struct real_buffer, buf); |
489 | | |
490 | | /* Use buf->writable_size instead of buf->alloc to reserve +1 for |
491 | | str_c() NUL in buffer_check_limits(). Otherwise the caller might |
492 | | increase the buffer's alloc size unnecessarily when it just wants |
493 | | to access the entire buffer. */ |
494 | 0 | return buf->writable_size; |
495 | 0 | } |
496 | | |
497 | | size_t buffer_get_avail_size(const buffer_t *_buf) |
498 | 0 | { |
499 | 0 | const struct real_buffer *buf = |
500 | 0 | container_of(_buf, const struct real_buffer, buf); |
501 | |
|
502 | 0 | i_assert(buf->alloc >= buf->used); |
503 | 0 | return ((buf->dynamic ? SIZE_MAX : buf->alloc) - buf->used); |
504 | 0 | } |
505 | | |
506 | | bool buffer_cmp(const buffer_t *buf1, const buffer_t *buf2) |
507 | 0 | { |
508 | 0 | if (buf1->used != buf2->used) |
509 | 0 | return FALSE; |
510 | 0 | if (buf1->used == 0) |
511 | 0 | return TRUE; |
512 | | |
513 | 0 | return memcmp(buf1->data, buf2->data, buf1->used) == 0; |
514 | 0 | } |
515 | | |
516 | | void buffer_verify_pool(buffer_t *_buf) |
517 | 0 | { |
518 | 0 | const struct real_buffer *buf = |
519 | 0 | container_of(_buf, struct real_buffer, buf); |
520 | 0 | void *ret; |
521 | |
|
522 | 0 | if (buf->pool != NULL && buf->pool->datastack_pool && buf->alloc > 0) { |
523 | | /* this doesn't really do anything except verify the |
524 | | stack frame */ |
525 | 0 | ret = p_realloc(buf->pool, buf->w_buffer, |
526 | 0 | buf->alloc, buf->alloc); |
527 | 0 | i_assert(ret == buf->w_buffer); |
528 | 0 | } |
529 | 0 | } |
530 | | |
531 | | void ATTR_NO_SANITIZE_IMPLICIT_CONVERSION |
532 | | ATTR_NO_SANITIZE_INTEGER |
533 | | buffer_truncate_rshift_bits(buffer_t *buf, size_t bits) |
534 | 0 | { |
535 | | /* no-op if it's shorten than bits in any case.. */ |
536 | 0 | if (buf->used * 8 < bits) return; |
537 | | |
538 | 0 | if (bits > 0) { |
539 | | /* truncate it to closest byte boundary */ |
540 | 0 | size_t bytes = ((bits + 7) & ~(size_t)7) / 8; |
541 | | /* remaining bits */ |
542 | 0 | bits = bits % 8; |
543 | 0 | buffer_set_used_size(buf, I_MIN(bytes, buf->used)); |
544 | 0 | unsigned char *ptr = buffer_get_modifiable_data(buf, &bytes); |
545 | | /* right shift over byte array */ |
546 | 0 | if (bits > 0) { |
547 | 0 | for(size_t i=bytes-1;i>0;i--) |
548 | 0 | ptr[i] = (ptr[i]>>(8-bits)) + |
549 | 0 | ((ptr[i-1]&(0xff>>(bits)))<<bits); |
550 | 0 | ptr[0] = ptr[0]>>(8-bits); |
551 | 0 | } |
552 | 0 | } else { |
553 | 0 | buffer_set_used_size(buf, 0); |
554 | 0 | } |
555 | 0 | } |