protocol-websocket 0.1.0 → 0.2.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
2
  SHA256:
3
- metadata.gz: 7a0f15089bc637ca5755d75a9cea1f219ad1b4903ad7d267f1faf26021bf050d
4
- data.tar.gz: c1e73cce6d131bbb0ef398144c48b08ea6439c46268ff1c12ab1f12679b1c8aa
3
+ metadata.gz: cbd73109c7ac70b53855864e3f3413473c4b375a46378b128e65c0a77f7c4881
4
+ data.tar.gz: 13c1bbfdbe82d21a03af3dc93944302a8cd5eec1a06154ccd8bfb44a8b251f73
5
5
  SHA512:
6
- metadata.gz: 0a2f6af33312ea2d00a13124422344a7dfbd1ba01077ffd8747ba8ab7125626bd6d2a8dfe69b28d29417f790dc247c49e87174bb4a74fe2b87919f27c54721f7
7
- data.tar.gz: 52b56ed1c83b0fe0869fffa67a90ea01650020104eef81dce01ee151e911035df6c8b5df4a493fac36f76a6e1a9cd4a744cb046373e8139cd23ff9ebf34409d7
6
+ metadata.gz: 33deb526b4c97ff9bd8887065dadab6750ee10022748c3cc56b240e0f0e3346fbd86ae92ab2c301de82fdbad539e47e4f2558567465bd193a13cf555fc633866
7
+ data.tar.gz: 857c58853914db3e6f5ab6924f5ac593cb094f7818553a2400ff1b80af07f2aece6e0ddcc044b39f684b6749a1c427527b5bffc862e8270b3eb012da78d40030
@@ -0,0 +1,33 @@
1
+ # Copyright, 2019, 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 BinaryFrame < Frame
26
+ OPCODE = 0x2
27
+
28
+ def apply(connection)
29
+ connection.receive_binary(self)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,44 @@
1
+ # Copyright, 2019, 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 CloseFrame < Frame
26
+ OPCODE = 0x8
27
+ FORMAT = "na*"
28
+
29
+ def unpack
30
+ data = super
31
+
32
+ return data.unpack(FORMAT)
33
+ end
34
+
35
+ def pack(code, reason)
36
+ super [code, reason].pack(FORMAT)
37
+ end
38
+
39
+ def apply(connection)
40
+ connection.receive_close(self)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,172 @@
1
+ # Copyright, 2019, 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 'framer'
22
+
23
+ module Protocol
24
+ module WebSocket
25
+ class Connection
26
+ def initialize(framer)
27
+ @framer = framer
28
+ @state = :open
29
+ @frames = []
30
+ end
31
+
32
+ attr :framer
33
+
34
+ # Buffered frames which form part of a complete message.
35
+ attr_accessor :frames
36
+
37
+ def closed?
38
+ @state == :closed
39
+ end
40
+
41
+ def close
42
+ send_close
43
+
44
+ @framer.close
45
+ end
46
+
47
+ def read_frame
48
+ return nil if closed?
49
+
50
+ frame = @framer.read_frame
51
+
52
+ yield frame if block_given?
53
+
54
+ frame.apply(self)
55
+
56
+ return frame
57
+ rescue ProtocolError => error
58
+ send_close(error.code, error.message)
59
+
60
+ raise
61
+ rescue
62
+ send_close(Error::PROTOCOL_ERROR, $!.message)
63
+
64
+ raise
65
+ end
66
+
67
+ def write_frame(frame)
68
+ @framer.write_frame(frame)
69
+ end
70
+
71
+ def receive_text(frame)
72
+ if @frames.empty?
73
+ @frames << frame
74
+ else
75
+ raise ProtocolError, "Received text, but expecting continuation!"
76
+ end
77
+ end
78
+
79
+ def receive_binary(frame)
80
+ if @frames.empty?
81
+ @frames << frame
82
+ else
83
+ raise ProtocolError, "Received binary, but expecting continuation!"
84
+ end
85
+ end
86
+
87
+ def receive_continuation(frame)
88
+ if @frames.any?
89
+ @frames << frame
90
+ else
91
+ raise ProtocolError, "Received unexpected continuation!"
92
+ end
93
+ end
94
+
95
+ def send_text(buffer)
96
+ frame = TextFrame.new
97
+ frame.pack buffer
98
+
99
+ write_frame(frame)
100
+ end
101
+
102
+ def send_binary(buffer)
103
+ frame = BinaryFrame.new
104
+ frame.pack buffer
105
+
106
+ write_frame(frame)
107
+ end
108
+
109
+ def send_close(code = Error::NO_ERROR, message = nil)
110
+ frame = CloseFrame.new
111
+ frame.pack(code, message)
112
+
113
+ write_frame(frame)
114
+
115
+ @state = :closed
116
+ end
117
+
118
+ def receive_close(frame)
119
+ @state = :closed
120
+
121
+ code, message = frame.unpack
122
+
123
+ if code and code != Error::NO_ERROR
124
+ raise ClosedError.new message, code
125
+ end
126
+ end
127
+
128
+ def send_ping(data)
129
+ if @state != :closed
130
+ frame = PingFrame.new
131
+ frame.pack data
132
+
133
+ write_frame(frame)
134
+ else
135
+ raise ProtocolError, "Cannot send ping in state #{@state}"
136
+ end
137
+ end
138
+
139
+ def open!
140
+ @state = :open
141
+
142
+ return self
143
+ end
144
+
145
+ def receive_ping(frame)
146
+ if @state != :closed
147
+ write_frame(frame.reply)
148
+ else
149
+ raise ProtocolError, "Cannot receive ping in state #{@state}"
150
+ end
151
+ end
152
+
153
+ def receive_frame(frame)
154
+ warn "Unhandled frame #{frame.inspect}"
155
+ end
156
+
157
+ # @return [Array<Frame>] sequence of frames, the first being either text or binary, optionally followed by a number of continuation frames.
158
+ def next_message
159
+ @framer.flush
160
+
161
+ while read_frame
162
+ if @frames.last&.finished?
163
+ frames = @frames
164
+ @frames = []
165
+
166
+ return frames
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,33 @@
1
+ # Copyright, 2019, 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 ContinuationFrame < Frame
26
+ OPCODE = 0x0
27
+
28
+ def apply(connection)
29
+ connection.receive_continuation(self)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -22,10 +22,42 @@ require 'protocol/http/error'
22
22
 
