aws-eventstream 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 91e0e9ec59b3137e556a697e02b94a1bea99041f
4
- data.tar.gz: 53cd60928f4434173f2e01798256d40e21e8e401
2
+ SHA256:
3
+ metadata.gz: fff3fdb08217556c55202a167109fc5bedf371ba7eabb7b23eb1e63df6b305b5
4
+ data.tar.gz: 9c47cccf76de702069df8692b74aefab41778374bc179db484c8835489708869
5
5
  SHA512:
6
- metadata.gz: 5c4a00f2542e62fadd50744026151d8ad7df6682e020a48e194a570377442e64cd8673ccc4464b552cda952631d4cf457b5df2c4f6b91cc7089b66541bb26bfb
7
- data.tar.gz: 4765ce5c6e944b58a44bb50e3921601932d2c9a6ac9eb33c8edb2f86ad1b031cb8dfaf88ef73303073931093122101720e8faceb2b616f2515395e3459239aac
6
+ metadata.gz: f82dd4b517d49fa88424cccb2d777a65b35e80ca47cd352f57c50d26e3073642d549f40d657d034c153df7a4863ad68ba8d163cf6ea422ee283ba27c8c0c3fe8
7
+ data.tar.gz: 20794804e55472b14614b5e0727ef807f9994a803328874a9fbb337777212c49d6798dc3136424c86de59d66d5ee7a121ccdd89b86572e5c84a7eeddd592513d
@@ -1,7 +1,6 @@
1
1
  require_relative 'aws-eventstream/decoder'
2
2
  require_relative 'aws-eventstream/encoder'
3
3
 
4
- require_relative 'aws-eventstream/bytes_buffer'
5
4
  require_relative 'aws-eventstream/message'
6
5
  require_relative 'aws-eventstream/header_value'
7
6
  require_relative 'aws-eventstream/types'
@@ -3,7 +3,7 @@ require 'tempfile'
3
3
  require 'zlib'
4
4
 
5
5
  module Aws
6
- module EventStream
6
+ module EventStream
7
7
 
8
8
  # This class provides method for decoding binary inputs into
9
9
  # single or multiple messages (Aws::EventStream::Message).
@@ -28,7 +28,7 @@ module Aws
28
28
  # message_pool = decoder.decode(io)
29
29
  # message_pool.next
30
30
  # # => Aws::EventStream::Message
31
- #
31
+ #
32
32
  # * {#decode_chunk} - decodes a single message from a chunk of data,
33
33
  # returning message object followed by boolean(indicating eof status
34
34
  # of data) in an array object
@@ -63,27 +63,26 @@ module Aws
63
63
  include Enumerable
64
64
 
65
65
  ONE_MEGABYTE = 1024 * 1024
66
+ private_constant :ONE_MEGABYTE
66
67
 
67
68
  # bytes of prelude part, including 4 bytes of
68
69
  # total message length, headers length and crc checksum of prelude
69
70
  PRELUDE_LENGTH = 12
71
+ private_constant :PRELUDE_LENGTH
70
72
 
71
- # bytes of total overhead in a message, including prelude
72
- # and 4 bytes total message crc checksum
73
- OVERHEAD_LENGTH = 16
73
+ # 4 bytes message crc checksum
74
+ CRC32_LENGTH = 4
75
+ private_constant :CRC32_LENGTH
74
76
 
75
- # @options options [Boolean] format (true) When `false`
76
- # disable user-friendly formatting for message header values
77
+ # @param [Hash] options The initialization options.
78
+ # @option options [Boolean] :format (true) When `false` it
79
+ # disables user-friendly formatting for message header values
77
80
  # including timestamp and uuid etc.
78
- #
79
81
  def initialize(options = {})
80
82
  @format = options.fetch(:format, true)
81
- @message_buffer = BytesBuffer.new('')
83
+ @message_buffer = ''
82
84
  end
83
85
 
84
- # @returns [BytesBuffer]
85
- attr_reader :message_buffer
86
-
87
86
  # Decodes messages from a binary stream
88
87
  #
89
88
  # @param [IO#read] io An IO-like object
@@ -93,12 +92,12 @@ module Aws
93
92
  # @return [Enumerable<Message>, nil] Returns a new Enumerable
