Class: Cabriolet::Binary::Bitstream
- Inherits:
-
Object
- Object
- Cabriolet::Binary::Bitstream
- Defined in:
- lib/cabriolet/binary/bitstream.rb
Overview
Bitstream provides bit-level I/O operations for reading compressed data
Instance Attribute Summary collapse
-
#bit_order ⇒ Object
readonly
Returns the value of attribute bit_order.
-
#bits_left ⇒ Object
readonly
Returns the value of attribute bits_left.
-
#buffer_size ⇒ Object
readonly
Returns the value of attribute buffer_size.
-
#handle ⇒ Object
readonly
Returns the value of attribute handle.
-
#io_system ⇒ Object
readonly
Returns the value of attribute io_system.
Instance Method Summary collapse
-
#byte_align ⇒ void
Align to the next byte boundary.
-
#ensure_bits(num_bits) ⇒ void
Ensure at least num_bits are available in the bit buffer.
-
#flush_bit_buffer ⇒ void
Flush the bit buffer entirely (discard all remaining bits).
-
#initialize(io_system, handle, buffer_size = Cabriolet.default_buffer_size, bit_order: :lsb, salvage: false) ⇒ Bitstream
constructor
Initialize a new bitstream.
-
#peek_bits(num_bits) ⇒ Integer
Peek at bits without consuming them.
-
#read_bits(num_bits) ⇒ Integer
Read specified number of bits from the stream.
-
#read_bits_be(num_bits) ⇒ Integer
Read bits in big-endian (MSB first) order.
-
#read_byte ⇒ Integer?
Read a single byte from the input.
-
#read_raw_byte ⇒ Integer
Read a raw byte directly from the input, bypassing the bit buffer.
-
#read_uint16_le ⇒ Integer
Read a 16-bit little-endian value.
-
#read_uint32_le ⇒ Integer
Read a 32-bit little-endian value.
-
#reset ⇒ void
Reset the bitstream state.
-
#skip_bits(num_bits) ⇒ void
Skip specified number of bits.
Constructor Details
#initialize(io_system, handle, buffer_size = Cabriolet.default_buffer_size, bit_order: :lsb, salvage: false) ⇒ Bitstream
Initialize a new bitstream
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/cabriolet/binary/bitstream.rb', line 16 def initialize(io_system, handle, buffer_size = Cabriolet.default_buffer_size, bit_order: :lsb, salvage: false) @io_system = io_system @handle = handle @buffer_size = buffer_size @bit_order = bit_order @salvage = salvage @buffer = "" @buffer_pos = 0 @bit_buffer = 0 @bits_left = 0 @input_end = false # Track EOF state (matches libmspack's input_end flag) # For MSB mode, we need to know the bit width of the buffer # Ruby integers are arbitrary precision, so we use 32 bits as standard @bitbuf_width = 32 # Cache ENV lookups once at initialization @debug_bitstream = ENV.fetch("DEBUG_BITSTREAM", nil) end |
Instance Attribute Details
#bit_order ⇒ Object (readonly)
Returns the value of attribute bit_order.
7 8 9 |
# File 'lib/cabriolet/binary/bitstream.rb', line 7 def bit_order @bit_order end |
#bits_left ⇒ Object (readonly)
Returns the value of attribute bits_left.
7 8 9 |
# File 'lib/cabriolet/binary/bitstream.rb', line 7 def bits_left @bits_left end |
#buffer_size ⇒ Object (readonly)
Returns the value of attribute buffer_size.
7 8 9 |
# File 'lib/cabriolet/binary/bitstream.rb', line 7 def buffer_size @buffer_size end |
#handle ⇒ Object (readonly)
Returns the value of attribute handle.
7 8 9 |
# File 'lib/cabriolet/binary/bitstream.rb', line 7 def handle @handle end |
#io_system ⇒ Object (readonly)
Returns the value of attribute io_system.
7 8 9 |
# File 'lib/cabriolet/binary/bitstream.rb', line 7 def io_system @io_system end |
Instance Method Details
#byte_align ⇒ void
This method returns an undefined value.
Align to the next byte boundary
203 204 205 206 207 208 209 210 211 212 |
# File 'lib/cabriolet/binary/bitstream.rb', line 203 def byte_align discard_bits = @bits_left % 8 if @bit_order == :msb # MSB mode: valid bits are at the left (high) end, shift left to discard @bit_buffer = (@bit_buffer << discard_bits) & ((1 << @bitbuf_width) - 1) else @bit_buffer >>= discard_bits end @bits_left -= discard_bits end |
#ensure_bits(num_bits) ⇒ void
This method returns an undefined value.
Ensure at least num_bits are available in the bit buffer. Reads from input if needed. Used for alignment operations.
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/cabriolet/binary/bitstream.rb', line 183 def ensure_bits(num_bits) if @bit_order == :msb while @bits_left < num_bits word = read_msb_word @bit_buffer |= (word << (@bitbuf_width - 16 - @bits_left)) @bits_left += 16 end else while @bits_left < num_bits byte = read_byte byte = 0 if byte.nil? @bit_buffer |= (byte << @bits_left) @bits_left += 8 end end end |
#flush_bit_buffer ⇒ void
This method returns an undefined value.
Flush the bit buffer entirely (discard all remaining bits). Per libmspack lzxd.c: used when transitioning to raw byte reading for uncompressed blocks. Sets bits_left=0 and bit_buffer=0.
219 220 221 222 |
# File 'lib/cabriolet/binary/bitstream.rb', line 219 def flush_bit_buffer @bit_buffer = 0 @bits_left = 0 end |
#peek_bits(num_bits) ⇒ Integer
Peek at bits without consuming them
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/cabriolet/binary/bitstream.rb', line 238 def peek_bits(num_bits) if num_bits < 1 || num_bits > 32 raise ArgumentError, "Can only peek 1-32 bits at a time" end if @bit_order == :msb # Ensure we have enough bits while @bits_left < num_bits # Read 2 bytes at a time (little-endian), like libmspack byte0 = read_byte if byte0.nil? # At EOF: break and work with remaining bits break end byte1 = read_byte byte1 = 0 if byte1.nil? # Combine as little-endian 16-bit value word = byte0 | (byte1 << 8) # INJECT_BITS (MSB): inject at the left side @bit_buffer |= (word << (@bitbuf_width - 16 - @bits_left)) @bits_left += 16 end # PEEK_BITS (MSB): extract from the left # If we have fewer than num_bits available, result may be incorrect # but this matches EOF handling behavior @bit_buffer >> (@bitbuf_width - num_bits) else # Ensure we have enough bits (LSB mode) while @bits_left < num_bits byte = read_byte if byte.nil? # At EOF: pad remaining bits with zeros and continue # This matches libmspack behavior where peek can use partial bits # The missing high bits are implicitly 0 break end @bit_buffer |= (byte << @bits_left) @bits_left += 8 end # Extract num_bits from bit_buffer # If we have fewer than num_bits, the high bits will be 0 @bit_buffer & ((1 << num_bits) - 1) end end |
#read_bits(num_bits) ⇒ Integer
Read specified number of bits from the stream
42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/cabriolet/binary/bitstream.rb', line 42 def read_bits(num_bits) if num_bits < 1 || num_bits > 32 raise ArgumentError, "Can only read 1-32 bits at a time" end if @bit_order == :msb read_bits_msb(num_bits) else read_bits_lsb(num_bits) end end |
#read_bits_be(num_bits) ⇒ Integer
Read bits in big-endian (MSB first) order
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 |
# File 'lib/cabriolet/binary/bitstream.rb', line 303 def read_bits_be(num_bits) result = 0 full_bytes = num_bits / 8 remaining_bits = num_bits % 8 # Read full bytes first (more efficient than bit-by-bit) full_bytes.times do result = (result << 8) | read_bits(8) end # Read remaining bits if remaining_bits.positive? result = (result << remaining_bits) | read_bits(remaining_bits) end result end |
#read_byte ⇒ Integer?
Read a single byte from the input
Per libmspack readbits.h: On first EOF, we pad with zeros. On second EOF, we raise an error (unless salvage mode).
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/cabriolet/binary/bitstream.rb', line 151 def read_byte if @buffer_pos >= @buffer.bytesize @buffer = @io_system.read(@handle, @buffer_size) @buffer_pos = 0 if @buffer.empty? # Hit EOF - check if this is first or second EOF if @input_end # Second EOF: raise error unless salvage mode unless @salvage raise DecompressionError, "Unexpected end of input stream" end # In salvage mode, keep returning nil else # First EOF: signal to pad with zeros (return nil) @input_end = true end return nil end end byte = @buffer.getbyte(@buffer_pos) @buffer_pos += 1 byte end |
#read_raw_byte ⇒ Integer
Read a raw byte directly from the input, bypassing the bit buffer. Per libmspack lzxd.c: uncompressed block headers and data are read directly from the input pointer (i_ptr), not through the bitstream. Call flush_bit_buffer first to discard any residual bits.
230 231 232 |
# File 'lib/cabriolet/binary/bitstream.rb', line 230 def read_raw_byte read_byte || 0 end |
#read_uint16_le ⇒ Integer
Read a 16-bit little-endian value
324 325 326 |
# File 'lib/cabriolet/binary/bitstream.rb', line 324 def read_uint16_le read_bits(16) end |
#read_uint32_le ⇒ Integer
Read a 32-bit little-endian value
331 332 333 334 335 |
# File 'lib/cabriolet/binary/bitstream.rb', line 331 def read_uint32_le low = read_bits(16) high = read_bits(16) (high << 16) | low end |
#reset ⇒ void
This method returns an undefined value.
Reset the bitstream state
340 341 342 343 344 345 346 |
# File 'lib/cabriolet/binary/bitstream.rb', line 340 def reset @buffer = "" @buffer_pos = 0 @bit_buffer = 0 @bits_left = 0 @io_system.seek(@handle, 0, Constants::SEEK_START) end |
#skip_bits(num_bits) ⇒ void
This method returns an undefined value.
Skip specified number of bits
294 295 296 297 |
# File 'lib/cabriolet/binary/bitstream.rb', line 294 def skip_bits(num_bits) read_bits(num_bits) nil end |