protocol-websocket 0.1.0 → 0.2.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: 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