Class: Binstream::Streams::Base

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Tracking
Defined in:
lib/binstream/streams/base.rb

Direct Known Subclasses

FileReader, StringReader

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Tracking

included, #track, #track_pos, #without_tracking

Constructor Details

#initialize(stream, startpos: nil, whence: IO::SEEK_SET, read_length: nil, **kwargs) ⇒ Base

Returns a new instance of Base.



11
12
13
14
15
# File 'lib/binstream/streams/base.rb', line 11

def initialize(stream, startpos: nil, whence: IO::SEEK_SET, read_length: nil, **kwargs)
  @stream = stream
  reset
  setup_stopper(startpos, whence, read_length)
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth_name, *args, &block) ⇒ Object

MISC



323
324
325
326
327
328
329
330
# File 'lib/binstream/streams/base.rb', line 323

def method_missing(meth_name, *args, &block)
  meth = "read_#{meth_name}".to_sym
  if respond_to?(meth)
    public_send(meth, *args, &block)
  else
    super
  end
end

Instance Attribute Details

#stopperObject (readonly)

Returns the value of attribute stopper.



9
10
11
# File 'lib/binstream/streams/base.rb', line 9

def stopper
  @stopper
end

Instance Method Details

#dump(filename) ⇒ Object

Dump entire stream to a file (for debugging)



313
314
315
316
317
318
319
# File 'lib/binstream/streams/base.rb', line 313

def dump(filename)
  # return nil unless $TESTING
  @stream.seek(@startpos, IO::SEEK_SET)
  File.open(filename, "wb") do |f|
    f.write(@stream.read(@stopper - @startpos))
  end
end

#eof?Boolean

Returns:



154
155
156
# File 'lib/binstream/streams/base.rb', line 154

def eof?
  remaining <= 0
end

#peek(length = nil) ⇒ Object

Returns data without advancing the offset pointer



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/binstream/streams/base.rb', line 108

def peek(length=nil)
  original_pos = stell
  read_size = (length || size)

  if remaining - read_size < 0
    raise StreamOverrunError.new(read_size, remaining, original_pos)
  end

  @stream.seek(@startpos + @cur_offset, IO::SEEK_SET)
  resp = @stream.read(length)

  return resp
rescue => e
  raise
ensure
  @stream.seek(original_pos, IO::SEEK_SET)
end

#peek_slice(new_length, offset_adjustment = nil) ⇒ Object

Get a new stream at the current position, but don’t advance our internal pointer



61
62
63
64
65
66
67
68
# File 'lib/binstream/streams/base.rb', line 61

def peek_slice(new_length, offset_adjustment=nil)
  offset_adjustment ||= 0

  self.class.new(@stream, 
                  startpos: (@startpos + @cur_offset + offset_adjustment), 
                  read_length: new_length
                )
end

#read(length = nil) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/binstream/streams/base.rb', line 85

def read(length=nil)

  original_pos = stell
  read_size = (length || size)

  if remaining - read_size < 0
    raise StreamOverrunError.new(read_size, remaining, original_pos)
  end

  @stream.seek(@startpos + @cur_offset, IO::SEEK_SET)
  resp = @stream.read(length)
  @cur_offset += read_size

  return resp
rescue => e
  raise
ensure
  # put the stream back
  # TODO: possibly remove this, it just makes it slower
  @stream.seek(original_pos, IO::SEEK_SET)
end

#read_binary(len) ⇒ Object



295
296
297
298
# File 'lib/binstream/streams/base.rb', line 295

def read_binary(len)
  track { sprintf("READ_BINARY(%d+%d = %d)", tell, len, (tell+len)) }
  read(len)
end

#read_boolObject Also known as: read_bool8

8 bit boolean



185
186
187
188
189
190
191
192
193
# File 'lib/binstream/streams/base.rb', line 185

def read_bool
  res = read_single("C", 1)

  if res != 0 && res != 1
    raise InvalidBooleanValueError.new(res, (tell - 1))
  end

  track(res != 0)
end

#read_byteObject



203
204
205
# File 'lib/binstream/streams/base.rb', line 203

