Coverage for src/bluetooth_sig/gatt/characteristics/utils/bit_field_utils.py: 97%

183 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-30 00:10 +0000

1"""Bit field manipulation and flag handling utilities.""" 

2 

3from __future__ import annotations 

4 

5 

6class BitPositions: # pylint: disable=too-few-public-methods 

7 """Common bit position constants for flag manipulation.""" 

8 

9 # Common flag bit positions 

10 BIT_0 = 1 << 0 

11 BIT_1 = 1 << 1 

12 BIT_2 = 1 << 2 

13 BIT_3 = 1 << 3 

14 BIT_4 = 1 << 4 

15 BIT_5 = 1 << 5 

16 BIT_6 = 1 << 6 

17 BIT_7 = 1 << 7 

18 

19 

20class BitFieldUtils: # pylint: disable=too-many-public-methods 

21 """Utility class for bit field manipulation and flag handling.""" 

22 

23 # Common bit operation constants 

24 SINGLE_BIT_MASK = 1 

25 DEFAULT_BIT_WIDTH = 32 

26 

27 @staticmethod 

28 def extract_bit_field(value: int, start_bit: int, num_bits: int) -> int: 

29 """Extract a bit field from an integer value.""" 

30 if num_bits <= 0 or start_bit < 0: 

31 raise ValueError("Invalid bit field parameters") 

32 mask = (1 << num_bits) - 1 

33 return (value >> start_bit) & mask 

34 

35 @staticmethod 

36 def set_bit_field(value: int, field_value: int, start_bit: int, num_bits: int) -> int: 

37 """Set a bit field in an integer value.""" 

38 if num_bits <= 0 or start_bit < 0: 

39 raise ValueError("Invalid bit field parameters") 

40 mask = (BitFieldUtils.SINGLE_BIT_MASK << num_bits) - 1 

41 if field_value > mask: 

42 raise ValueError(f"Field value {field_value} exceeds {num_bits}-bit capacity") 

43 # Clear the field and set new value 

44 value &= ~(mask << start_bit) 

45 value |= (field_value & mask) << start_bit 

46 return value 

47 

48 @staticmethod 

49 def create_bitmask(start_bit: int, num_bits: int) -> int: 

50 """Create a bitmask for a specific bit field range.""" 

51 if num_bits <= 0 or start_bit < 0: 

52 raise ValueError("Invalid bitmask parameters") 

53 mask = (1 << num_bits) - 1 

54 return mask << start_bit 

55 

56 @staticmethod 

57 def test_bit(value: int, bit_position: int) -> bool: 

58 """Test if a specific bit is set in the value.""" 

59 if bit_position < 0: 

60 raise ValueError("Bit position must be non-negative") 

61 return bool(value & (BitFieldUtils.SINGLE_BIT_MASK << bit_position)) 

62 

63 @staticmethod 

64 def test_bits_any(value: int, bitmask: int) -> bool: 

65 """Test if any bits in the bitmask are set in the value.""" 

66 return bool(value & bitmask) 

67 

68 @staticmethod 

69 def test_bits_all(value: int, bitmask: int) -> bool: 

70 """Test if all bits in the bitmask are set in the value.""" 

71 return (value & bitmask) == bitmask 

72 

73 @staticmethod 

74 def set_bit(value: int, bit_position: int) -> int: 

75 """Set a specific bit in the value.""" 

76 if bit_position < 0: 

77 raise ValueError("Bit position must be non-negative") 

78 return value | (BitFieldUtils.SINGLE_BIT_MASK << bit_position) 

79 

80 @staticmethod 

81 def clear_bit(value: int, bit_position: int) -> int: 

82 """Clear a specific bit in the value.""" 

83 if bit_position < 0: 

84 raise ValueError("Bit position must be non-negative") 

85 return value & ~(BitFieldUtils.SINGLE_BIT_MASK << bit_position) 

86 

87 @staticmethod 

