Class: BinData::IO

Inherits:
Object
  • Object
show all
Defined in:
lib/bindata/io.rb

Overview

A wrapper around an IO object. The wrapper provides a consistent interface for BinData objects to use when accessing the IO.

Defined Under Namespace

Classes: Unseekable

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(io) ⇒ IO

Create a new IO wrapper around io. io must support #read if used for reading, #write if used for writing, #pos if reading the current stream position and #seek if setting the current stream position. If io is a string it will be automatically wrapped in an StringIO object.

The IO can handle bitstreams in either big or little endian format.

M  byte1   L      M  byte2   L
S 76543210 S      S fedcba98 S
B          B      B          B

In big endian format:

readbits(6), readbits(5) #=> [765432, 10fed]

In little endian format:

readbits(6), readbits(5) #=> [543210, a9876]

Raises:

  • (ArgumentError)


36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/bindata/io.rb', line 36

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 = BinData::IO.create_string_io(io.to_str)
  end

  @raw_io = io

  # initial stream position if stream supports positioning
  @initial_pos = current_position rescue 0

  # bits when reading
  @rnbits  = 0
  @rval    = 0
  @rendian = nil

  # bits when writing
  @wnbits  = 0
  @wval    = 0
  @wendian = nil
end

Instance Attribute Details

#raw_ioObject (readonly)

Access to the underlying raw io.



61
62
63
# File 'lib/bindata/io.rb', line 61

def raw_io
  @raw_io
end

Class Method Details

.create_string_io(str = "") ⇒ Object

Creates a StringIO around str.



12
13
14
15
16
17
# File 'lib/bindata/io.rb', line 12

def self.create_string_io(str = "")
  if str.respond_to?(:force_encoding)
    str = str.dup.force_encoding(Encoding::BINARY)
  end
  StringIO.new(str)
end

Instance Method Details

#flushbitsObject Also known as: flush

To be called after all writebits have been applied.



159
160
161
162
163
164
165
# File 'lib/bindata/io.rb', line 159

def flushbits
  raise "Internal state error nbits = #{@wnbits}" if @wnbits >= 8

  if @wnbits > 0
    writebits(0, 8 - @wnbits, @wendian)
  end
end

#num_bytes_remainingObject

The number of bytes remaining in the input stream.



72
73
74
75
76
77
78
79
80
81
# File 'lib/bindata/io.rb', line 72

def num_bytes_remaining
  pos = current_position
  @raw_io.seek(0, ::IO::SEEK_END)
  bytes_remaining = current_position - pos
  @raw_io.seek(pos, ::IO::SEEK_SET)

  bytes_remaining
rescue Unseekable
  0
end

#offsetObject

Returns the current offset of the io stream. The exact value of the offset when reading bitfields is not defined.



65
66
67
68
69
# File 'lib/bindata/io.rb', line 65

def offset
  current_position - @initial_pos
rescue Unseekable
  0
end

#read_all_bytesObject

Reads all remaining bytes from the stream.



106
107
108
109
# File 'lib/bindata/io.rb', line 106

def read_all_bytes
  reset_read_bits
  @raw_io.read
end

#readbits(nbits, endian) ⇒ Object

Reads exactly nbits bits from the stream. endian specifies whether the bits are stored in :big or :little endian format.



113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/bindata/io.rb', line 113

def readbits(nbits, endian)
  if @rendian != endian
    # don't mix bits of differing endian
    reset_read_bits
    @rendian = endian
  end

  if endian == :big
    read_big_endian_bits(nbits)
  else
    read_little_endian_bits(nbits)
  end
end

#readbytes(n) ⇒ Object

Reads exactly n bytes from io.

If the data read is nil an EOFError is raised.

If the data read is too short an IOError is raised.

Raises:

  • (EOFError)


96
97
98
99
100
101
102
103
# File 'lib/bindata/io.rb', line 96

def readbytes(n)
  reset_read_bits

  str = @raw_io.read(n)
  raise EOFError, "End of file reached" if str.nil?
  raise IOError, "data truncated" if str.size < n
  str
end

#reset_read_bitsObject

Discards any read bits so the stream becomes aligned at the next byte boundary.



129
130
131
132
# File 'lib/bindata/io.rb', line 129

def reset_read_bits
  @rnbits = 0
  @rval   = 0
end

#seekbytes(n) ⇒ Object

Seek n bytes from the current position in the io stream.



84
85
86
87
88
89
# File 'lib/bindata/io.rb', line 84

def seekbytes(n)
  reset_read_bits
  @raw_io.seek(n, ::IO::SEEK_CUR)
rescue NoMethodError, Errno::ESPIPE, Errno::EPIPE
  skipbytes(n)
end

#writebits(val, nbits, endian) ⇒ Object

Writes nbits bits from val to the stream. endian specifies whether the bits are to be stored in :big or :little endian format.



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/bindata/io.rb', line 142

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

#writebytes(str) ⇒ Object

Writes the given string of bytes to the io stream.



135
136
137
138
# File 'lib/bindata/io.rb', line 135

def writebytes(str)
  flushbits
  @raw_io.write(str)
end