aws-eventstream 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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