23
23
  module Protocol
24
24
  module WebSocket
25
- class BadRequest < HTTP::BadRequest
25
+ # Status codes as defined by <https://tools.ietf.org/html/rfc6455#section-7.4.1>.
26
+ class Error < HTTP::Error
27
+ # Indicates a normal closure, meaning that the purpose for which the connection was established has been fulfilled.
28
+ NO_ERROR = 1000
29
+
30
+ # Indicates that an endpoint is "going away", such as a server going down or a browser having navigated away from a page.
31
+ GOING_AWAY = 1001
32
+
33
+ # Indicates that an endpoint is terminating the connection due to a protocol error.
34
+ PROTOCOL_ERROR = 1002
35
+
36
+ # Indicates that an endpoint is terminating the connection because it has received a type of data it cannot accept.
37
+ INVALID_DATA = 1003
38
+
39
+ # There are other status codes but most of them are "implementation specific".
26
40
  end
27
41
 
28
- class ProtocolError < HTTP::ProtocolError
42
+ # Raised by stream or connection handlers, results in GOAWAY frame
43
+ # which signals termination of the current connection. You *cannot*
44
+ # recover from this exception, or any exceptions subclassed from it.
45
+ class ProtocolError < Error
46
+ def initialize(message, code = PROTOCOL_ERROR)
47
+ super(message)
48
+
49
+ @code = code
50
+ end
51
+
52
+ attr :code
53
+ end
54
+
55
+ # The connection was closed, maybe unexpectedly.
56
+ class ClosedError < ProtocolError
57
+ end
58
+
59
+ # When the frame payload does not match expectations.
60
+ class FrameSizeError < ProtocolError
29
61
  end
30
62
  end
31
63
  end
@@ -25,47 +25,35 @@ module Protocol
25
25
  class Frame
26
26
  include Comparable
27
27
 
