aws-eventstream 1.0.3 → 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: f7aee8044bf87469f60f9a7fa8a7b28069a28a72
4
- data.tar.gz: ab363b06b8d2e45da754e5dffc707dd68fdac1f9
2
+ SHA256:
3
+ metadata.gz: fff3fdb08217556c55202a167109fc5bedf371ba7eabb7b23eb1e63df6b305b5
4
+ data.tar.gz: 9c47cccf76de702069df8692b74aefab41778374bc179db484c8835489708869
5
5
  SHA512:
6
- metadata.gz: 8da3c6a510ab3355814264258350c8930847ddc0c1197ecfb59508eb9e37e97297572e0e7ea429953d3a1ac05cb5b070201c7054915986f1d630b4ec9da4dc3f
7
- data.tar.gz: 2aff7fe4e8b4a0c07d7a2a35946066c376b5341a983a2da13059c8bdf8ba40add81f9c19a2e0b7b2441f375b43e3ee12a6e84681506b350befa2b0208a4e5486
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
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.3
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-04-24 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,7 +18,6 @@ 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
@@ -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