88 def toggle_bit(value: int, bit_position: int) -> int: 

89 """Toggle a specific bit in the value.""" 

90 if bit_position < 0: 

91 raise ValueError("Bit position must be non-negative") 

92 return value ^ (BitFieldUtils.SINGLE_BIT_MASK << bit_position) 

93 

94 @staticmethod 

95 def extract_bits(value: int, bitmask: int) -> int: 

96 """Extract bits from value using a bitmask.""" 

97 return value & bitmask 

98 

99 @staticmethod 

100 def set_bits(value: int, bitmask: int) -> int: 

101 """Set all bits specified in the bitmask.""" 

102 return value | bitmask 

103 

104 @staticmethod 

105 def clear_bits(value: int, bitmask: int) -> int: 

106 """Clear all bits specified in the bitmask.""" 

107 return value & ~bitmask 

108 

109 @staticmethod 

110 def toggle_bits(value: int, bitmask: int) -> int: 

111 """Toggle all bits specified in the bitmask.""" 

112 return value ^ bitmask 

113 

114 @staticmethod 

115 def count_set_bits(value: int) -> int: 

116 """Count the number of set bits in the value.""" 

117 count = 0 

118 while value: 

119 count += value & 1 

120 value >>= 1 

121 return count 

122 

123 @staticmethod 

124 def get_bit_positions(value: int) -> list[int]: 

125 """Get a list of positions of all set bits in the value.""" 

126 positions: list[int] = [] 

127 bit_pos = 0 

128 while value: 

129 if value & 1: 

130 positions.append(bit_pos) 

131 value >>= 1 

132 bit_pos += 1 

133 return positions 

134 

135 @staticmethod 

136 def find_first_set_bit(value: int) -> int | None: 

137 """Find the position of the first (least significant) set bit.""" 

138 if value == 0: 

139 return None 

140 position = 0 

141 while (value & 1) == 0: 

142 value >>= 1 

143 position += 1 

144 return position 

145 

146 @staticmethod 

147 def find_last_set_bit(value: int) -> int | None: 

148 """Find the position of the last (most significant) set bit.""" 

149 if value == 0: 

150 return None 

151 position = 0 

152 while value > 1: 

153 value >>= 1 

154 position += 1 

155 return position 

156 

157 @staticmethod 

158 def reverse_bits(value: int, bit_width: int = DEFAULT_BIT_WIDTH) -> int: 

159 """Reverse the bits in a value within the specified bit width.""" 

160 result = 0 

161 for i in range(bit_width): 

162 if value & (1 << i): 

163 result |= 1 << (bit_width - 1 - i) 

164 return result 

165 

166 @staticmethod 

167 def calculate_parity(value: int) -> int: 

168 """Calculate the parity (even/odd) of set bits. 

169 

170 Returns 0 for even, 1 for odd. 

171 """ 

172 parity = 0 

173 while value: 

174 parity ^= value & 1 

175 value >>= 1 

176 return parity 

177 

178 @staticmethod 

179 def validate_bit_field_range(start_bit: int, num_bits: int, total_bits: int = DEFAULT_BIT_WIDTH) -> bool: 

180 """Validate that a bit field range is within bounds.""" 

181 return start_bit >= 0 and num_bits > 0 and start_bit + num_bits <= total_bits 

182 

183 @staticmethod 

184 def copy_bit_field(source: int, dest: int, source_start: int, dest_start: int, num_bits: int) -> int: 

185 """Copy a bit field from source to destination.""" 

186 field_value = BitFieldUtils.extract_bit_field(source, source_start, num_bits) 

187 return BitFieldUtils.set_bit_field(dest, field_value, dest_start, num_bits) 

188 

189 @staticmethod 

190 def shift_bit_field_left(value: int, start_bit: int, num_bits: int, shift_amount: int) -> int: 

191 """Shift a bit field left within the value.""" 

192 if shift_amount <= 0: 

193 return value 