28
- # Opcodes
29
- CONTINUATION = 0x0
30
- TEXT = 0x1
31
- BINARY = 0x2
32
- CLOSE = 0x8
33
- PING = 0x9
34
- PONG = 0xA
28
+ OPCODE = 0
35
29
 
36
30
  # @param length [Integer] the length of the payload, or nil if the header has not been read yet.
37
- def initialize(fin = true, opcode = 0, mask = nil, payload = nil)
38
- @fin = fin
31
+ def initialize(finished = true, opcode = self.class::OPCODE, mask = false, payload = nil)
32
+ @finished = finished
39
33
  @opcode = opcode
40
34
  @mask = mask
41
35
  @length = payload&.bytesize
42
36
  @payload = payload
43
37
  end
44
38
 
45
- def self.read(stream)
46
- frame = self.new
47
-
48
- if frame.read(stream)
49
- return frame
50
- end
51
- rescue EOFError
52
- return nil
53
- end
54
-
55
39
  def <=> other
56
40
  to_ary <=> other.to_ary
57
41
  end
58
42
 
59
43
  def to_ary
60
- [@fin, @opcode, @mask, @length, @payload]
44
+ [@finished, @opcode, @mask, @length, @payload]
61
45
  end
62
46
 
63
47
  def control?
64
48
  @opcode & 0x8
65
49
  end
66
50
 
51
+ def finished?
52
+ @finished == true
53
+ end
54
+
67
55
  def continued?
68
- @fin == false
56
+ @finished == false
69
57
  end
70
58
 
71
59
  # The generic frame header uses the following binary representation:
@@ -89,7 +77,7 @@ module Protocol
89
77
  # | Payload Data continued ... |
90
78
  # +---------------------------------------------------------------+
91
79
 
92
- attr_accessor :fin
80
+ attr_accessor :finished
93
81
  attr_accessor :opcode
94
82
  attr_accessor :mask
95
83
  attr_accessor :length
@@ -99,54 +87,90 @@ module Protocol
99
87
  @payload
100
88
  end
101
89
 
102
- def pack(payload)
103
- @payload = payload
104
- @length = payload.bytesize
90
+ def pack(data, mask = @mask)
91
+ length = data.bytesize
105
92
 
106
- if @length.bit_length > 64
107
- raise ProtocolError, "Frame length #{@length} bigger than allowed 64-bit field!"
93
+ if length.bit_length > 63
94
+ raise ProtocolError, "Frame length #{@length} bigger than allowed maximum!"
95
+ end
96
+
97
+ if @mask = mask
98
+ @payload = String.new.b
99
+
100
+ for i in 0...data.bytesize do
101
+ @payload << (data.getbyte(i) ^ mask.getbyte(i % 4))
102
+ end
103
+ else
104
+ @payload = data
105
+ @length = length
108
106
  end
109
107
  end
110
108
 
111
- def read(stream)
112
- buffer = stream.read(2) or raise EOFError
113
- first, second = buffer.unpack("CC")
109
+ def unpack
110
+ if @mask
111
+ data = String.new.b
112
+
113
+ for i in 0...@payload.bytesize do
114
+ data << (@payload.getbyte(i) ^ @mask.getbyte(i % 4))
115
+ end
116
+
117
+ return data
118
+ else
119
+ return @payload
120
+ end
121
+ end
122
+
123
+ def apply(connection)
124
+ connection.receive_frame(self)
125
+ end
126
+
127
+ def self.parse_header(buffer)
128
+ byte = buffer.unpack("C").first
114
129
 
115
- @fin = (first & 0b1000_0000 != 0)
130
+ finished = (byte & 0b1000_0000 != 0)
116
131
  # rsv = byte & 0b0111_0000
117
- @opcode = first & 0b0000_1111
132
+ opcode = byte & 0b0000_1111
118
133
 
119
- @mask = (second & 0b1000_0000 != 0)
120
- @length = second & 0b0111_1111
134
+ return finished, opcode
135
+ end
136
+
137
+ def self.read(finished, opcode, stream, maximum_frame_size)
138
+ buffer = stream.read(1) or raise EOFError
139
+ byte = buffer.unpack("C").first
121
140
 