94
93
  # containing decoded messages if no block is given
95
94
  def decode(io, &block)
96
- io = BytesBuffer.new(io.read)
97
- return decode_io(io) unless block_given?
98
- until io.eof?
99
- # fetch message only
100
- yield(decode_message(io).first)
101
- end
95
+ raw_message = io.read
96
+ decoded_message = decode_message(raw_message)
97
+ return wrap_as_enumerator(decoded_message) unless block_given?
98
+ # fetch message only
99
+ raw_event, _eof = decoded_message
100
+ block.call(raw_event)
102
101
  end
103
102
 
104
103
  # Decodes a single message from a chunk of string
@@ -111,95 +110,81 @@ module Aws
111
110
  # and boolean pair, the boolean flag indicates whether this chunk
112
111
  # has been fully consumed, unused data is tracked at #message_buffer
113
112
  def decode_chunk(chunk = nil)
114
- @message_buffer.write(chunk) if chunk
115
- @message_buffer.rewind
113
+ @message_buffer = [@message_buffer, chunk].pack('a*a*') if chunk
116
114
  decode_message(@message_buffer)
117
115
  end
118
116
 
119
117
  private
120
118
 
121
- def decode_io(io)
122
- ::Enumerator.new {|e| e << decode_message(io) unless io.eof? }
119
+ # exposed via object.send for testing
120
+ attr_reader :message_buffer
121
+
122
+ def wrap_as_enumerator(decoded_message)
123
+ Enumerator.new do |yielder|
124
+ yielder << decoded_message
125
+ end
123
126
  end
124
127
 
125
- def decode_message(io)
126
- # incomplete message prelude received, leave it in the buffer
127
- return [nil, true] if io.bytesize < PRELUDE_LENGTH
128
+ def decode_message(raw_message)
129
+ # incomplete message prelude received
130
+ return [nil, true] if raw_message.bytesize < PRELUDE_LENGTH
131
+
132
+ prelude, content = raw_message.unpack("a#{PRELUDE_LENGTH}a*")
128
133
 
129
134
  # decode prelude
130
- total_len, headers_len, prelude_buffer = prelude(io)
135
+ total_length, header_length = decode_prelude(prelude)
131
136
 
132
137
  # incomplete message received, leave it in the buffer
133
- return [nil, true] if io.bytesize < total_len
138
+ return [nil, true] if raw_message.bytesize < total_length
134
139
 
135
- # decode headers and payload
136
- headers, payload = context(io, total_len, headers_len, prelude_buffer)
137
-
138
- # track extra message data in the buffer if exists
139
- # for #decode_chunk, io is @message_buffer
140
- if eof = io.eof?
141
- @message_buffer.clear!
142
- else
143
- @message_buffer = BytesBuffer.new(@message_buffer.read)
140
+ content, checksum, remaining = content.unpack("a#{total_length - PRELUDE_LENGTH - CRC32_LENGTH}Na*")
141
+ unless Zlib.crc32([prelude, content].pack('a*a*')) == checksum
142
+ raise Errors::MessageChecksumError
144
143
  end
145
144
 
146
- [Message.new(headers: headers, payload: payload), eof]
145
+ # decode headers and payload
146
+ headers, payload = decode_context(content, header_length)
147
+
148
+ @message_buffer = remaining
149
+
150
+ [Message.new(headers: headers, payload: payload), remaining.empty?]
147
151
  end
148
152
 
149
- def prelude(io)
150
- # buffer prelude into bytes buffer
153
+ def decode_prelude(prelude)
151
154
  # prelude contains length of message and headers,
152
155
  # followed with CRC checksum of itself
153
- buffer = BytesBuffer.new(io.read(PRELUDE_LENGTH))
154
-
155
- # prelude checksum takes last 4 bytes
156
- checksum = Zlib.crc32(buffer.read(PRELUDE_LENGTH - 4))
157
- unless checksum == unpack_uint32(buffer)
158
- raise Errors::PreludeChecksumError
159
- end
160
-
161
- buffer.rewind
162
- total_len, headers_len, _ = buffer.read.unpack("N*")
163
- [total_len, headers_len, buffer]
156
+ content, checksum = prelude.unpack("a#{PRELUDE_LENGTH - CRC32_LENGTH}N")
157
+ raise Errors::PreludeChecksumError unless Zlib.crc32(content) == checksum
158
+ content.unpack('N*')
164
159
  end