194 field_value = BitFieldUtils.extract_bit_field(value, start_bit, num_bits) 

195 # Clear the original field 

196 value = BitFieldUtils.set_bit_field(value, 0, start_bit, num_bits) 

197 # Set the shifted field 

198 new_start = start_bit + shift_amount 

199 return BitFieldUtils.set_bit_field(value, field_value, new_start, num_bits) 

200 

201 @staticmethod 

202 def shift_bit_field_right(value: int, start_bit: int, num_bits: int, shift_amount: int) -> int: 

203 """Shift a bit field right within the value.""" 

204 if shift_amount <= 0: 

205 return value 

206 field_value = BitFieldUtils.extract_bit_field(value, start_bit, num_bits) 

207 # Clear the original field 

208 value = BitFieldUtils.set_bit_field(value, 0, start_bit, num_bits) 

209 # Set the shifted field 

210 new_start = max(0, start_bit - shift_amount) 

211 return BitFieldUtils.set_bit_field(value, field_value, new_start, num_bits) 

212 

213 @staticmethod 

214 def merge_bit_fields(*fields: tuple[int, int, int]) -> int: 

215 """Merge multiple bit fields into a single value. 

216 

217 Args: 

218 fields: Tuples of (field_value, start_bit, num_bits) 

219 

220 """ 

221 result = 0 

222 for field_value, start_bit, num_bits in fields: 

223 result = BitFieldUtils.set_bit_field(result, field_value, start_bit, num_bits) 

224 return result 

225 

226 @staticmethod 

227 def split_bit_field(value: int, *field_specs: tuple[int, int]) -> list[int]: 

228 """Split a value into multiple bit fields. 

229 

230 Args: 

231 value: The value to split 

232 field_specs: Tuples of (start_bit, num_bits) for each field 

233 

234 Returns: 

235 List of extracted field values 

236 

237 """ 

238 return [BitFieldUtils.extract_bit_field(value, start_bit, num_bits) for start_bit, num_bits in field_specs] 

239 

240 @staticmethod 

241 def compare_bit_fields(value1: int, value2: int, start_bit: int, num_bits: int) -> int: 

242 """Compare bit fields between two values. 

243 

244 Returns -1, 0, or 1. 

245 """ 

246 field1 = BitFieldUtils.extract_bit_field(value1, start_bit, num_bits) 

247 field2 = BitFieldUtils.extract_bit_field(value2, start_bit, num_bits) 

248 if field1 < field2: 

249 return -1 

250 if field1 > field2: 

251 return 1 

252 return 0 

253 

254 @staticmethod 

255 def rotate_left(value: int, positions: int, bit_width: int = DEFAULT_BIT_WIDTH) -> int: 

256 """Rotate bits left by the specified number of positions.""" 

257 positions = positions % bit_width 

258 if positions == 0: 

259 return value 

260 mask = (1 << bit_width) - 1 

261 value &= mask 

262 return ((value << positions) | (value >> (bit_width - positions))) & mask 

263 

264 @staticmethod 

265 def rotate_right(value: int, positions: int, bit_width: int = DEFAULT_BIT_WIDTH) -> int: 

266 """Rotate bits right by the specified number of positions.""" 

267 positions = positions % bit_width 

268 if positions == 0: 

269 return value 

270 mask = (1 << bit_width) - 1 

271 value &= mask 

272 return ((value >> positions) | (value << (bit_width - positions))) & mask 

273 

274 @staticmethod 

275 def extract_bit_field_from_mask(value: int, mask: int, shift: int) -> int: 

276 """Extract a bit field using a mask and shift amount. 

277 

278 Args: 

279 value: The value to extract from 

280 mask: The base mask (e.g., 0x0F for 4 bits) 

281 shift: How many bits to shift the mask left 

282 

283 Returns: 

284 The extracted bit field value 

285 

286 """ 

287 shifted_mask = mask << shift 

288 return BitFieldUtils.extract_bits(value, shifted_mask) >> shift