def read_byte
  track read_single("c", 1)
end

#read_doubleObject Also known as: read_doublele

8 byte double



278
279
280
281
282
283
284
# File 'lib/binstream/streams/base.rb', line 278

def read_double
  res = read_single("E", 8)
  if res.nan?
    raise InvalidFloatValueError.new(tell - 8)
  end
  track res
end

#read_doublebeObject



285
286
287
288
289
290
291
# File 'lib/binstream/streams/base.rb', line 285

def read_doublebe
  res = read_single("G", 8)
  if res.nan?
    raise InvalidFloatValueError.new(tell - 8)
  end
  track res
end

#read_floatObject Also known as: read_floatle

4 byte floats



260
261
262
263
264
265
266
# File 'lib/binstream/streams/base.rb', line 260

def read_float
  res = read_single("e", 4)
  if res.nan?
    raise InvalidFloatValueError.new(tell - 4)
  end
  track res
end

#read_floatbeObject



267
268
269
270
271
272
273
# File 'lib/binstream/streams/base.rb', line 267

def read_floatbe
  res = read_single("g", 4)
  if res.nan?
    raise InvalidFloatValueError.new(tell - 4)
  end
  track res
end

#read_hash(len) ⇒ Object



300
301
302
# File 'lib/binstream/streams/base.rb', line 300

def read_hash(len)
  track read_single("H*", len)
end

#read_int16Object Also known as: read_int16le



216
217
218
# File 'lib/binstream/streams/base.rb', line 216

def read_int16
  track read_single("s<", 2)
end

#read_int16beObject



219
220
221
# File 'lib/binstream/streams/base.rb', line 219

def read_int16be
  track read_single("s>", 2)
end

#read_int32Object Also known as: read_int32le

32 bits



225
226
227
# File 'lib/binstream/streams/base.rb', line 225

def read_int32
  track read_single("l<", 4)
end

#read_int32beObject



228
229
230
# File 'lib/binstream/streams/base.rb', line 228

def read_int32be
  track read_single("l>", 4)
end

#read_int64Object Also known as: read_int64le

64 bits



243
244
245
# File 'lib/binstream/streams/base.rb', line 243

def read_int64
  track read_single("q<", 8)
end

#read_int64beObject



246
247
248
# File 'lib/binstream/streams/base.rb', line 246

def read_int64be
  track read_single("q>", 8)
end

#read_int8Object

8 Bits



197
198
199
# File 'lib/binstream/streams/base.rb', line 197

def read_int8
  track read_single("c", 1)
end

#read_single(fmt, bytes = 4) ⇒ Object



304
305
306
# File 'lib/binstream/streams/base.rb', line 304

def read_single(fmt, bytes = 4)
  read(bytes).unpack1(fmt)
end

#read_string(length, encoding: "UTF-8", packfmt: "Z*") ⇒ Object

Reads a null terminated string off the stream



175
176
177
178
179
180
181
182
# File 'lib/binstream/streams/base.rb', line 175

def read_string(length, encoding: "UTF-8", packfmt: "Z*")
  if length > 0
    res = read_single(packfmt, length).force_encoding(encoding).encode(encoding)
    track res
  else
    raise InvalidLengthError.new(length)
  end
end

#read_uint16Object Also known as: read_uint16le

16 Bits



208
209
210
# File 'lib/binstream/streams/base.rb', line 208

def read_uint16
  track read_single("S<", 2)
end

#read_uint16beObject



211
212
213
# File 'lib/binstream/streams/base.rb', line 211

def read_uint16be
  track read_single("S>", 2)
end

#read_uint32Object Also known as: read_uint32le



233
234
235
# File 'lib/binstream/streams/base.rb', line 233

def read_uint32
  track read_single("L<", 4)
end

#read_uint32beObject



236
237
238
# File 'lib/binstream/streams/base.rb', line 236

def read_uint32be
  track read_single("L>", 4)
end

#read_uint64Object Also known as: read_uint64le



251
252
253
# File 'lib/binstream/streams/base.rb', line 251

