protocol-websocket 0.7.4 → 0.9.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
2
  SHA256:
3
- metadata.gz: 519265fe5ebe9530d3ddec6e9bc3836dd0a2af827e377ef4ac7f390f5791f2a9
4
- data.tar.gz: 32a7ab9ab4e4b065ee6b0f738396ea575b49da9c8ac927ee38da8afa1ab595fb
3
+ metadata.gz: 1c466ba64c434d16b197dacfb48341ab14eca48ba5aefde4cf06df4f10be84fc
4
+ data.tar.gz: 3d470e57b3c94936ec31adce97e829550344e6345de423f0d3c51915d951d782
5
5
  SHA512:
6
- metadata.gz: 5f5e59c42893bf46ba3581f4a44952c919b56d94db139b558fd6fafa3d4c360d52d58741da99fb456afe72fb8679c27a4f22eaf551b014b4e774dd5cb31f39ff
7
- data.tar.gz: '08611be52a6cbbfb10ec656bf820bc3241afb626de9aa9fb2ce19f9efa21e3d5f38bfe6f5f1b86f7ec184355bd236505469705559d7e234c4d3a8f04d5b536fa'
6
+ metadata.gz: 271a7a12c932857ea8887e67d7303a008852cc700e2d3bea0cbf96a6f30c53b8cae442f62877f4ad89c215dfb5bae81c0948da6b3cd5c267304dc368d795e542
7
+ data.tar.gz: 826444b74fd01885c66389a1c08b8b2cb99617662c3e364453d4d1572fa0237447d70d196fc867bde6231bc11efd6b47c874619bafe1efa800bcc1304a055329
checksums.yaml.gz.sig ADDED
@@ -0,0 +1,3 @@
1
+ ��\`��W��y0!�l���i�Xu�����R|
2
+ #��v@���]�x�i�����G0#/öV�tC��,�|K�����ˈ�Xb3B�7Dlk� b{�tV
3
+ z!�%��BY��] �~!�q@P�����{oc������EsACf_�ѻ>��t�6� ?𯭴�v��E S����`��F�4�5,|q�ԃ�,[U+,
data/lib/.DS_Store ADDED
Binary file
Binary file
@@ -19,6 +19,7 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  require_relative 'frame'
22
+ require_relative 'message'
22
23
 
23
24
  module Protocol
24
25
  module WebSocket
@@ -29,6 +30,10 @@ module Protocol
29
30
  true
30
31
  end
31
32
 
33
+ def read_message(buffer)
34
+ return BinaryMessage.new(buffer)
35
+ end
36
+
32
37
  def apply(connection)
33
38
  connection.receive_binary(self)
34
39
  end
@@ -29,11 +29,41 @@ module Protocol
29
29
  def unpack
30
30
  data = super
31
31
 
32
- return data.unpack(FORMAT)
32
+ case data.length
33
+ when 0
34
+ [nil, ""]
35
+ when 1
36
+ raise ProtocolError, "invalid close frame length!"
37
+ else
38
+ code, reason = *data.unpack(FORMAT)
39
+
40
+ case code
41
+ when 0 .. 999, 1005 .. 1006, 1015, 5000 .. 0xFFFF
42
+ raise ProtocolError, "invalid close code!"
43
+ when 1004, 1016 .. 2999
44
+ raise ProtocolError, "reserved close code!"
45
+ end
46
+
47
+ reason.force_encoding(Encoding::UTF_8)
48
+
49
+ unless reason.valid_encoding?
50
+ raise ProtocolError, "invalid UTF-8 in close reason!"
51
+ end
52
+
53
+ [code, reason]
54
+ end
33
55
  end
34
56
 
35
57
  def pack(code, reason)
36
- super [code, reason].pack(FORMAT)
58
+ if code
59
+ unless reason.encoding == Encoding::UTF_8
60
+ reason = reason.encode(Encoding::UTF_8)
61
+ end
62
+
63
+ super [code, reason].pack(FORMAT)
64
+ else
65
+ super String.new(encoding: Encoding::BINARY)
66
+ end
37
67
  end
38
68
 
39
69
  def apply(connection)
@@ -23,18 +23,20 @@ require 'securerandom'
23
23
 
24
24
  module Protocol
25
25
  module WebSocket
26
+ # Wraps a framer and implements for implementing connection specific interactions like reading and writing text.
26
27
  class Connection
27
- # @option mask [String] 4-byte mask to be used for frames generated by this connection.
28
- def initialize(framer, mask: nil)
29
- if mask == true
30
- mask = SecureRandom.bytes(4)
31
- end
32
-
28
+ # @parameter mask [String] 4-byte mask to be used for frames generated by this connection.
29
+ def initialize(framer, mask: nil, **options)
33
30
  @framer = framer
34
31
  @mask = mask
35
32
 
36
33
  @state = :open
37
34
  @frames = []
35
+
36
+ @reserved = Frame::RESERVED
37
+
38
+ @reader = self
39
+ @writer = self
38
40
  end
39
41
 
40
42
  # The framer which is used for reading and writing frames.
@@ -43,9 +45,25 @@ module Protocol
43
45
  # The (optional) mask which is used when generating frames.
44
46
  attr :mask
45
47
 
48
+ # The allowed reserved bits:
49
+ attr :reserved
50
+
46
51
  # Buffered frames which form part of a complete message.
47
52
  attr_accessor :frames
48
53
 
54
+ attr_accessor :reader
55
+ attr_accessor :writer
56
+
57
+ def reserve!(bit)
58
+ if (@reserved & bit).zero?
59
+ raise "Unable to use #{bit}!"
60
+ end
61
+
62
+ @reserved &= ~bit
63
+
64
+ return true
65
+ end
66
+
49
67
  def flush
50
68
  @framer.flush
51
69
  end
@@ -54,8 +72,8 @@ module Protocol
54
72
  @state == :closed
55
73
  end
56
74
 
57
- def close
58
- send_close unless closed?
75
+ def close(code = Error::NO_ERROR, reason = "")
76
+ send_close(code, reason) unless closed?
59
77
 
60
78
  @framer.close
61
79
  end
@@ -65,6 +83,10 @@ module Protocol
65
83
 
66
84
  frame = @framer.read_frame
67
85
 
86
+ unless (frame.flags & @reserved).zero?
87
+ raise ProtocolError, "Received frame with reserved flags set!"
88
+ end
89
+
68
90
  yield frame if block_given?
69
91
 
70
92
  frame.apply(self)
@@ -82,6 +104,8 @@ module Protocol
82
104
 
83
105
  def write_frame(frame)
84
106
  @framer.write_frame(frame)
107
+
108
+ return frame
85
109
  end
86
110
 
87
111
  def receive_text(frame)
@@ -107,38 +131,16 @@ module Protocol
107
131
  raise ProtocolError, "Received unexpected continuation!"
108
132
  end
109
133
  end
110
-
111
- def send_text(buffer)
112
- frame = TextFrame.new(mask: @mask)
113
- frame.pack buffer
114
-
115
- write_frame(frame)
116
- end
117
-
118
- def send_binary(buffer)
119
- frame = BinaryFrame.new(mask: @mask)
120
- frame.pack buffer
121
-
122
- write_frame(frame)
123
- end
124
-
125
- def send_close(code = Error::NO_ERROR, message = nil)
126
- frame = CloseFrame.new(mask: @mask)
127
- frame.pack(code, message)
128
-
129
- self.write_frame(frame)
130
- self.flush
131
-
132
- @state = :closed
133
- end
134
-
134
+
135
135
  def receive_close(frame)
136
136
  @state = :closed
137
137
 
138
- code, message = frame.unpack
138
+ code, reason = frame.unpack
139
+
140
+ send_close(code, reason)
139
141
 
140
142
  if code and code != Error::NO_ERROR
141
- raise ClosedError.new message, code
143
+ raise ClosedError.new reason, code
142
144
  end
143
145
  end
144
146
 
@@ -161,7 +163,7 @@ module Protocol
161
163
 
162
164
  def receive_ping(frame)
163
165
  if @state != :closed
164
- write_frame(frame.reply)
166
+ write_frame(frame.reply(mask: @mask))
165
167
  else
166
168
  raise ProtocolError, "Cannot receive ping in state #{@state}"
167
169
  end
@@ -175,34 +177,60 @@ module Protocol
175
177
  warn "Unhandled frame #{frame.inspect}"
176
178
  end
177
179
 
178
- # @param buffer [String] a unicode or binary string.
179
- def write(buffer)
180
- # https://tools.ietf.org/html/rfc6455#section-5.6
180
+ def pack_text_frame(buffer, **options)
181
+ frame = TextFrame.new(mask: @mask)
182
+ frame.pack(buffer)
181
183
 
182
- # Text: The "Payload data" is text data encoded as UTF-8
183
- if buffer.encoding == Encoding::UTF_8
184
- send_text(buffer)
185
- else
186
- send_binary(buffer)
187
- end
184
+ return frame
188
185
  end
189
186
 
190
- # @return [String] a unicode or binary string.
191
- def read
192
- @framer.flush
187
+ def send_text(buffer, **options)
188
+ write_frame(@writer.pack_text_frame(buffer, **options))
189
+ end
190
+
191
+ def pack_binary_frame(buffer, **options)
192
+ frame = BinaryFrame.new(mask: @mask)
193
+ frame.pack(buffer)
193
194
 
194
- while read_frame
195
- if @frames.last&.finished?
196
- buffer = @frames.map(&:unpack).join
197
- @frames = []
198
-
199
- return buffer
195
+ return frame
196
+ end
197
+
198
+ def send_binary(buffer, **options)
199
+ write_frame(@writer.pack_binary_frame(buffer, **options))
200
+ end
201
+
202
+ def send_close(code = Error::NO_ERROR, reason = "")
203
+ frame = CloseFrame.new(mask: @mask)
204
+ frame.pack(code, reason)
205
+
206
+ self.write_frame(frame)
207
+ self.flush
208
+
209
+ @state = :closed
210
+ end
211
+
212
+ # Write a message to the connection.
213
+ # @parameter message [Message] The message to send.
214
+ def write(message, **options)
215
+ if message.is_a?(String)
216
+ if message.encoding == Encoding::UTF_8
217
+ return send_text(message, **options)
218
+ else
219
+ return send_binary(message, **options)
200
220
  end
201
221
  end
222
+
223
+ message.send(self, **options)
224
+ end
225
+
226
+ # The default implementation for reading a message buffer.
227
+ def unpack_frames(frames)
228
+ frames.map(&:unpack).join("")
202
229
  end
203
230
 
204
- # Deprecated.
205
- def next_message
231
+ # Read a message from the connection.
232
+ # @returns message [Message] The received message.
233
+ def read(**options)
206
234
  @framer.flush
207
235
 
208
236
  while read_frame
@@ -210,9 +238,14 @@ module Protocol
210
238
  frames = @frames
211
239
  @frames = []
212
240
 
213
- return frames
241
+ buffer = @reader.unpack_frames(frames, **options)
242
+ return frames.first.read_message(buffer)
214
243
  end
215
244
  end
245
+ rescue ProtocolError => error
246
+ send_close(error.code, error.message)
247
+
248
+ raise
216
249
  end
217
250
  end
218
251
  end
@@ -0,0 +1,34 @@
1
+ # Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'zlib'
22
+
23
+ module Protocol
24
+ module WebSocket
25
+ module Extension
26
+ module Compression
27
+ NAME = 'permessage-deflate'
28
+
29
+ # Zlib is not capable of handling < 9 window bits.
30
+ MINIMUM_WINDOW_BITS = 9
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,105 @@
1
+ # Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'constants'
22
+
23
+ module Protocol
24
+ module WebSocket
25
+ module Extension
26
+ module Compression
27
+ class Deflate
28
+ # Client writing to server.
29
+ def self.client(parent, client_max_window_bits: 15, client_no_context_takeover: false, **options)
30
+ self.new(parent,
31
+ window_bits: client_max_window_bits,
32
+ context_takeover: !client_no_context_takeover,
33
+ **options
34
+ )
35
+ end
36
+
37
+ # Server writing to client.
38
+ def self.server(parent, server_max_window_bits: 15, server_no_context_takeover: false, **options)
39
+ self.new(parent,
40
+ window_bits: server_max_window_bits,
41
+ context_takeover: !server_no_context_takeover,
42
+ **options
43
+ )
44
+ end
45
+
46
+ def initialize(parent, level: Zlib::DEFAULT_COMPRESSION, memory_level: Zlib::DEF_MEM_LEVEL, strategy: Zlib::DEFAULT_STRATEGY, window_bits: 15, context_takeover: true, **options)
47
+ @parent = parent
48
+
49
+ @deflate = nil
50
+
51
+ @level = level
52
+ @memory_level = memory_level
53
+ @strategy = strategy
54
+
55
+ if window_bits < MINIMUM_WINDOW_BITS
56
+ window_bits = MINIMUM_WINDOW_BITS
57
+ end
58
+
59
+ @window_bits = window_bits
60
+ @context_takeover = context_takeover
61
+ end
62
+
63
+ def inspect
64
+ "#<#{self.class} window_bits=#{@window_bits} context_takeover=#{@context_takeover}>"
65
+ end
66
+
67
+ attr :window_bits
68
+ attr :context_takeover
69
+
70
+ def pack_text_frame(buffer, compress: true, **options)
71
+ buffer = self.deflate(buffer)
72
+
73
+ frame = @parent.pack_text_frame(buffer, **options)
74
+
75
+ frame.flags |= Frame::RSV1
76
+
77
+ return frame
78
+ end
79
+
80
+ def pack_binary_frame(buffer, compress: false, **options)
81
+ buffer = self.deflate(buffer)
82
+
83
+ frame = @parent.pack_binary_frame(buffer, **options)
84
+
85
+ frame.flags |= Frame::RSV1
86
+
87
+ return frame
88
+ end
89
+
90
+ private
91
+
92
+ def deflate(buffer)
93
+ deflate = @deflate || Zlib::Deflate.new(@level, -@window_bits, @memory_level, @strategy)
94
+
95
+ if @context_takeover
96
+ @deflate = deflate
97
+ end
98
+
99
+ return deflate.deflate(buffer, Zlib::SYNC_FLUSH)[0...-4]
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,91 @@
1
+ # Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'constants'
22
+
23
+ module Protocol
24
+ module WebSocket
25
+ module Extension
26
+ module Compression
27
+ class Inflate
28
+ # Client reading from server.
29
+ def self.client(parent, server_max_window_bits: 15, server_no_context_takeover: false, **options)
30
+ self.new(parent,
31
+ window_bits: server_max_window_bits,
32
+ context_takeover: !server_no_context_takeover,
33
+ )
34
+ end
35
+
36
+ # Server reading from client.
37
+ def self.server(parent, client_max_window_bits: 15, client_no_context_takeover: false, **options)
38
+ self.new(parent,
39
+ window_bits: client_max_window_bits,
40
+ context_takeover: !client_no_context_takeover,
41
+ )
42
+ end
43
+
44
+ TRAILER = [0x00, 0x00, 0xff, 0xff].pack('C*')
45
+
46
+ def initialize(parent, context_takeover: true, window_bits: 15)
47
+ @parent = parent
48
+
49
+ @inflate = nil
50
+
51
+ if window_bits < MINIMUM_WINDOW_BITS
52
+ window_bits = MINIMUM_WINDOW_BITS
53
+ end
54
+
55
+ @window_bits = window_bits
56
+ @context_takeover = context_takeover
57
+ end
58
+
59
+ attr :window_bits
60
+ attr :context_takeover
61
+
62
+ def unpack_frames(frames, **options)
63
+ buffer = @parent.unpack_frames(frames, **options)
64
+
65
+ frame = frames.first
66
+
67
+ if frame.flags & Frame::RSV1
68
+ buffer = self.inflate(buffer)
69
+ end
70
+
71
+ frame.flags &= ~Frame::RSV1
72
+
73
+ return buffer
74
+ end
75
+
76
+ private
77
+
78
+ def inflate(buffer)
79
+ inflate = @inflate || Zlib::Inflate.new(-@window_bits)
80
+
81
+ if @context_takeover
82
+ @inflate = inflate
83
+ end
84
+
85
+ return inflate.inflate(buffer + TRAILER)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,137 @@
1
+ # Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'compression/constants'
22
+ require_relative 'compression/inflate'
23
+ require_relative 'compression/deflate'
24
+
25
+ module Protocol
26
+ module WebSocket
27
+ module Extension
28
+ module Compression
29
+ # Client offer to server, construct a list of requested compression parameters suitable for the `Sec-WebSocket-Extensions` header.
30
+ # @returns [Array(String)] a list of compression parameters suitable to send to the server.
31
+ def self.offer(client_max_window_bits: true, server_max_window_bits: true, client_no_context_takeover: false, server_no_context_takeover: false)
32
+
33
+ header = [NAME]
34
+
35
+ case client_max_window_bits
36
+ when 8..15
37
+ header << "client_max_window_bits=#{client_max_window_bits}"
38
+ when true
39
+ header << 'client_max_window_bits'
40
+ else
41
+ raise ArgumentError, "Invalid local maximum window bits!"
42
+ end
43
+
44
+ if client_no_context_takeover
45
+ header << 'client_no_context_takeover'
46
+ end
47
+
48
+ case server_max_window_bits
49
+ when 8..15
50
+ header << "server_max_window_bits=#{server_max_window_bits}"
51
+ when true
52
+ # Default (unspecified) to the server maximum window bits.
53
+ else
54
+ raise ArgumentError, "Invalid remote maximum window bits!"
55
+ end
56
+
57
+ if server_no_context_takeover
58
+ header << 'server_no_context_takeover'
59
+ end
60
+
61
+ return header
62
+ end
63
+
64
+ # Negotiate on the server a response to client based on the incoming client offer.
65
+ # @parameter options [Hash] a hash of options which are accepted by the server.
66
+ # @returns [Array(String)] a list of compression parameters suitable to send back to the client.
67
+ def self.negotiate(arguments, **options)
68
+ header = [NAME]
69
+
70
+ arguments.each do |key, value|
71
+ case key
72
+ when "server_no_context_takeover"
73
+ options[:server_no_context_takeover] = true
74
+ header << key
75
+ when "client_no_context_takeover"
76
+ options[:client_no_context_takeover] = true
77
+ header << key
78
+ when "server_max_window_bits"
79
+ value = Integer(value || 15)
80
+ value = MINIMUM_WINDOW_BITS if value < MINIMUM_WINDOW_BITS
81
+ options[:server_max_window_bits] = value
82
+ header << "server_max_window_bits=#{value}"
83
+ when "client_max_window_bits"
84
+ value = Integer(value || 15)
85
+ value = MINIMUM_WINDOW_BITS if value < MINIMUM_WINDOW_BITS
86
+ options[:client_max_window_bits] = value
87
+ header << "client_max_window_bits=#{value}"
88
+ else
89
+ raise ArgumentError, "Unknown option #{key}!"
90
+ end
91
+ end
92
+
93
+ # The header which represents the final accepted/negotiated configuration.
94
+ return header, options
95
+ end
96
+
97
+ # @parameter options [Hash] a hash of options which are accepted by the server.
98
+ def self.server(connection, **options)
99
+ connection.reserve!(Frame::RSV1)
100
+
101
+ connection.reader = Inflate.server(connection.reader, **options)
102
+ connection.writer = Deflate.server(connection.writer, **options)
103
+ end
104
+
105
+ # Accept on the client, the negotiated server response.
106
+ # @parameter options [Hash] a hash of options which are accepted by the client.
107
+ # @parameter arguments [Array(String)] a list of compression parameters as accepted/negotiated by the server.
108
+ def self.accept(arguments, **options)
109
+ arguments.each do |key, value|
110
+ case key
111
+ when "server_no_context_takeover"
112
+ options[:server_no_context_takeover] = true
113
+ when "client_no_context_takeover"
114
+ options[:client_no_context_takeover] = true
115
+ when "server_max_window_bits"
116
+ options[:server_max_window_bits] = Integer(value || 15)
117
+ when "client_max_window_bits"
118
+ options[:client_max_window_bits] = Integer(value || 15)
119
+ else
120
+ raise ArgumentError, "Unknown option #{key}!"
121
+ end
122
+ end
123
+
124
+ return options
125
+ end
126
+
127
+ # @parameter options [Hash] a hash of options which are accepted by the client.
128
+ def self.client(connection, **options)
129
+ connection.reserve!(Frame::RSV1)
130
+
131
+ connection.reader = Inflate.client(connection.reader, **options)
132
+ connection.writer = Deflate.client(connection.writer, **options)
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end