165
160
 
166
- def context(io, total_len, headers_len, prelude_buffer)
167
- # buffer rest of the message except prelude length
168
- # including context and total message checksum
169
- buffer = BytesBuffer.new(io.read(total_len - PRELUDE_LENGTH))
170
- context_len = total_len - OVERHEAD_LENGTH
171
-
172
- prelude_buffer.rewind
173
- checksum = Zlib.crc32(prelude_buffer.read << buffer.read(context_len))
174
- unless checksum == unpack_uint32(buffer)
175
- raise Errors::MessageChecksumError
176
- end
177
-
178
- buffer.rewind
161
+ def decode_context(content, header_length)
162
+ encoded_header, encoded_payload = content.unpack("a#{header_length}a*")
179
163
  [
180
- extract_headers(BytesBuffer.new(buffer.read(headers_len))),
181
- extract_payload(BytesBuffer.new(buffer.read(context_len - headers_len)))
164
+ extract_headers(encoded_header),
165
+ extract_payload(encoded_payload)
182
166
  ]
183
167
  end
184
168
 
185
169
  def extract_headers(buffer)
170
+ scanner = buffer
186
171
  headers = {}
187
- until buffer.eof?
172
+ until scanner.bytesize == 0
188
173
  # header key
189
- key_len = unpack_uint8(buffer)
190
- key = buffer.read(key_len)
174
+ key_length, scanner = scanner.unpack('Ca*')
175
+ key, scanner = scanner.unpack("a#{key_length}a*")
191
176
 
192
177
  # header value
193
- value_type = Types.types[unpack_uint8(buffer)]
194
- unpack_pattern, value_len, _ = Types.pattern[value_type]
195
- if !!unpack_pattern == unpack_pattern
178
+ type_index, scanner = scanner.unpack('Ca*')
179
+ value_type = Types.types[type_index]
180
+ unpack_pattern, value_length = Types.pattern[value_type]
181
+ value = if !!unpack_pattern == unpack_pattern
196
182
  # boolean types won't have value specified
197
- value = unpack_pattern
183
+ unpack_pattern
198
184
  else
