protocol-websocket 0.7.4 → 0.9.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 +4 -4
- checksums.yaml.gz.sig +3 -0
- data/lib/.DS_Store +0 -0
- data/lib/protocol/.DS_Store +0 -0
- data/lib/protocol/websocket/binary_frame.rb +5 -0
- data/lib/protocol/websocket/close_frame.rb +32 -2
- data/lib/protocol/websocket/connection.rb +90 -57
- data/lib/protocol/websocket/extension/compression/constants.rb +34 -0
- data/lib/protocol/websocket/extension/compression/deflate.rb +105 -0
- data/lib/protocol/websocket/extension/compression/inflate.rb +91 -0
- data/lib/protocol/websocket/extension/compression.rb +137 -0
- data/lib/protocol/websocket/extensions.md +13 -0
- data/lib/protocol/websocket/extensions.rb +148 -0
- data/lib/protocol/websocket/frame.rb +43 -15
- data/lib/protocol/websocket/framer.rb +7 -3
- data/lib/protocol/websocket/headers.rb +5 -1
- data/lib/protocol/websocket/message.rb +61 -0
- data/lib/protocol/websocket/ping_frame.rb +5 -2
- data/lib/protocol/websocket/pong_frame.rb +1 -0
- data/lib/protocol/websocket/text_frame.rb +13 -6
- data/lib/protocol/websocket/version.rb +1 -1
- data/lib/protocol/websocket.rb +1 -0
- data.tar.gz.sig +0 -0
- metadata +56 -36
- metadata.gz.sig +0 -0
- data/.gitignore +0 -13
- data/.rspec +0 -3
- data/.travis.yml +0 -19
- data/Gemfile +0 -4
- data/README.md +0 -64
- data/Rakefile +0 -6
- data/protocol-websocket.gemspec +0 -28
@@ -0,0 +1,13 @@
|
|
1
|
+
# Extensions
|
2
|
+
|
3
|
+
WebSockets have a mechanism for implementing extensions. The only published extension is for per-message compression. It operates on complete messages rather than individual frames.
|
4
|
+
|
5
|
+
## Setup
|
6
|
+
|
7
|
+
Clients need to define a set of extensions they want to support. The server then receives this via the `Sec-WebSocket-Extensions` header which includes a list of:
|
8
|
+
|
9
|
+
Name, Options
|
10
|
+
|
11
|
+
The server processes this and returns a subset of accepted `(Name, Options)`. It also instantiates the extensions and applies them to the server connection object.
|
12
|
+
|
13
|
+
The client receives a list of accepted `(Name, Options)` and instantiates the extensions and applies them to the client connection object.
|
@@ -0,0 +1,148 @@
|
|
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 'extension/compression'
|
22
|
+
require_relative 'headers'
|
23
|
+
|
24
|
+
module Protocol
|
25
|
+
module WebSocket
|
26
|
+
module Extensions
|
27
|
+
def self.parse(headers)
|
28
|
+
return to_enum(:parse, headers) unless block_given?
|
29
|
+
|
30
|
+
headers.each do |header|
|
31
|
+
name, *arguments = header.split(/\s*;\s*/)
|
32
|
+
|
33
|
+
arguments = arguments.map do |argument|
|
34
|
+
argument.split('=', 2)
|
35
|
+
end
|
36
|
+
|
37
|
+
yield name, arguments
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Client
|
42
|
+
def self.default
|
43
|
+
self.new([
|
44
|
+
[Extension::Compression, {}]
|
45
|
+
])
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(extensions = [])
|
49
|
+
@extensions = extensions
|
50
|
+
@accepted = []
|
51
|
+
end
|
52
|
+
|
53
|
+
attr :extensions
|
54
|
+
attr :accepted
|
55
|
+
|
56
|
+
def named
|
57
|
+
@extensions.map do |extension|
|
58
|
+
[extension.first::NAME, extension]
|
59
|
+
end.to_h
|
60
|
+
end
|
61
|
+
|
62
|
+
def offer
|
63
|
+
@extensions.each do |extension, options|
|
64
|
+
if header = extension.offer(**options)
|
65
|
+
yield header
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def accept(headers)
|
71
|
+
named = self.named
|
72
|
+
|
73
|
+
# Each response header should map to at least one extension.
|
74
|
+
Extensions.parse(headers) do |name, arguments|
|
75
|
+
if extension = named.delete(name)
|
76
|
+
klass, options = extension
|
77
|
+
|
78
|
+
options = klass.accept(arguments, **options)
|
79
|
+
|
80
|
+
@accepted << [klass, options]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def apply(connection)
|
86
|
+
@accepted.each do |(klass, options)|
|
87
|
+
klass.client(connection, **options)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class Server
|
93
|
+
def self.default
|
94
|
+
self.new([
|
95
|
+
[Extension::Compression, {}]
|
96
|
+
])
|
97
|
+
end
|
98
|
+
|
99
|
+
def initialize(extensions)
|
100
|
+
@extensions = extensions
|
101
|
+
@accepted = []
|
102
|
+
end
|
103
|
+
|
104
|
+
attr :extensions
|
105
|
+
attr :accepted
|
106
|
+
|
107
|
+
def named
|
108
|
+
@extensions.map do |extension|
|
109
|
+
[extension.first::NAME, extension]
|
110
|
+
end.to_h
|
111
|
+
end
|
112
|
+
|
113
|
+
def accept(headers)
|
114
|
+
extensions = []
|
115
|
+
|
116
|
+
named = self.named
|
117
|
+
response = []
|
118
|
+
|
119
|
+
# Each response header should map to at least one extension.
|
120
|
+
Extensions.parse(headers) do |name, arguments|
|
121
|
+
if extension = named[name]
|
122
|
+
klass, options = extension
|
123
|
+
|
124
|
+
if result = klass.negotiate(arguments, **options)
|
125
|
+
header, options = result
|
126
|
+
|
127
|
+
# The extension is accepted and no further offers will be considered:
|
128
|
+
named.delete(name)
|
129
|
+
|
130
|
+
yield header
|
131
|
+
|
132
|
+
@accepted << [klass, options]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
return headers
|
138
|
+
end
|
139
|
+
|
140
|
+
def apply(connection)
|
141
|
+
@accepted.reverse_each do |(klass, options)|
|
142
|
+
klass.server(connection, **options)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -25,11 +25,22 @@ module Protocol
|
|
25
25
|
class Frame
|
26
26
|
include Comparable
|
27
27
|
|
28
|
-
|
28
|
+
RSV1 = 0b0100
|
29
|
+
RSV2 = 0b0010
|
30
|
+
RSV3 = 0b0001
|
31
|
+
RESERVED = RSV1 | RSV2 | RSV3
|
29
32
|
|
30
|
-
|
31
|
-
|
33
|
+
OPCODE = 0
|
34
|
+
|
35
|
+
# @parameter length [Integer] The length of the payload, or nil if the header has not been read yet.
|
36
|
+
# @parameter mask [Boolean | String] An optional 4-byte string which is used to mask the payload.
|
37
|
+
def initialize(finished = true, payload = nil, flags: 0, opcode: self.class::OPCODE, mask: false)
|
38
|
+
if mask == true
|
39
|
+
mask = SecureRandom.bytes(4)
|
40
|
+
end
|
41
|
+
|
32
42
|
@finished = finished
|
43
|
+
@flags = flags
|
33
44
|
@opcode = opcode
|
34
45
|
@mask = mask
|
35
46
|
@length = payload&.bytesize
|
@@ -41,11 +52,11 @@ module Protocol
|
|
41
52
|
end
|
42
53
|
|
43
54
|
def to_ary
|
44
|
-
[@finished, @opcode, @mask, @length, @payload]
|
55
|
+
[@finished, @flags, @opcode, @mask, @length, @payload]
|
45
56
|
end
|
46
57
|
|
47
58
|
def control?
|
48
|
-
@opcode & 0x8
|
59
|
+
@opcode & 0x8 != 0
|
49
60
|
end
|
50
61
|
|
51
62
|
def data?
|
@@ -82,6 +93,7 @@ module Protocol
|
|
82
93
|
# +---------------------------------------------------------------+
|
83
94
|
|
84
95
|
attr_accessor :finished
|
96
|
+
attr_accessor :flags
|
85
97
|
attr_accessor :opcode
|
86
98
|
attr_accessor :mask
|
87
99
|
attr_accessor :length
|
@@ -93,9 +105,9 @@ module Protocol
|
|
93
105
|
if length.bit_length > 63
|
94
106
|
raise ProtocolError, "Frame length #{@length} bigger than allowed maximum!"
|
95
107
|
end
|
96
|
-
|
108
|
+
|
97
109
|
if @mask
|
98
|
-
@payload = String.new
|
110
|
+
@payload = String.new(encoding: Encoding::BINARY)
|
99
111
|
|
100
112
|
for i in 0...data.bytesize do
|
101
113
|
@payload << (data.getbyte(i) ^ mask.getbyte(i % 4))
|
@@ -106,11 +118,13 @@ module Protocol
|
|
106
118
|
@payload = data
|
107
119
|
@length = length
|
108
120
|
end
|
121
|
+
|
122
|
+
return self
|
109
123
|
end
|
110
124
|
|
111
125
|
def unpack
|
112
|
-
if @mask
|
113
|
-
data = String.new
|
126
|
+
if @mask and !@payload.empty?
|
127
|
+
data = String.new(encoding: Encoding::BINARY)
|
114
128
|
|
115
129
|
for i in 0...@payload.bytesize do
|
116
130
|
data << (@payload.getbyte(i) ^ @mask.getbyte(i % 4))
|
@@ -130,19 +144,33 @@ module Protocol
|
|
130
144
|
byte = buffer.unpack("C").first
|
131
145
|
|
132
146
|
finished = (byte & 0b1000_0000 != 0)
|
133
|
-
|
147
|
+
flags = (byte & 0b0111_0000) >> 4
|
134
148
|
opcode = byte & 0b0000_1111
|
135
149
|
|
136
|
-
|
150
|
+
if (0x3 .. 0x7).include?(opcode)
|
151
|
+
raise ProtocolError, "non-control opcode = #{opcode} is reserved!"
|
152
|
+
elsif (0xB .. 0xF).include?(opcode)
|
153
|
+
raise ProtocolError, "control opcode = #{opcode} is reserved!"
|
154
|
+
end
|
155
|
+
|
156
|
+
return finished, flags, opcode
|
137
157
|
end
|
138
158
|
|
139
|
-
def self.read(finished, opcode, stream, maximum_frame_size)
|
159
|
+
def self.read(finished, flags, opcode, stream, maximum_frame_size)
|
140
160
|
buffer = stream.read(1) or raise EOFError, "Could not read header!"
|
141
161
|
byte = buffer.unpack("C").first
|
142
162
|
|
143
163
|
mask = (byte & 0b1000_0000 != 0)
|
144
164
|
length = byte & 0b0111_1111
|
145
165
|
|
166
|
+
if opcode & 0x8 != 0
|
167
|
+
if length > 125
|
168
|
+
raise ProtocolError, "Invalid control frame payload length: #{length} > 125!"
|
169
|
+
elsif !finished
|
170
|
+
raise ProtocolError, "Fragmented control frame!"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
146
174
|
if length == 126
|
147
175
|
buffer = stream.read(2) or raise EOFError, "Could not read length!"
|
148
176
|
length = buffer.unpack('n').first
|
@@ -165,11 +193,11 @@ module Protocol
|
|
165
193
|
raise EOFError, "Incorrect payload length: #{@length} != #{@payload.bytesize}!"
|
166
194
|
end
|
167
195
|
|
168
|
-
return self.new(finished, payload, opcode: opcode, mask: mask)
|
196
|
+
return self.new(finished, payload, flags: flags, opcode: opcode, mask: mask)
|
169
197
|
end
|
170
198
|
|
171
199
|
def write(stream)
|
172
|
-
buffer = String.new
|
200
|
+
buffer = String.new(encoding: Encoding::BINARY)
|
173
201
|
|
174
202
|
if @payload&.bytesize != @length
|
175
203
|
raise ProtocolError, "Invalid payload length: #{@length} != #{@payload.bytesize} for #{self}!"
|
@@ -188,7 +216,7 @@ module Protocol
|
|
188
216
|
end
|
189
217
|
|
190
218
|
buffer << [
|
191
|
-
(@finished ? 0b1000_0000 : 0) | @opcode,
|
219
|
+
(@finished ? 0b1000_0000 : 0) | (@flags << 4) | @opcode,
|
192
220
|
(@mask ? 0b1000_0000 : 0) | short_length,
|
193
221
|
].pack('CC')
|
194
222
|
|
@@ -29,7 +29,7 @@ require_relative 'pong_frame'
|
|
29
29
|
|
30
30
|
module Protocol
|
31
31
|
module WebSocket
|
32
|
-
# HTTP/2 frame type mapping as defined by the spec
|
32
|
+
# HTTP/2 frame type mapping as defined by the spec.
|
33
33
|
FRAMES = {
|
34
34
|
0x0 => ContinuationFrame,
|
35
35
|
0x1 => TextFrame,
|
@@ -39,8 +39,10 @@ module Protocol
|
|
39
39
|
0xA => PongFrame,
|
40
40
|
}.freeze
|
41
41
|
|
42
|
+
# The maximum allowed frame size in bytes.
|
42
43
|
MAXIMUM_ALLOWED_FRAME_SIZE = 2**63
|
43
44
|
|
45
|
+
# Wraps an underlying {Async::IO::Stream} for reading and writing binary data into structured frames.
|
44
46
|
class Framer
|
45
47
|
def initialize(stream, frames = FRAMES)
|
46
48
|
@stream = stream
|
@@ -55,13 +57,15 @@ module Protocol
|
|
55
57
|
@stream.flush
|
56
58
|
end
|
57
59
|
|
60
|
+
# Read a frame from the underlying stream.
|
61
|
+
# @returns [Frame]
|
58
62
|
def read_frame(maximum_frame_size = MAXIMUM_ALLOWED_FRAME_SIZE)
|
59
63
|
# Read the header:
|
60
|
-
finished, opcode = read_header
|
64
|
+
finished, flags, opcode = read_header
|
61
65
|
|
62
66
|
# Read the frame:
|
63
67
|
klass = @frames[opcode] || Frame
|
64
|
-
frame = klass.read(finished, opcode, @stream, maximum_frame_size)
|
68
|
+
frame = klass.read(finished, flags, opcode, @stream, maximum_frame_size)
|
65
69
|
|
66
70
|
return frame
|
67
71
|
end
|
@@ -27,13 +27,17 @@ module Protocol
|
|
27
27
|
# The protocol string used for the `upgrade:` header (HTTP/1) and `:protocol` pseudo-header (HTTP/2).
|
28
28
|
PROTOCOL = "websocket".freeze
|
29
29
|
|
30
|
-
#
|
30
|
+
# The WebSocket protocol header, used for application level protocol negotiation.
|
31
31
|
SEC_WEBSOCKET_PROTOCOL = 'sec-websocket-protocol'.freeze
|
32
|
+
|
33
|
+
# The WebSocket version header. Used for negotiating binary protocol version.
|
32
34
|
SEC_WEBSOCKET_VERSION = 'sec-websocket-version'.freeze
|
33
35
|
|
34
36
|
SEC_WEBSOCKET_KEY = 'sec-websocket-key'.freeze
|
35
37
|
SEC_WEBSOCKET_ACCEPT = 'sec-websocket-accept'.freeze
|
36
38
|
|
39
|
+
SEC_WEBSOCKET_EXTENSIONS = 'sec-websocket-extensions'.freeze
|
40
|
+
|
37
41
|
module Nounce
|
38
42
|
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
39
43
|
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# Copyright, 2022, 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 'frame'
|
22
|
+
|
23
|
+
module Protocol
|
24
|
+
module WebSocket
|
25
|
+
class Message
|
26
|
+
def initialize(buffer)
|
27
|
+
@buffer = buffer
|
28
|
+
end
|
29
|
+
|
30
|
+
attr :buffer
|
31
|
+
|
32
|
+
def size
|
33
|
+
@buffer.bytesize
|
34
|
+
end
|
35
|
+
|
36
|
+
def == other
|
37
|
+
@buffer == other
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_str
|
41
|
+
@buffer
|
42
|
+
end
|
43
|
+
|
44
|
+
def encoding
|
45
|
+
@buffer.encoding
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class TextMessage < Message
|
50
|
+
def send(connection, **options)
|
51
|
+
connection.send_text(@buffer, **options)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class BinaryMessage < Message
|
56
|
+
def send(connection, **options)
|
57
|
+
connection.send_binary(@buffer, **options)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -26,10 +26,13 @@ module Protocol
|
|
26
26
|
class PingFrame < Frame
|
27
27
|
OPCODE = 0x9
|
28
28
|
|
29
|
-
|
30
|
-
|
29
|
+
# Generate a suitable reply.
|
30
|
+
# @returns [PongFrame]
|
31
|
+
def reply(**options)
|
32
|
+
PongFrame.new(true, self.unpack, **options)
|
31
33
|
end
|
32
34
|
|
35
|
+
# Apply this frame to the specified connection.
|
33
36
|
def apply(connection)
|
34
37
|
connection.receive_ping(self)
|
35
38
|
end
|
@@ -19,9 +19,11 @@
|
|
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
|
26
|
+
# Implements the text frame for sending and receiving text.
|
25
27
|
class TextFrame < Frame
|
26
28
|
OPCODE = 0x1
|
27
29
|
|
@@ -29,14 +31,19 @@ module Protocol
|
|
29
31
|
true
|
30
32
|
end
|
31
33
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
34
|
+
# Decode the binary buffer into a suitable text message.
|
35
|
+
# @parameter buffer [String] The binary data to unpack.
|
36
|
+
def read_message(buffer)
|
37
|
+
buffer.force_encoding(Encoding::UTF_8)
|
38
|
+
|
39
|
+
unless buffer.valid_encoding?
|
40
|
+
raise ProtocolError, "invalid UTF-8 in text frame!"
|
41
|
+
end
|
42
|
+
|
43
|
+
return TextMessage.new(buffer)
|
38
44
|
end
|
39
45
|
|
46
|
+
# Apply this frame to the specified connection.
|
40
47
|
def apply(connection)
|
41
48
|
connection.receive_text(self)
|
42
49
|
end
|
data/lib/protocol/websocket.rb
CHANGED
data.tar.gz.sig
ADDED
Binary file
|
metadata
CHANGED
@@ -1,14 +1,47 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: protocol-websocket
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
|
-
|
8
|
+
- Aurora
|
9
|
+
- Soumya
|
10
|
+
- Olle Jonsson
|
11
|
+
- William T. Nelson
|
12
|
+
autorequire:
|
9
13
|
bindir: bin
|
10
|
-
cert_chain:
|
11
|
-
|
14
|
+
cert_chain:
|
15
|
+
- |
|
16
|
+
-----BEGIN CERTIFICATE-----
|
17
|
+
MIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11
|
18
|
+
ZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK
|
19
|
+
CZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz
|
20
|
+
MjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd
|
21
|
+
MBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj
|
22
|
+
bzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB
|
23
|
+
igKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2
|
24
|
+
9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW
|
25
|
+
sGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE
|
26
|
+
e5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN
|
27
|
+
XibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss
|
28
|
+
RZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn
|
29
|
+
tUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM
|
30
|
+
zp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW
|
31
|
+
xm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O
|
32
|
+
BBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs
|
33
|
+
aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs
|
34
|
+
aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE
|
35
|
+
cBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl
|
36
|
+
xCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/
|
37
|
+
c1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp
|
38
|
+
8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws
|
39
|
+
JkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP
|
40
|
+
eX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt
|
41
|
+
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
|
42
|
+
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
43
|
+
-----END CERTIFICATE-----
|
44
|
+
date: 2022-08-22 00:00:00.000000000 Z
|
12
45
|
dependencies:
|
13
46
|
- !ruby/object:Gem::Dependency
|
14
47
|
name: protocol-http
|
@@ -39,7 +72,7 @@ dependencies:
|
|
39
72
|
- !ruby/object:Gem::Version
|
40
73
|
version: '0.2'
|
41
74
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
75
|
+
name: bundler
|
43
76
|
requirement: !ruby/object:Gem::Requirement
|
44
77
|
requirements:
|
45
78
|
- - ">="
|
@@ -53,7 +86,7 @@ dependencies:
|
|
53
86
|
- !ruby/object:Gem::Version
|
54
87
|
version: '0'
|
55
88
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
89
|
+
name: covered
|
57
90
|
requirement: !ruby/object:Gem::Requirement
|
58
91
|
requirements:
|
59
92
|
- - ">="
|
@@ -67,65 +100,52 @@ dependencies:
|
|
67
100
|
- !ruby/object:Gem::Version
|
68
101
|
version: '0'
|
69
102
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '10.0'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '10.0'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: rspec
|
103
|
+
name: sus
|
85
104
|
requirement: !ruby/object:Gem::Requirement
|
86
105
|
requirements:
|
87
106
|
- - "~>"
|
88
107
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
108
|
+
version: 0.9.1
|
90
109
|
type: :development
|
91
110
|
prerelease: false
|
92
111
|
version_requirements: !ruby/object:Gem::Requirement
|
93
112
|
requirements:
|
94
113
|
- - "~>"
|
95
114
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
97
|
-
description:
|
115
|
+
version: 0.9.1
|
116
|
+
description:
|
98
117
|
email:
|
99
|
-
- samuel.williams@oriontransfer.co.nz
|
100
118
|
executables: []
|
101
119
|
extensions: []
|
102
120
|
extra_rdoc_files: []
|
103
121
|
files:
|
104
|
-
-
|
105
|
-
-
|
106
|
-
- ".travis.yml"
|
107
|
-
- Gemfile
|
108
|
-
- README.md
|
109
|
-
- Rakefile
|
122
|
+
- lib/.DS_Store
|
123
|
+
- lib/protocol/.DS_Store
|
110
124
|
- lib/protocol/websocket.rb
|
111
125
|
- lib/protocol/websocket/binary_frame.rb
|
112
126
|
- lib/protocol/websocket/close_frame.rb
|
113
127
|
- lib/protocol/websocket/connection.rb
|
114
128
|
- lib/protocol/websocket/continuation_frame.rb
|
115
129
|
- lib/protocol/websocket/error.rb
|
130
|
+
- lib/protocol/websocket/extension/compression.rb
|
131
|
+
- lib/protocol/websocket/extension/compression/constants.rb
|
132
|
+
- lib/protocol/websocket/extension/compression/deflate.rb
|
133
|
+
- lib/protocol/websocket/extension/compression/inflate.rb
|
134
|
+
- lib/protocol/websocket/extensions.md
|
135
|
+
- lib/protocol/websocket/extensions.rb
|
116
136
|
- lib/protocol/websocket/frame.rb
|
117
137
|
- lib/protocol/websocket/framer.rb
|
118
138
|
- lib/protocol/websocket/headers.rb
|
139
|
+
- lib/protocol/websocket/message.rb
|
119
140
|
- lib/protocol/websocket/ping_frame.rb
|
120
141
|
- lib/protocol/websocket/pong_frame.rb
|
121
142
|
- lib/protocol/websocket/text_frame.rb
|
122
143
|
- lib/protocol/websocket/version.rb
|
123
|
-
- protocol-websocket.gemspec
|
124
144
|
homepage: https://github.com/socketry/protocol-websocket
|
125
145
|
licenses:
|
126
146
|
- MIT
|
127
147
|
metadata: {}
|
128
|
-
post_install_message:
|
148
|
+
post_install_message:
|
129
149
|
rdoc_options: []
|
130
150
|
require_paths:
|
131
151
|
- lib
|
@@ -133,15 +153,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
133
153
|
requirements:
|
134
154
|
- - ">="
|
135
155
|
- !ruby/object:Gem::Version
|
136
|
-
version:
|
156
|
+
version: 2.5.0
|
137
157
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
158
|
requirements:
|
139
159
|
- - ">="
|
140
160
|
- !ruby/object:Gem::Version
|
141
161
|
version: '0'
|
142
162
|
requirements: []
|
143
|
-
rubygems_version: 3.
|
144
|
-
signing_key:
|
163
|
+
rubygems_version: 3.3.7
|
164
|
+
signing_key:
|
145
165
|
specification_version: 4
|
146
166
|
summary: A low level implementation of the WebSocket protocol.
|
147
167
|
test_files: []
|
metadata.gz.sig
ADDED
Binary file
|
data/.gitignore
DELETED
data/.rspec
DELETED