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 +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
|