// Copyright (c) Meta Platforms, Inc. and affiliates. // SPDX-License-Identifier: LGPL-2.1-or-later #include #include #include "serialize.h" static inline uint8_t copy_bits_step(const uint8_t *s, unsigned int src_bit_offset, unsigned int bit_size, unsigned int dst_bit_offset, bool lsb0) { uint8_t result; if (lsb0) { result = s[0] >> src_bit_offset; if (bit_size > 8 - src_bit_offset) result |= s[1] << (8 - src_bit_offset); result <<= dst_bit_offset; } else { result = s[0] << src_bit_offset; if (bit_size > 8 - src_bit_offset) result |= s[1] >> (8 - src_bit_offset); result >>= dst_bit_offset; } return result; } void copy_bits(void *dst, unsigned int dst_bit_offset, const void *src, unsigned int src_bit_offset, uint64_t bit_size, bool lsb0) { assert(dst_bit_offset < 8); assert(src_bit_offset < 8); if (bit_size == 0) return; uint8_t *d = dst; const uint8_t *s = src; uint64_t dst_last_bit = dst_bit_offset + bit_size - 1; uint8_t dst_first_mask = copy_bits_first_mask(dst_bit_offset, lsb0); uint8_t dst_last_mask = copy_bits_last_mask(dst_last_bit, lsb0); if (dst_bit_offset == src_bit_offset) { /* * In the common case that the source and destination have the * same offset, we can use memcpy(), preserving bits at the * start and/or end if necessary. */ uint8_t first_byte = d[0]; uint8_t last_byte = d[dst_last_bit / 8]; memcpy(d, s, dst_last_bit / 8 + 1); if (dst_bit_offset != 0) { d[0] = ((first_byte & ~dst_first_mask) | (d[0] & dst_first_mask)); } if (dst_last_bit % 8 != 7) { d[dst_last_bit / 8] = ((last_byte & ~dst_last_mask) | (d[dst_last_bit / 8] & dst_last_mask)); } } else if (bit_size <= 8 - dst_bit_offset) { /* Destination is only one byte. */ uint8_t dst_mask = dst_first_mask & dst_last_mask; d[0] = ((d[0] & ~dst_mask) | (copy_bits_step(&s[0], src_bit_offset, bit_size, dst_bit_offset, lsb0) & dst_mask)); } else { /* Destination is two or more bytes. */ d[0] = ((d[0] & ~dst_first_mask) | (copy_bits_step(&s[0], src_bit_offset, 8 - dst_bit_offset, dst_bit_offset, lsb0) & dst_first_mask)); src_bit_offset += 8 - dst_bit_offset; size_t si = src_bit_offset / 8; src_bit_offset %= 8; size_t di = 1; while (di < dst_last_bit / 8) { d[di] = copy_bits_step(&s[si], src_bit_offset, 8, 0, lsb0); di++; si++; } d[di] = ((d[di] & ~dst_last_mask) | (copy_bits_step(&s[si], src_bit_offset, dst_last_bit % 8 + 1, 0, lsb0) & dst_last_mask)); } } void serialize_bits(void *buf, uint64_t bit_offset, uint64_t uvalue, uint8_t bit_size, bool little_endian) { uint8_t *p; size_t bits, size; unsigned char tmp[9]; uint8_t first_mask, last_mask; assert(bit_size > 0); assert(bit_size <= 64); p = (uint8_t *)buf + bit_offset / 8; bit_offset %= 8; bits = bit_offset + bit_size; size = (bits + 7) / 8; if (little_endian) { if (size > sizeof(uvalue)) tmp[8] = uvalue >> (64 - bit_offset); uvalue = htole64(uvalue << bit_offset); memcpy(tmp, &uvalue, sizeof(uvalue)); /* bit_offset least significant bits. */ first_mask = (1 << bit_offset) - 1; /* 8 - (bit_offset + bit_size) % 8 most significant bits. */ last_mask = 0xff00 >> -bits % 8; } else { unsigned int shift; shift = -bits % 8; if (size > sizeof(uvalue)) { tmp[0] = uvalue >> (64 - shift); uvalue = htobe64(uvalue << shift); memcpy(&tmp[1], &uvalue, sizeof(uvalue)); } else { uvalue = htobe64(uvalue << (64 - bits)); memcpy(&tmp[0], &uvalue, sizeof(uvalue)); } /* bit_offset most significant bits. */ first_mask = 0xff00 >> bit_offset; /* 8 - (bit_offset + bit_size) % 8 least significant bits. */ last_mask = (1 << shift) - 1; } if (size == 1) { p[0] = (p[0] & (first_mask | last_mask)) | tmp[0]; } else { p[0] = (p[0] & first_mask) | tmp[0]; memcpy(p + 1, tmp + 1, size - 2); p[size - 1] = (p[size - 1] & last_mask) | tmp[size - 1]; } } uint64_t deserialize_bits(const void *buf, uint64_t bit_offset, uint8_t bit_size, bool little_endian) { const uint8_t *p; size_t bits, size; uint64_t ret = 0; assert(bit_size > 0); assert(bit_size <= 64); p = (const uint8_t *)buf + bit_offset / 8; bit_offset %= 8; bits = bit_offset + bit_size; size = (bits + 7) / 8; if (little_endian) { memcpy(&ret, p, min(size, sizeof(ret))); ret = le64toh(ret) >> bit_offset; if (size > sizeof(ret)) ret |= (uint64_t)p[8] << (64 - bit_offset); } else { unsigned int shift; if (size > sizeof(ret)) memcpy(&ret, &p[1], sizeof(ret)); else memcpy((char *)(&ret + 1) - size, p, size); shift = -bits % 8; ret = be64toh(ret) >> shift; if (size > sizeof(ret)) ret |= (uint64_t)p[0] << (64 - shift); } return truncate_unsigned(ret, bit_size); }