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 +5 -5
- data/lib/aws-eventstream.rb +0 -1
- data/lib/aws-eventstream/decoder.rb +69 -101
- data/lib/aws-eventstream/encoder.rb +39 -59
- metadata +3 -4
- data/lib/aws-eventstream/bytes_buffer.rb +0 -66
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: fff3fdb08217556c55202a167109fc5bedf371ba7eabb7b23eb1e63df6b305b5
|
4
|
+
data.tar.gz: 9c47cccf76de702069df8692b74aefab41778374bc179db484c8835489708869
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f82dd4b517d49fa88424cccb2d777a65b35e80ca47cd352f57c50d26e3073642d549f40d657d034c153df7a4863ad68ba8d163cf6ea422ee283ba27c8c0c3fe8
|
7
|
+
data.tar.gz: 20794804e55472b14614b5e0727ef807f9994a803328874a9fbb337777212c49d6798dc3136424c86de59d66d5ee7a121ccdd89b86572e5c84a7eeddd592513d
|
data/lib/aws-eventstream.rb
CHANGED
@@ -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
|
72
|
-
|
73
|
-
|
73
|
+
# 4 bytes message crc checksum
|
74
|
+
CRC32_LENGTH = 4
|
75
|
+
private_constant :CRC32_LENGTH
|
74
76
|
|
75
|
-
# @
|
76
|
-
#
|
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 =
|
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
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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.
|
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
|
-
|
122
|
-
|
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(
|
126
|
-
# incomplete message prelude received
|
127
|
-
return [nil, true] if
|
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
|
-
|
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
|
138
|
+
return [nil, true] if raw_message.bytesize < total_length
|
134
139
|
|
135
|
-
#
|
136
|
-
|
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
|
-
|
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
|
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
|
-
|
154
|
-
|
155
|
-
|
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
|
167
|
-
|
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(
|
181
|
-
extract_payload(
|
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
|
172
|
+
until scanner.bytesize == 0
|
188
173
|
# header key
|
189
|
-
|
190
|
-
key =
|
174
|
+
key_length, scanner = scanner.unpack('Ca*')
|
175
|
+
key, scanner = scanner.unpack("a#{key_length}a*")
|
191
176
|
|
192
177
|
# header value
|
193
|
-
|
194
|
-
|
195
|
-
|
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
|
-
|
183
|
+
unpack_pattern
|
198
184
|
else
|
199
|
-
|
200
|
-
|
201
|
-
|
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(
|
215
|
-
|
216
|
-
payload_stringio(
|
217
|
-
payload_tempfile(
|
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(
|
221
|
-
StringIO.new(
|
205
|
+
def payload_stringio(encoded)
|
206
|
+
StringIO.new(encoded)
|
222
207
|
end
|
223
208
|
|
224
|
-
def payload_tempfile(
|
209
|
+
def payload_tempfile(encoded)
|
225
210
|
payload = Tempfile.new
|
226
211
|
payload.binmode
|
227
|
-
|
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)
|
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
|
72
|
+
# into String
|
73
73
|
#
|
74
|
-
# @param [Aws::EventStream::Message]
|
74
|
+
# @param [Aws::EventStream::Message] message
|
75
75
|
#
|
76
|
-
# @return [
|
76
|
+
# @return [String]
|
77
77
|
def encode_message(message)
|
78
78
|
# create context buffer with encode headers
|
79
|
-
|
80
|
-
|
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
|
-
|
86
|
-
|
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
|
-
|
89
|
+
encoded_prelude = encode_prelude(total_length, header_length)
|
90
90
|
|
91
91
|
# append message context (headers, payload)
|
92
|
-
|
92
|
+
encoded_content = [
|
93
|
+
encoded_prelude,
|
94
|
+
encoded_header,
|
95
|
+
encoded_payload,
|
96
|
+
].pack('a*a*a*')
|
93
97
|
# append message checksum
|
94
|
-
|
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
|
103
|
+
# into String
|
103
104
|
#
|
104
|
-
# @param [Aws::EventStream::Message]
|
105
|
+
# @param [Aws::EventStream::Message] message
|
105
106
|
#
|
106
|
-
# @return [
|
107
|
-
def encode_headers(
|
108
|
-
|
109
|
-
|
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,
|
116
|
-
|
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
|
-
|
120
|
-
|
121
|
-
|
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
|
-
|
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
|
132
|
-
|
133
|
-
|
134
|
-
|
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
|
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:
|
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.
|
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
|