122
- if @length == 126
141
+ mask = (byte & 0b1000_0000 != 0)
142
+ length = byte & 0b0111_1111
143
+
144
+ if length == 126
123
145
  buffer = stream.read(2) or raise EOFError
124
- @length = buffer.unpack('n').first
125
- elsif @length == 127
146
+ length = buffer.unpack('n').first
147
+ elsif length == 127
126
148
  buffer = stream.read(4) or raise EOFError
127
- @length = buffer.unpack('Q>').first
149
+ length = buffer.unpack('Q>').first
128
150
  end
129
151
 
130
- if @mask
131
- @mask = stream.read(4) or raise EOFError
132
- @payload = read_mask(@mask, @length, stream)
133
- else
134
- @mask = nil
135
- @payload = stream.read(@length) or raise EOFError
152
+ if length > maximum_frame_size
153
+ raise ProtocolError, "Invalid payload length: #{@length} > #{maximum_frame_size}!"
136
154
  end
137
155
 
138
- if @payload&.bytesize != @length
139
- raise ProtocolError, "Invalid payload size: #{@length} != #{@payload.bytesize}!"
156
+ if mask
157
+ mask = stream.read(4) or raise EOFError
158
+ end
159
+
160
+ payload = stream.read(length) or raise EOFError
161
+
162
+ if payload.bytesize != length
163
+ raise EOFError, "Incorrect payload length: #{@length} != #{@payload.bytesize}!"
140
164
  end
141
165
 
142
- return true
166
+ return self.new(finished, opcode, mask, payload)
143
167
  end
144
168
 
145
169
  def write(stream)
146
170
  buffer = String.new.b
147
171
 
148
172
  if @payload&.bytesize != @length
149
- raise ProtocolError, "Invalid payload size: #{@length} != #{@payload.bytesize}!"
173
+ raise ProtocolError, "Invalid payload length: #{@length} != #{@payload.bytesize}!"
150
174
  end
151
175
 
152
176
  if @mask and @mask.bytesize != 4
@@ -162,7 +186,7 @@ module Protocol
162
186
  end
163
187
 
164
188
  buffer << [
165
- (@fin ? 0b1000_0000 : 0) | @opcode,
189
+ (@finished ? 0b1000_0000 : 0) | @opcode,
166
190
  (@mask ? 0b1000_0000 : 0) | short_length,
167
191
  ].pack('CC')
168
192
 
@@ -172,32 +196,8 @@ module Protocol
172
196
  buffer << [@length].pack('Q>')
173
197
  end
174
198
 
175
- if @mask
176
- buffer << @mask
177
- write_mask(@mask, @payload, buffer)
178
- stream.write(buffer)
179
- else
180
- stream.write(buffer)
181
- stream.write(@payload)
182
- end
183
- end
184
-
185
- private
186
-
187
- def read_mask(mask, length, stream)
188
- data = stream.read(length) or raise EOFError
189
-
190
- for i in 0...data.bytesize do
191
- data.setbyte(i, data.getbyte(i) ^ mask.getbyte(i % 4))
192
- end
193
-
194
- return data
195
- end
196
-
197
- def write_mask(mask, data, buffer)
198
- for i in 0...data.bytesize do
199
- buffer << (data.getbyte(i) ^ mask.getbyte(i % 4))
200
- end
199
+ stream.write(buffer)
200
+ stream.write(@payload)
201
201
  end
202
202
  end
203
203
  end
@@ -20,11 +20,31 @@
20
20
 
21
21
  require_relative 'frame'
22
22
 
23
+ require_relative 'continuation_frame'
24
+ require_relative 'text_frame'
25
+ require_relative 'binary_frame'
26
+ require_relative 'close_frame'
27
+ require_relative 'ping_frame'
28
+ require_relative 'pong_frame'
29
+
23
30
  module Protocol
24
31
  module WebSocket
32
+ # HTTP/2 frame type mapping as defined by the spec
33
+ FRAMES = {
34
+ 0x0 => ContinuationFrame,
35
+ 0x1 => TextFrame,
36
+ 0x2 => BinaryFrame,
37
+ 0x8 => CloseFrame,
38
+ 0x9 => PingFrame,
39
+ 0xA => PongFrame,
40
+ }.freeze
41
+
42
+ MAXIMUM_ALLOWED_FRAME_SIZE = 2**63
43
+
25
44
  class Framer
