blob: b03d163b007fbeef67e0ea754dd541d371955692 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//! Bitmap helpers for the two — deliberately separate — bit conventions in
//! the IoTDB wire protocol (spec gotcha #4):
//!
//! * **Write side** (tablet null bitmaps, §3.3): LSB-first, bit=1 ⇒ null.
//! * **Read side** (TsBlock null indicators and BOOLEAN value arrays, §5.3):
//! MSB-first, bit=1 ⇒ set (null, or `true` for booleans).
//!
//! Do not mix them.
/// Packs `bits` LSB-first into `ceil(len/8)` bytes: bit `i` → byte `i >> 3`,
/// mask `1 << (i & 7)`. Used for tablet write-side null bitmaps.
pub fn pack_bits_lsb_first(bits: &[bool]) -> Vec<u8> {
let mut out = vec![0u8; bits.len().div_ceil(8)];
for (i, &b) in bits.iter().enumerate() {
if b {
out[i >> 3] |= 1 << (i & 7);
}
}
out
}
/// Unpacks `count` bits MSB-first from `bytes`: bit `i` → byte `i >> 3`,
/// mask `0x80 >> (i & 7)`. Used for TsBlock read-side null indicators and
/// BOOLEAN value arrays. Returns `None` if `bytes` is too short.
pub fn unpack_bits_msb_first(bytes: &[u8], count: usize) -> Option<Vec<bool>> {
if bytes.len() < count.div_ceil(8) {
return None;
}
Some(
(0..count)
.map(|i| bytes[i >> 3] & (0x80 >> (i & 7)) != 0)
.collect(),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pack_lsb_first_bit_positions() {
// Position 0 → 0x01, position 7 → 0x80.
assert_eq!(pack_bits_lsb_first(&[true]), vec![0x01]);
let mut bits = [false; 8];
bits[7] = true;
assert_eq!(pack_bits_lsb_first(&bits), vec![0x80]);
}
#[test]
fn pack_lsb_first_multi_byte_and_padding() {
// 9 bits → 2 bytes; positions 0 and 8 set.
let mut bits = [false; 9];
bits[0] = true;
bits[8] = true;
assert_eq!(pack_bits_lsb_first(&bits), vec![0x01, 0x01]);
// Padding bits stay 0.
assert_eq!(pack_bits_lsb_first(&[false; 3]), vec![0x00]);
assert_eq!(pack_bits_lsb_first(&[]), Vec::<u8>::new());
}
#[test]
fn unpack_msb_first_bit_positions() {
// Position 0 → 0x80, position 7 → 0x01.
assert_eq!(unpack_bits_msb_first(&[0x80], 1), Some(vec![true]));
let bits = unpack_bits_msb_first(&[0x01], 8).unwrap();
assert!(bits[7]);
assert!(bits[..7].iter().all(|&b| !b));
}
#[test]
fn unpack_msb_first_multi_byte() {
// 0b10100000 0b01000000 over 10 bits → positions 0, 2, 9 set.
let bits = unpack_bits_msb_first(&[0xA0, 0x40], 10).unwrap();
let set: Vec<usize> = (0..10).filter(|&i| bits[i]).collect();
assert_eq!(set, vec![0, 2, 9]);
}
#[test]
fn unpack_msb_first_short_buffer_is_none() {
assert_eq!(unpack_bits_msb_first(&[0xFF], 9), None);
assert_eq!(unpack_bits_msb_first(&[], 0), Some(vec![]));
}
#[test]
fn the_two_conventions_differ() {
// Same logical bit vector, different byte images — guards against
// accidentally unifying the two implementations.
let mut bits = [false; 8];
bits[0] = true;
let written = pack_bits_lsb_first(&bits); // 0x01
let read_back = unpack_bits_msb_first(&written, 8).unwrap();
assert_ne!(read_back, bits.to_vec());
}
}