class BinData::IO
interface for BinData objects to use when accessing the IO.
A wrapper around an IO object. The wrapper provides a consistent
def accumulate_big_endian_bits
def accumulate_big_endian_bits byte = @raw_io.read(1) raise EOFError, "End of file reached" if byte.nil? byte = byte.unpack('C').at(0) & 0xff @rval = (@rval << 8) | byte @rnbits += 8 end
def accumulate_little_endian_bits
def accumulate_little_endian_bits byte = @raw_io.read(1) raise EOFError, "End of file reached" if byte.nil? byte = byte.unpack('C').at(0) & 0xff @rval = @rval | (byte << @rnbits) @rnbits += 8 end
def flushbits
def flushbits raise "Internal state error nbits = #{@wnbits}" if @wnbits >= 8 if @wnbits > 0 writebits(0, 8 - @wnbits, @wendian) end end
def initialize(io)
readbits(6), readbits(5) #=> [543210, a9876]
In little endian format:
readbits(6), readbits(5) #=> [765432, 10fed]
In big endian format:
B B B B
S 76543210 S S fedcba98 S
M byte1 L M byte2 L
The IO can handle bitstreams in either big or little endian format.
+io+ is a string it will be automatically wrapped in an StringIO object.
stream position and #seek if setting the current stream position. If
for reading, #write if used for writing, #pos if reading the current
Create a new IO wrapper around +io+. +io+ must support #read if used
def initialize(io) raise ArgumentError, "io must not be a BinData::IO" if BinData::IO === io # wrap strings in a StringIO if io.respond_to?(:to_str) io = StringIO.new(io) end @raw_io = io # initial stream position if stream supports positioning @initial_pos = positioning_supported? ? io.pos : 0 # bits when reading @rnbits = 0 @rval = 0 @rendian = nil # bits when writing @wnbits = 0 @wval = 0 @wendian = nil end
def mask(nbits)
def mask(nbits) (1 << nbits) - 1 end
def offset
Returns the current offset of the io stream. The exact value of
def offset if positioning_supported? @raw_io.pos - @initial_pos else 0 end end
def positioning_supported?
def positioning_supported? unless defined? @positioning_supported @positioning_supported = begin @raw_io.pos true rescue NoMethodError, Errno::ESPIPE false end end @positioning_supported end
def read_all_bytes
def read_all_bytes raise "Internal state error nbits = #{@rnbits}" if @rnbits >= 8 @rnbits = 0 @rval = 0 @raw_io.read end
def read_big_endian_bits(nbits)
def read_big_endian_bits(nbits) while @rnbits < nbits accumulate_big_endian_bits end val = (@rval >> (@rnbits - nbits)) & mask(nbits) @rnbits -= nbits @rval &= mask(@rnbits) val end
def read_little_endian_bits(nbits)
def read_little_endian_bits(nbits) while @rnbits < nbits accumulate_little_endian_bits end val = @rval & mask(nbits) @rnbits -= nbits @rval >>= nbits val end
def readbits(nbits, endian)
Reads exactly +nbits+ bits from the stream. +endian+ specifies whether
def readbits(nbits, endian) if @rendian != endian # don't mix bits of differing endian @rnbits = 0 @rval = 0 @rendian = endian end if endian == :big read_big_endian_bits(nbits) else read_little_endian_bits(nbits) end end
def readbytes(n)
If the data read is nil an EOFError is raised.
Reads exactly +n+ bytes from +io+.
def readbytes(n) raise "Internal state error nbits = #{@rnbits}" if @rnbits >= 8 @rnbits = 0 @rval = 0 str = @raw_io.read(n) raise EOFError, "End of file reached" if str.nil? raise IOError, "data truncated" if str.size < n str end
def seekbytes(n)
def seekbytes(n) @raw_io.seek(n, ::IO::SEEK_CUR) end
def write_big_endian_bits(val, nbits)
def write_big_endian_bits(val, nbits) while nbits > 0 bits_req = 8 - @wnbits if nbits >= bits_req msb_bits = (val >> (nbits - bits_req)) & mask(bits_req) nbits -= bits_req val &= mask(nbits) @wval = (@wval << bits_req) | msb_bits @raw_io.write(@wval.chr) @wval = 0 @wnbits = 0 else @wval = (@wval << nbits) | val @wnbits += nbits nbits = 0 end end end
def write_little_endian_bits(val, nbits)
def write_little_endian_bits(val, nbits) while nbits > 0 bits_req = 8 - @wnbits if nbits >= bits_req lsb_bits = val & mask(bits_req) nbits -= bits_req val >>= bits_req @wval = @wval | (lsb_bits << @wnbits) @raw_io.write(@wval.chr) @wval = 0 @wnbits = 0 else @wval = @wval | (val << @wnbits) @wnbits += nbits nbits = 0 end end end
def writebits(val, nbits, endian)
Writes +nbits+ bits from +val+ to the stream. +endian+ specifies whether
def writebits(val, nbits, endian) if @wendian != endian # don't mix bits of differing endian flushbits @wendian = endian end clamped_val = val & mask(nbits) if endian == :big write_big_endian_bits(clamped_val, nbits) else write_little_endian_bits(clamped_val, nbits) end end
def writebytes(str)
def writebytes(str) flushbits @raw_io.write(str) end