File: buffer.rb

package info (click to toggle)
ruby-bindata 2.5.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 652 kB
  • sloc: ruby: 8,896; makefile: 4
file content (195 lines) | stat: -rw-r--r-- 4,672 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
require 'bindata/base'
require 'bindata/dsl'

module BinData
  # A Buffer is conceptually a substream within a data stream.  It has a
  # defined size and it will always read or write the exact number of bytes to
  # fill the buffer.  Short reads will skip over unused bytes and short writes
  # will pad the substream with "\0" bytes.
  #
  #   require 'bindata'
  #
  #   obj = BinData::Buffer.new(length: 5, type: [:string, {value: "abc"}])
  #   obj.to_binary_s #=> "abc\000\000"
  #
  #
  #   class MyBuffer < BinData::Buffer
  #     default_parameter length: 8
  #
  #     endian :little
  #
  #     uint16 :num1
  #     uint16 :num2
  #     # padding occurs here
  #   end
  #
  #   obj = MyBuffer.read("\001\000\002\000\000\000\000\000")
  #   obj.num1 #=> 1
  #   obj.num1 #=> 2
  #   obj.raw_num_bytes #=> 4
  #   obj.num_bytes #=> 8
  #
  #
  #   class StringTable < BinData::Record
  #     endian :little
  #
  #     uint16 :table_size_in_bytes
  #     buffer :strings, length: :table_size_in_bytes do
  #       array read_until: :eof do
  #         uint8 :len
  #         string :str, length: :len
  #       end
  #     end
  #   end
  #
  #
  # == Parameters
  #
  # Parameters may be provided at initialisation to control the behaviour of
  # an object.  These params are:
  #
  # <tt>:length</tt>::   The number of bytes in the buffer.
  # <tt>:type</tt>::     The single type inside the buffer.  Use a struct if
  #                      multiple fields are required.
  class Buffer < BinData::Base
    extend DSLMixin

    dsl_parser    :buffer
    arg_processor :buffer

    mandatory_parameters :length, :type

    def initialize_instance
      @type = get_parameter(:type).instantiate(nil, self)
    end

    # The number of bytes used, ignoring the padding imposed by the buffer.
    def raw_num_bytes
      @type.num_bytes
    end

    def clear?
      @type.clear?
    end

    def assign(val)
      @type.assign(val)
    end

    def snapshot
      @type.snapshot
    end

    def respond_to_missing?(symbol, include_all = false) # :nodoc:
      @type.respond_to?(symbol, include_all) || super
    end

    def method_missing(symbol, *args, &block) # :nodoc:
      @type.__send__(symbol, *args, &block)
    end

    def do_read(io) # :nodoc:
      buf_len = eval_parameter(:length)
      io.transform(BufferIO.new(buf_len)) do |transformed_io, _|
        @type.do_read(transformed_io)
      end
    end

    def do_write(io) # :nodoc:
      buf_len = eval_parameter(:length)
      io.transform(BufferIO.new(buf_len)) do |transformed_io, _|
        @type.do_write(transformed_io)
      end
    end

    def do_num_bytes # :nodoc:
      eval_parameter(:length)
    end

    # Transforms the IO stream to restrict access inside
    # a buffer of specified length.
    class BufferIO < IO::Transform
      def initialize(length)
        super()
        @bytes_remaining = length
      end

      def before_transform
        @buf_start = offset
        @buf_end = @buf_start + @bytes_remaining
      end

      def num_bytes_remaining
        [@bytes_remaining, super].min
      rescue IOError
        @bytes_remaining
      end

      def skip(n)
        nbytes = buffer_limited_n(n)
        @bytes_remaining -= nbytes

        chain_skip(nbytes)
      end

      def seek_abs(n)
        if n < @buf_start || n >= @buf_end
          raise IOError, "can not seek to abs_offset outside of buffer"
        end

        @bytes_remaining -= (n - offset)
        chain_seek_abs(n)
      end

      def read(n)
        nbytes = buffer_limited_n(n)
        @bytes_remaining -= nbytes

        chain_read(nbytes)
      end

      def write(data)
        nbytes = buffer_limited_n(data.size)
        @bytes_remaining -= nbytes
        if nbytes < data.size
          data = data[0, nbytes]
        end

        chain_write(data)
      end

      def after_read_transform
        read(nil)
      end

      def after_write_transform
        write("\x00" * @bytes_remaining)
      end

      def buffer_limited_n(n)
        if n.nil?
          @bytes_remaining
        elsif n.positive?
          limit = @bytes_remaining
          n > limit ? limit : n
# uncomment if we decide to allow backwards skipping
#        elsif n.negative?
#          limit = @bytes_remaining + @buf_start - @buf_end
#          n < limit ? limit : n
        else
          0
        end
      end
    end
  end

  class BufferArgProcessor < BaseArgProcessor
    include MultiFieldArgSeparator

    def sanitize_parameters!(obj_class, params)
      params.merge!(obj_class.dsl_params)
      params.must_be_integer(:length)
      params.sanitize_object_prototype(:type)
    end
  end
end