26
- def initialize(stream)
45
+ def initialize(stream, frames = FRAMES)
27
46
  @stream = stream
47
+ @frames = frames
28
48
  end
29
49
 
30
50
  def close
@@ -35,20 +55,28 @@ module Protocol
35
55
  @stream.flush
36
56
  end
37
57
 
38
- def read_frame
39
- Frame.read(@stream)
40
- end
41
-
42
- def read_message
58
+ def read_frame(maximum_frame_size = MAXIMUM_ALLOWED_FRAME_SIZE)
59
+ # Read the header:
60
+ finished, opcode = read_header
43
61
 
44
- end
45
-
46
- def write_message(opcode, payload)
62
+ # Read the frame:
63
+ klass = @frames[opcode] || Frame
64
+ frame = klass.read(finished, opcode, @stream, maximum_frame_size)
65
+
66
+ return frame
47
67
  end
48
68
 
49
69
  def write_frame(frame)
50
70
  frame.write(@stream)
51
71
  end
72
+
73
+ def read_header
74
+ if buffer = @stream.read(1)
75
+ return Frame.parse_header(buffer)
76
+ end
77
+
78
+ raise EOFError, "Could not read frame header!"
79
+ end
52
80
  end
53
81
  end
54
82
  end
@@ -0,0 +1,38 @@
1
+ # Copyright, 2019, 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
+ require_relative 'pong_frame'
23
+
24
+ module Protocol
25
+ module WebSocket
26
+ class PingFrame < Frame
27
+ OPCODE = 0x9
28
+
29
+ def reply
30
+ PongFrame.new(true, PongFrame::OPCODE, @mask, @payload)
31
+ end
32
+
33
+ def apply(connection)
34
+ connection.receive_ping(self)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,33 @@
1
+ # Copyright, 2019, 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 PongFrame < Frame
26
+ OPCODE = 0xA
27
+
28
+ def apply(connection)
29
+ connection.receive_pong(self)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,41 @@
1
+ # Copyright, 2019, 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 TextFrame < Frame
26
+ OPCODE = 0x1
27
+
28
+ def unpack
29
+ super.force_encoding(Encoding::UTF_8)
30
+ end
31
+
32
+ def pack(data, mask = @mask)
33
+ super(data.b, mask = @mask)
34
+ end
35
+
36
+ def apply(connection)
37
+ connection.receive_text(self)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Protocol
22
22
  module WebSocket
23
- VERSION = "0.1.0"
23
+ VERSION = "0.2.0"
24
24
  end
25
25
  end
@@ -18,7 +18,8 @@ Gem::Specification.new do |spec|
18
18
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency "protocol-http"
21
+ spec.add_dependency "protocol-http", "~> 0.2"
22
+ spec.add_dependency "protocol-http1", "~> 0.2"
22
23
 
23
24
  spec.add_development_dependency "covered"
24
25
  spec.add_development_dependency "bundler"
metadata CHANGED
@@ -1,29 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protocol-websocket
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-08 00:00:00.000000000 Z
11
+ date: 2019-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: protocol-http
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '0.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '0.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: protocol-http1
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.2'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: covered
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -94,10 +108,17 @@ files:
94
108
  - README.md
95
109
  - Rakefile
96
110
  - lib/protocol/websocket.rb
111
+ - lib/protocol/websocket/binary_frame.rb
112
+ - lib/protocol/websocket/close_frame.rb
113
+ - lib/protocol/websocket/connection.rb
114
+ - lib/protocol/websocket/continuation_frame.rb
97
115
  - lib/protocol/websocket/digest.rb
98
116
  - lib/protocol/websocket/error.rb
99
117
  - lib/protocol/websocket/frame.rb
100
118
  - lib/protocol/websocket/framer.rb
119
+ - lib/protocol/websocket/ping_frame.rb
120
+ - lib/protocol/websocket/pong_frame.rb
121
+ - lib/protocol/websocket/text_frame.rb
101
122
  - lib/protocol/websocket/version.rb
102
123
  - protocol-websocket.gemspec
103
124
  homepage: https://github.com/socketry/protocol-websocket