def read_uint64
  track read_single("Q<", 8)
end

#read_uint64beObject



254
255
256
# File 'lib/binstream/streams/base.rb', line 254

def read_uint64be
  track read_single("Q>", 8)
end

#read_uint8Object



200
201
202
# File 'lib/binstream/streams/base.rb', line 200

def read_uint8
  track read_single("C", 1)
end

#read_unpack(bytes, fmt) ⇒ Object



308
309
310
# File 'lib/binstream/streams/base.rb', line 308

def read_unpack(bytes, fmt)
  read(bytes).unpack(fmt)
end

#remainingObject

How many remaining bytes are there



71
72
73
# File 'lib/binstream/streams/base.rb', line 71

def remaining
  size - tell
end

#remaining?(len_to_check = 1) ⇒ Boolean

Do we have any remaining bytes?

Returns:



76
77
78
# File 'lib/binstream/streams/base.rb', line 76

def remaining?(len_to_check=1)
  remaining >= len_to_check
end

#resetObject



35
36
37
38
39
# File 'lib/binstream/streams/base.rb', line 35

def reset
  @cur_offset = 0
  @stopper = nil
  @startpos = 0
end

#rewindObject

Reset the position pointer back to the start



81
82
83
# File 'lib/binstream/streams/base.rb', line 81

def rewind
  @cur_offset = 0
end

#seek(seek_len, whence = IO::SEEK_CUR) ⇒ Object

Seek to a specific position, or relative

Raises:



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/binstream/streams/base.rb', line 127

def seek(seek_len, whence=IO::SEEK_CUR)
  raise ArgumentError.new("Position must be an integer") if seek_len.nil?

  case whence
  when IO::SEEK_SET, :SET
    proposal = seek_len
  when IO::SEEK_CUR, :CUR
    proposal = @cur_offset + seek_len
  when IO::SEEK_END, :END
    proposal = @stopper + seek_len # This will actually be a +(-999)
  else
    raise ArgumentError.new("whence must be :SET, :CUR, :END")
  end

  if valid_position?(proposal)
    @cur_offset = proposal
  else
    raise InvalidPositionError.new(proposal)
  end
  return true
end

#setup_stopper(startpos, whence, max_length) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/binstream/streams/base.rb', line 17

def setup_stopper(startpos, whence, max_length)
  if whence == IO::SEEK_CUR
    startpos += @stream.tell
  end

  @startpos = startpos || @stream.tell

  if max_length
    @stopper = startpos + max_length
  else
    @stopper = @stream.size
  end
end

#slice(new_length, new_offset = nil) ⇒ Object

Create a new stream based off this one using an offset and length



42
43
44
45
46
47
48
49
# File 'lib/binstream/streams/base.rb', line 42

def slice(new_length, new_offset=nil)
  new_stream = peek_slice(new_length, new_offset)
  
  # advance our pointer
  @cur_offset += new_length

  return new_stream
end

#slice!(new_length, new_offset) ⇒ Object

slice!(length, offset) Get new stream using absolute position



53
54
55
56
57
58
# File 'lib/binstream/streams/base.rb', line 53

def slice!(new_length, new_offset)
  self.class.new(@stream, 
    startpos: (@startpos + new_offset), 
    read_length: new_length
  )
end

#starting_offsetObject



31
32
33
# File 'lib/binstream/streams/base.rb', line 31

def starting_offset
  @startpos
end

#stellObject

Position on the underlying stream



165
166
167
# File 'lib/binstream/streams/base.rb', line 165

def stell
  @stream.tell
end

#tellObject Also known as: pos

Position in our current high level stream



159
160
161
# File 'lib/binstream/streams/base.rb', line 159

def tell
  @cur_offset
end

#total_sizeObject Also known as: size



169
170
171
# File 'lib/binstream/streams/base.rb', line 169

def total_size
  stopper - @startpos
end

#valid_position?(proposal) ⇒ Boolean

Is this actually a valid position?

Returns:



150
151
152
# File 'lib/binstream/streams/base.rb', line 150

def valid_position?(proposal)
  proposal.abs <= size
end