199
- value_len = unpack_uint16(buffer) unless value_len
200
- value = unpack_pattern ?
201
- buffer.read(value_len).unpack(unpack_pattern)[0] :
202
- buffer.read(value_len)
185
+ value_length, scanner = scanner.unpack('S>a*') unless value_length
186
+ unpacked_value, scanner = scanner.unpack("#{unpack_pattern || "a#{value_length}"}a*")
187
+ unpacked_value
203
188
  end
204
189
 
205
190
  headers[key] = HeaderValue.new(
@@ -211,40 +196,23 @@ module Aws
211
196
  headers
212
197
  end
213
198
 
214
- def extract_payload(buffer)
215
- buffer.bytesize <= ONE_MEGABYTE ?
216
- payload_stringio(buffer) :
217
- payload_tempfile(buffer)
199
+ def extract_payload(encoded)
200
+ encoded.bytesize <= ONE_MEGABYTE ?
201
+ payload_stringio(encoded) :
202
+ payload_tempfile(encoded)
218
203
  end
219
204
 
220
- def payload_stringio(buffer)
221
- StringIO.new(buffer.read)
205
+ def payload_stringio(encoded)
206
+ StringIO.new(encoded)
222
207
  end
223
208
 
224
- def payload_tempfile(buffer)
209
+ def payload_tempfile(encoded)
225
210
  payload = Tempfile.new
226
211
  payload.binmode
227
- until buffer.eof?
228
- payload.write(buffer.read(ONE_MEGABYTE))
229
- end
212
+ payload.write(encoded)
230
213
  payload.rewind
231
214
  payload
232
215
  end
233
-
234
- # overhead decode helpers
235
-
236
- def unpack_uint32(buffer)
237
- buffer.read(4).unpack("N")[0]
238
- end
239
-
240
- def unpack_uint16(buffer)
241
- buffer.read(2).unpack("S>")[0]
242
- end
243
-
244
- def unpack_uint8(buffer)
245
- buffer.readbyte.unpack("C")[0]
246
- end
247
216
  end
248
-
249
217
  end
250
218
  end
@@ -1,7 +1,7 @@
1
1
  require 'zlib'
2
2
 
3
3
  module Aws
4
- module EventStream
4
+ module EventStream
5
5
 
6
6
  # This class provides #encode method for encoding
7
7
  # Aws::EventStream::Message into binary.
@@ -59,7 +59,7 @@ module Aws
59
59
  # will be returned. Else, encoded binary string is
60
60
  # returned.
61
61
  def encode(message, io = nil)
62
- encoded = encode_message(message).read
62
+ encoded = encode_message(message)
63
63
  if io
64
64
  io.write(encoded)
65
65
  io.close
@@ -69,92 +69,72 @@ module Aws
69
69
  end
70
70
 
71
71
  # Encodes an Aws::EventStream::Message
72
- # into Aws::EventStream::BytesBuffer
72
+ # into String
73
73
  #
74
- # @param [Aws::EventStream::Message] msg
74
+ # @param [Aws::EventStream::Message] message
75
75
  #
76
- # @return [Aws::EventStream::BytesBuffer]
76
+ # @return [String]
77
77
  def encode_message(message)
78
78
  # create context buffer with encode headers
79
- ctx_buffer = encode_headers(message)
80
- headers_len = ctx_buffer.bytesize
79
+ encoded_header = encode_headers(message)
80
+ header_length = encoded_header.bytesize
81
81
  # encode payload
82
82
  if message.payload.length > MAX_PAYLOAD_LENGTH
83
83
  raise Aws::EventStream::Errors::EventPayloadLengthExceedError.new
84
84
  end
85
- ctx_buffer << message.payload.read
86
- total_len = ctx_buffer.bytesize + OVERHEAD_LENGTH
85
+ encoded_payload = message.payload.read
86
+ total_length = header_length + encoded_payload.bytesize + OVERHEAD_LENGTH
87
87
 
88
88
  # create message buffer with prelude section
89
- buffer = prelude(total_len, headers_len)
89
+ encoded_prelude = encode_prelude(total_length, header_length)
90
90
 
91
91
  # append message context (headers, payload)
92
- buffer << ctx_buffer.read
92
+ encoded_content = [
93
+ encoded_prelude,
94
+ encoded_header,
95
+ encoded_payload,
96
+ ].pack('a*a*a*')
93
97
  # append message checksum
94
- buffer << pack_uint32(Zlib.crc32(buffer.read))
95
-
96
- # write buffered message to io
97
- buffer.rewind
98
- buffer
98
+ message_checksum = Zlib.crc32(encoded_content)
99
+ [encoded_content, message_checksum].pack('a*N')
99
100
  end
100
101
 
101
102
  # Encodes headers part of an Aws::EventStream::Message
102
- # into Aws::EventStream::BytesBuffer
103
+ # into String
103
104
  #
104
- # @param [Aws::EventStream::Message] msg
105
+ # @param [Aws::EventStream::Message] message
105
106
  #
106
- # @return [Aws::EventStream::BytesBuffer]
107
- def encode_headers(msg)
108
- buffer = BytesBuffer.new('')
109
- msg.headers.each do |k, v|
110
- # header key
111
- buffer << pack_uint8(k.bytesize)
112
- buffer << k
107
+ # @return [String]
108
+ def encode_headers(message)
109
+ header_entries = message.headers.map do |key, value|
110
+ encoded_key = [key.bytesize, key].pack('Ca*')
113
111
 
114
112
  # header value
115
- pattern, val_len, idx = Types.pattern[v.type]
116
- buffer << pack_uint8(idx)
113
+ pattern, value_length, type_index = Types.pattern[value.type]
114
+ encoded_value = [type_index].pack('C')
117
115
  # boolean types doesn't need to specify value
118
- next if !!pattern == pattern
119
- buffer << pack_uint16(v.value.bytesize) unless val_len
120
- pattern ? buffer << [v.value].pack(pattern) :
121
- buffer << v.value
116
+ next [encoded_key, encoded_value].pack('a*a*') if !!pattern == pattern
117
+ encoded_value = [encoded_value, value.value.bytesize].pack('a*S>') unless value_length
118
+
119
+ [
120
+ encoded_key,
121
+ encoded_value,
122
+ pattern ? [value.value].pack(pattern) : value.value,
123
+ ].pack('a*a*a*')
122
124
  end
123
- if buffer.bytesize > MAX_HEADERS_LENGTH
125
+ header_entries.join.tap do |encoded_header|
126
+ break encoded_header if encoded_header.bytesize <= MAX_HEADERS_LENGTH
124
127
  raise Aws::EventStream::Errors::EventHeadersLengthExceedError.new
125
128
  end
126
- buffer
127
129
  end
128
130
 
129
131
  private
130
132
 
131
- def prelude(total_len, headers_len)
132
- BytesBuffer.new(pack_uint32([
133
- total_len,
134
- headers_len,
135
- Zlib.crc32(pack_uint32([total_len, headers_len]))
136
- ]))
137
- end
138
-
139
- # overhead encode helpers
140
-
141
- def pack_uint8(val)
142
- [val].pack("C")
143
- end
144
-
145
- def pack_uint16(val)
146
- [val].pack("S>")
133
+ def encode_prelude(total_length, headers_length)
134
+ prelude_body = [total_length, headers_length].pack('NN')
135
+ checksum = Zlib.crc32(prelude_body)
136
+ [prelude_body, checksum].pack('a*N')
147
137
  end
148
-
149
- def pack_uint32(val)
150
- if val.respond_to?(:each)
151
- val.pack("N*")
152
- else
153
- [val].pack("N")
154
- end
155
- end
156
-
157
138
  end
158
-
159
139
  end
160
140
  end
@@ -14,19 +14,19 @@ module Aws
14
14
  # Raise when insufficient bytes of a message is received
15
15
  class IncompleteMessageError < RuntimeError
16
16
  def initialize(*args)
17
- super("Not enough bytes for event message")
17
+ super('Not enough bytes for event message')
18
18
  end
19
19
  end
20
20
 
21
21
  class PreludeChecksumError < RuntimeError
22
22
  def initialize(*args)
23
- super("Prelude checksum mismatch")
23
+ super('Prelude checksum mismatch')
24
24
  end
25
25
  end
26
26
 
27
27
  class MessageChecksumError < RuntimeError
28
28
  def initialize(*args)
29
- super("Message checksum mismatch")
29
+ super('Message checksum mismatch')
30
30
  end
31
31
  end
32
32
 
@@ -20,8 +20,8 @@ module Aws
20
20
 
21
21
  def format_value(value)
22
22
  case @type
23
- when "timestamp" then format_timestamp(value)
24
- when "uuid" then format_uuid(value)
23
+ when 'timestamp' then format_timestamp(value)
24
+ when 'uuid' then format_uuid(value)
25
25
  else
26
26
  value
27
27
  end
@@ -32,7 +32,7 @@ module Aws
32
32
  # For user-friendly uuid representation,
33
33
  # format binary bytes into uuid string format
34
34
  uuid_pattern = [ [ 3, 2, 1, 0 ], [ 5, 4 ], [ 7, 6 ], [ 8, 9 ], 10..15 ]
35
- uuid_pattern.map {|p| p.map {|n| "%02x" % bytes.to_a[n] }.join }.join("-")
35
+ uuid_pattern.map {|p| p.map {|n| "%02x" % bytes.to_a[n] }.join }.join('-')
36
36
  end
37
37
 
38
38
  def format_timestamp(value)
@@ -6,32 +6,32 @@ module Aws
6
6
 
7
7
  def self.types
8
8
  [
9
- "bool_true",
10
- "bool_false",
11
- "byte",
12
- "short",
13
- "integer",
14
- "long",
15
- "bytes",
16
- "string",
17
- "timestamp",
18
- "uuid"
9
+ 'bool_true',
10
+ 'bool_false',
11
+ 'byte',
12
+ 'short',
13
+ 'integer',
14
+ 'long',
15
+ 'bytes',
16
+ 'string',
17
+ 'timestamp',
18
+ 'uuid'
19
19
  ]
20
20
  end
21
21
 
22
22
  # pack/unpack pattern, byte size, type idx
23
23
  def self.pattern
24
24
  {
25
- "bool_true" => [true, 0, 0],
26
- "bool_false" => [false, 0, 1],
27
- "byte" => ["c", 1, 2],
28
- "short" => ["s>", 2, 3],
29
- "integer" => ["l>", 4, 4],
30
- "long" => ["q>", 8, 5],
31
- "bytes" => [nil, nil, 6],
32
- "string" => [nil, nil, 7],
33
- "timestamp" => ["q>", 8, 8],
34
- "uuid" => [nil, 16, 9]
25
+ 'bool_true' => [true, 0, 0],
26
+ 'bool_false' => [false, 0, 1],
27
+ 'byte' => ["c", 1, 2],
28
+ 'short' => ["s>", 2, 3],
29
+ 'integer' => ["l>", 4, 4],
30
+ 'long' => ["q>", 8, 5],
31
+ 'bytes' => [nil, nil, 6],
32
+ 'string' => [nil, nil, 7],
33
+ 'timestamp' => ["q>", 8, 8],
34
+ 'uuid' => [nil, 16, 9]
35
35
  }
36
36
  end
37
37
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aws-eventstream
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Amazon Web Services
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-03-11 00:00:00.000000000 Z
11
+ date: 2020-04-08 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Amazon Web Services event stream library. Decodes and encodes binary
14
14
  stream under `vnd.amazon.event-stream` content-type
@@ -18,14 +18,13 @@ extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
20
  - lib/aws-eventstream.rb
21
- - lib/aws-eventstream/bytes_buffer.rb
22
21
  - lib/aws-eventstream/decoder.rb
23
22
  - lib/aws-eventstream/encoder.rb
24
23
  - lib/aws-eventstream/errors.rb
25
24
  - lib/aws-eventstream/header_value.rb
26
25
  - lib/aws-eventstream/message.rb
27
26
  - lib/aws-eventstream/types.rb
28
- homepage: http://github.com/aws/aws-sdk-ruby
27
+ homepage: https://github.com/aws/aws-sdk-ruby
29
28
  licenses:
30
29
  - Apache-2.0
31
30
  metadata:
@@ -47,7 +46,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
47
46
  version: '0'
48
47
  requirements: []
49
48
  rubyforge_project:
50
- rubygems_version: 2.5.2.3
49
+ rubygems_version: 2.7.6.2
51
50
  signing_key:
52
51
  specification_version: 4
53
52
  summary: AWS Event Stream Library
@@ -1,66 +0,0 @@
1
- module Aws
2
- module EventStream
3
-
4
- # @api private
5
- class BytesBuffer
6
-
7
- # This Util class is for Decoder/Encoder usage only
8
- # Not for public common bytes buffer usage
9
- def initialize(data)
10
- @data = data
11
- @pos = 0
12
- end
13
-
14
- def read(len = nil, offset = 0)
15
- return '' if len == 0 || bytesize == 0
16
- unless eof?
17
- start_byte = @pos + offset
18
- end_byte = len ?
19
- start_byte + len - 1 :
20
- bytesize - 1
21
-
22
- error = Errors::ReadBytesExceedLengthError.new(end_byte, bytesize)
23
- raise error if end_byte >= bytesize
24
-
25
- @pos = end_byte + 1
26
- @data[start_byte..end_byte]
27
- end
28
- end
29
-
30
- def readbyte
31
- unless eof?
32
- @pos += 1
33
- @data[@pos - 1]
34
- end
35
- end
36
-
37
- def write(bytes)
38
- @data <<= bytes
39
- bytes.bytesize
40
- end
41
- alias_method :<<, :write
42
-
43
- def rewind
44
- @pos = 0
45
- end
46
-
47
- def eof?
48
- @pos == bytesize
49
- end
50
-
51
- def bytesize
52
- @data.bytesize
53
- end
54
-
55
- def tell
56
- @pos
57
- end
58
-
59
- def clear!
60
- @data = ''
61
- @pos = 0
62
- end
63
- end
64
-
65
- end
66
- end