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.
-
#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.
-
#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_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 |
# 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 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 |
#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
178 179 180 181 182 |
# File 'lib/cabriolet/binary/bitstream.rb', line 178 def byte_align discard_bits = @bits_left % 8 @bit_buffer >>= discard_bits @bits_left -= discard_bits end |
#peek_bits(num_bits) ⇒ Integer
Peek at bits without consuming them
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/cabriolet/binary/bitstream.rb', line 188 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
39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/cabriolet/binary/bitstream.rb', line 39 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
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/cabriolet/binary/bitstream.rb', line 253 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).
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/cabriolet/binary/bitstream.rb', line 148 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_uint16_le ⇒ Integer
Read a 16-bit little-endian value
274 275 276 |
# File 'lib/cabriolet/binary/bitstream.rb', line 274 def read_uint16_le read_bits(16) end |
#read_uint32_le ⇒ Integer
Read a 32-bit little-endian value
281 282 283 284 285 |
# File 'lib/cabriolet/binary/bitstream.rb', line 281 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
290 291 292 293 294 295 296 |
# File 'lib/cabriolet/binary/bitstream.rb', line 290 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
244 245 246 247 |
# File 'lib/cabriolet/binary/bitstream.rb', line 244 def skip_bits(num_bits) read_bits(num_bits) nil end |