protocol-websocket 0.7.5 → 0.8.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 +0 -0
 - data/lib/protocol/websocket/binary_frame.rb +4 -0
 - data/lib/protocol/websocket/close_frame.rb +32 -2
 - data/lib/protocol/websocket/connection.rb +68 -33
 - data/lib/protocol/websocket/extension/compression/deflate.rb +93 -0
 - data/lib/protocol/websocket/extension/compression/inflate.rb +85 -0
 - data/lib/protocol/websocket/extension/compression.rb +131 -0
 - data/lib/protocol/websocket/extensions.md +13 -0
 - data/lib/protocol/websocket/extensions.rb +146 -0
 - data/lib/protocol/websocket/frame.rb +36 -13
 - data/lib/protocol/websocket/framer.rb +7 -3
 - data/lib/protocol/websocket/headers.rb +5 -1
 - data/lib/protocol/websocket/ping_frame.rb +3 -0
 - data/lib/protocol/websocket/pong_frame.rb +1 -0
 - data/lib/protocol/websocket/text_frame.rb +12 -6
 - data/lib/protocol/websocket/version.rb +1 -1
 - data/lib/protocol/websocket.rb +1 -0
 - data.tar.gz.sig +4 -0
 - metadata +42 -4
 - metadata.gz.sig +0 -0
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 076d348e98312526d98d8fc80840b9760cf30496ac4ca83d129ac53e120c10a0
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 142d1fa0b2c8e06a3642cfff30f76f835308b23ca3d6964e1c3ae0870204d5e1
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 941eda4ac12acbc31bdd359cae2b2aaffd59f78f328844fcd171f6aca517cc225831ea3726f919448eaef3c7246a630d415988528ef08f4f6a394239b5924aeb
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 9c37cfc67173a3632c344d042682f906b9b9afc2c4cbc35263f9cc48fa0cf4e62ee7348b4ce5a1af7bed2b118d23f477721e2b575f8d91f991806fa53996da7c
         
     | 
    
        checksums.yaml.gz.sig
    ADDED
    
    | 
         Binary file 
     | 
| 
         @@ -29,11 +29,41 @@ module Protocol 
     | 
|
| 
       29 
29 
     | 
    
         
             
            			def unpack
         
     | 
| 
       30 
30 
     | 
    
         
             
            				data = super
         
     | 
| 
       31 
31 
     | 
    
         | 
| 
       32 
     | 
    
         
            -
            				 
     | 
| 
      
 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 
     | 
    
         
            -
            				 
     | 
| 
      
 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,14 +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 
28 
     | 
    
         
             
            			# @parameter mask [String] 4-byte mask to be used for frames generated by this connection.
         
     | 
| 
       28 
     | 
    
         
            -
            			def initialize(framer, mask: nil)
         
     | 
| 
      
 29 
     | 
    
         
            +
            			def initialize(framer, mask: nil, **options)
         
     | 
| 
       29 
30 
     | 
    
         
             
            				@framer = framer
         
     | 
| 
       30 
31 
     | 
    
         
             
            				@mask = mask
         
     | 
| 
       31 
32 
     | 
    
         | 
| 
       32 
33 
     | 
    
         
             
            				@state = :open
         
     | 
| 
       33 
34 
     | 
    
         
             
            				@frames = []
         
     | 
| 
      
 35 
     | 
    
         
            +
            				
         
     | 
| 
      
 36 
     | 
    
         
            +
            				@reserved = Frame::RESERVED
         
     | 
| 
      
 37 
     | 
    
         
            +
            				
         
     | 
| 
      
 38 
     | 
    
         
            +
            				@reader = self
         
     | 
| 
      
 39 
     | 
    
         
            +
            				@writer = self
         
     | 
| 
       34 
40 
     | 
    
         
             
            			end
         
     | 
| 
       35 
41 
     | 
    
         | 
| 
       36 
42 
     | 
    
         
             
            			# The framer which is used for reading and writing frames.
         
     | 
| 
         @@ -39,9 +45,25 @@ module Protocol 
     | 
|
| 
       39 
45 
     | 
    
         
             
            			# The (optional) mask which is used when generating frames.
         
     | 
| 
       40 
46 
     | 
    
         
             
            			attr :mask
         
     | 
| 
       41 
47 
     | 
    
         | 
| 
      
 48 
     | 
    
         
            +
            			# The allowed reserved bits:
         
     | 
| 
      
 49 
     | 
    
         
            +
            			attr :reserved
         
     | 
| 
      
 50 
     | 
    
         
            +
            			
         
     | 
| 
       42 
51 
     | 
    
         
             
            			# Buffered frames which form part of a complete message.
         
     | 
| 
       43 
52 
     | 
    
         
             
            			attr_accessor :frames
         
     | 
| 
       44 
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 
     | 
    
         
            +
            			
         
     | 
| 
       45 
67 
     | 
    
         
             
            			def flush
         
     | 
| 
       46 
68 
     | 
    
         
             
            				@framer.flush
         
     | 
| 
       47 
69 
     | 
    
         
             
            			end
         
     | 
| 
         @@ -50,8 +72,8 @@ module Protocol 
     | 
|
| 
       50 
72 
     | 
    
         
             
            				@state == :closed
         
     | 
| 
       51 
73 
     | 
    
         
             
            			end
         
     | 
| 
       52 
74 
     | 
    
         | 
| 
       53 
     | 
    
         
            -
            			def close
         
     | 
| 
       54 
     | 
    
         
            -
            				send_close unless closed?
         
     | 
| 
      
 75 
     | 
    
         
            +
            			def close(code = Error::NO_ERROR, reason = "")
         
     | 
| 
      
 76 
     | 
    
         
            +
            				send_close(code, reason) unless closed?
         
     | 
| 
       55 
77 
     | 
    
         | 
| 
       56 
78 
     | 
    
         
             
            				@framer.close
         
     | 
| 
       57 
79 
     | 
    
         
             
            			end
         
     | 
| 
         @@ -61,6 +83,10 @@ module Protocol 
     | 
|
| 
       61 
83 
     | 
    
         | 
| 
       62 
84 
     | 
    
         
             
            				frame = @framer.read_frame
         
     | 
| 
       63 
85 
     | 
    
         | 
| 
      
 86 
     | 
    
         
            +
            				unless (frame.flags & @reserved).zero?
         
     | 
| 
      
 87 
     | 
    
         
            +
            					raise ProtocolError, "Received frame with reserved flags set!"
         
     | 
| 
      
 88 
     | 
    
         
            +
            				end
         
     | 
| 
      
 89 
     | 
    
         
            +
            				
         
     | 
| 
       64 
90 
     | 
    
         
             
            				yield frame if block_given?
         
     | 
| 
       65 
91 
     | 
    
         | 
| 
       66 
92 
     | 
    
         
             
            				frame.apply(self)
         
     | 
| 
         @@ -106,23 +132,31 @@ module Protocol 
     | 
|
| 
       106 
132 
     | 
    
         
             
            				end
         
     | 
| 
       107 
133 
     | 
    
         
             
            			end
         
     | 
| 
       108 
134 
     | 
    
         | 
| 
       109 
     | 
    
         
            -
            			def  
     | 
| 
      
 135 
     | 
    
         
            +
            			def text_message(buffer, **options)
         
     | 
| 
       110 
136 
     | 
    
         
             
            				frame = TextFrame.new(mask: @mask)
         
     | 
| 
       111 
     | 
    
         
            -
            				frame.pack 
     | 
| 
      
 137 
     | 
    
         
            +
            				frame.pack(buffer)
         
     | 
| 
       112 
138 
     | 
    
         | 
| 
       113 
     | 
    
         
            -
            				 
     | 
| 
      
 139 
     | 
    
         
            +
            				return frame
         
     | 
| 
       114 
140 
     | 
    
         
             
            			end
         
     | 
| 
       115 
141 
     | 
    
         | 
| 
       116 
     | 
    
         
            -
            			def  
     | 
| 
      
 142 
     | 
    
         
            +
            			def send_text(buffer, **options)
         
     | 
| 
      
 143 
     | 
    
         
            +
            				write_frame(@writer.text_message(buffer, **options))
         
     | 
| 
      
 144 
     | 
    
         
            +
            			end
         
     | 
| 
      
 145 
     | 
    
         
            +
            			
         
     | 
| 
      
 146 
     | 
    
         
            +
            			def binary_message(buffer, **options)
         
     | 
| 
       117 
147 
     | 
    
         
             
            				frame = BinaryFrame.new(mask: @mask)
         
     | 
| 
       118 
     | 
    
         
            -
            				frame.pack 
     | 
| 
      
 148 
     | 
    
         
            +
            				frame.pack(buffer)
         
     | 
| 
       119 
149 
     | 
    
         | 
| 
       120 
     | 
    
         
            -
            				 
     | 
| 
      
 150 
     | 
    
         
            +
            				return frame
         
     | 
| 
       121 
151 
     | 
    
         
             
            			end
         
     | 
| 
       122 
152 
     | 
    
         | 
| 
       123 
     | 
    
         
            -
            			def  
     | 
| 
      
 153 
     | 
    
         
            +
            			def send_binary(buffer, **options)
         
     | 
| 
      
 154 
     | 
    
         
            +
            				write_frame(@writer.binary_message(buffer, **options))
         
     | 
| 
      
 155 
     | 
    
         
            +
            			end
         
     | 
| 
      
 156 
     | 
    
         
            +
            			
         
     | 
| 
      
 157 
     | 
    
         
            +
            			def send_close(code = Error::NO_ERROR, reason = "")
         
     | 
| 
       124 
158 
     | 
    
         
             
            				frame = CloseFrame.new(mask: @mask)
         
     | 
| 
       125 
     | 
    
         
            -
            				frame.pack(code,  
     | 
| 
      
 159 
     | 
    
         
            +
            				frame.pack(code, reason)
         
     | 
| 
       126 
160 
     | 
    
         | 
| 
       127 
161 
     | 
    
         
             
            				self.write_frame(frame)
         
     | 
| 
       128 
162 
     | 
    
         
             
            				self.flush
         
     | 
| 
         @@ -133,10 +167,12 @@ module Protocol 
     | 
|
| 
       133 
167 
     | 
    
         
             
            			def receive_close(frame)
         
     | 
| 
       134 
168 
     | 
    
         
             
            				@state = :closed
         
     | 
| 
       135 
169 
     | 
    
         | 
| 
       136 
     | 
    
         
            -
            				code,  
     | 
| 
      
 170 
     | 
    
         
            +
            				code, reason = frame.unpack
         
     | 
| 
      
 171 
     | 
    
         
            +
            				
         
     | 
| 
      
 172 
     | 
    
         
            +
            				send_close(code, reason)
         
     | 
| 
       137 
173 
     | 
    
         | 
| 
       138 
174 
     | 
    
         
             
            				if code and code != Error::NO_ERROR
         
     | 
| 
       139 
     | 
    
         
            -
            					raise ClosedError.new  
     | 
| 
      
 175 
     | 
    
         
            +
            					raise ClosedError.new reason, code
         
     | 
| 
       140 
176 
     | 
    
         
             
            				end
         
     | 
| 
       141 
177 
     | 
    
         
             
            			end
         
     | 
| 
       142 
178 
     | 
    
         | 
| 
         @@ -173,34 +209,24 @@ module Protocol 
     | 
|
| 
       173 
209 
     | 
    
         
             
            				warn "Unhandled frame #{frame.inspect}"
         
     | 
| 
       174 
210 
     | 
    
         
             
            			end
         
     | 
| 
       175 
211 
     | 
    
         | 
| 
       176 
     | 
    
         
            -
            			 
     | 
| 
       177 
     | 
    
         
            -
            			def write(buffer)
         
     | 
| 
       178 
     | 
    
         
            -
            				# https://tools.ietf.org/html/rfc6455#section-5.6
         
     | 
| 
       179 
     | 
    
         
            -
            				
         
     | 
| 
      
 212 
     | 
    
         
            +
            			def write_message(buffer, **options)
         
     | 
| 
       180 
213 
     | 
    
         
             
            				# Text: The "Payload data" is text data encoded as UTF-8
         
     | 
| 
       181 
214 
     | 
    
         
             
            				if buffer.encoding == Encoding::UTF_8
         
     | 
| 
       182 
     | 
    
         
            -
            					send_text(buffer)
         
     | 
| 
      
 215 
     | 
    
         
            +
            					send_text(buffer, **options)
         
     | 
| 
       183 
216 
     | 
    
         
             
            				else
         
     | 
| 
       184 
     | 
    
         
            -
            					send_binary(buffer)
         
     | 
| 
      
 217 
     | 
    
         
            +
            					send_binary(buffer, **options)
         
     | 
| 
       185 
218 
     | 
    
         
             
            				end
         
     | 
| 
       186 
219 
     | 
    
         
             
            			end
         
     | 
| 
       187 
220 
     | 
    
         | 
| 
       188 
     | 
    
         
            -
            			# @ 
     | 
| 
       189 
     | 
    
         
            -
            			def  
     | 
| 
       190 
     | 
    
         
            -
            				 
     | 
| 
      
 221 
     | 
    
         
            +
            			# @param buffer [String] a unicode or binary string.
         
     | 
| 
      
 222 
     | 
    
         
            +
            			def write(buffer, **options)
         
     | 
| 
      
 223 
     | 
    
         
            +
            				# https://tools.ietf.org/html/rfc6455#section-5.6
         
     | 
| 
       191 
224 
     | 
    
         | 
| 
       192 
     | 
    
         
            -
            				 
     | 
| 
       193 
     | 
    
         
            -
            					if @frames.last&.finished?
         
     | 
| 
       194 
     | 
    
         
            -
            						buffer = @frames.map(&:unpack).join
         
     | 
| 
       195 
     | 
    
         
            -
            						@frames = []
         
     | 
| 
       196 
     | 
    
         
            -
            						
         
     | 
| 
       197 
     | 
    
         
            -
            						return buffer
         
     | 
| 
       198 
     | 
    
         
            -
            					end
         
     | 
| 
       199 
     | 
    
         
            -
            				end
         
     | 
| 
      
 225 
     | 
    
         
            +
            				self.write_message(buffer, **options)
         
     | 
| 
       200 
226 
     | 
    
         
             
            			end
         
     | 
| 
       201 
227 
     | 
    
         | 
| 
       202 
     | 
    
         
            -
            			#  
     | 
| 
       203 
     | 
    
         
            -
            			def  
     | 
| 
      
 228 
     | 
    
         
            +
            			# @return [String] a unicode or binary string.
         
     | 
| 
      
 229 
     | 
    
         
            +
            			def read(**options)
         
     | 
| 
       204 
230 
     | 
    
         
             
            				@framer.flush
         
     | 
| 
       205 
231 
     | 
    
         | 
| 
       206 
232 
     | 
    
         
             
            				while read_frame
         
     | 
| 
         @@ -208,9 +234,18 @@ module Protocol 
     | 
|
| 
       208 
234 
     | 
    
         
             
            						frames = @frames
         
     | 
| 
       209 
235 
     | 
    
         
             
            						@frames = []
         
     | 
| 
       210 
236 
     | 
    
         | 
| 
       211 
     | 
    
         
            -
            						 
     | 
| 
      
 237 
     | 
    
         
            +
            						buffer = @reader.read_message(frames, **options)
         
     | 
| 
      
 238 
     | 
    
         
            +
            						return frames.first.read_message(buffer)
         
     | 
| 
       212 
239 
     | 
    
         
             
            					end
         
     | 
| 
       213 
240 
     | 
    
         
             
            				end
         
     | 
| 
      
 241 
     | 
    
         
            +
            			rescue ProtocolError => error
         
     | 
| 
      
 242 
     | 
    
         
            +
            				send_close(error.code, error.message)
         
     | 
| 
      
 243 
     | 
    
         
            +
            				
         
     | 
| 
      
 244 
     | 
    
         
            +
            				raise
         
     | 
| 
      
 245 
     | 
    
         
            +
            			end
         
     | 
| 
      
 246 
     | 
    
         
            +
            			
         
     | 
| 
      
 247 
     | 
    
         
            +
            			def read_message(frames, **options)
         
     | 
| 
      
 248 
     | 
    
         
            +
            				frames.map(&:unpack).join("")
         
     | 
| 
       214 
249 
     | 
    
         
             
            			end
         
     | 
| 
       215 
250 
     | 
    
         
             
            		end
         
     | 
| 
       216 
251 
     | 
    
         
             
            	end
         
     | 
| 
         @@ -0,0 +1,93 @@ 
     | 
|
| 
      
 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 
     | 
    
         
            +
            				class Deflate
         
     | 
| 
      
 28 
     | 
    
         
            +
            					def self.client(parent, client_window_bits: 15, client_no_context_takeover: false, **options)
         
     | 
| 
      
 29 
     | 
    
         
            +
            						self.new(parent,
         
     | 
| 
      
 30 
     | 
    
         
            +
            							window_bits: client_window_bits,
         
     | 
| 
      
 31 
     | 
    
         
            +
            							context_takeover: !client_no_context_takeover,
         
     | 
| 
      
 32 
     | 
    
         
            +
            							**options
         
     | 
| 
      
 33 
     | 
    
         
            +
            						)
         
     | 
| 
      
 34 
     | 
    
         
            +
            					end
         
     | 
| 
      
 35 
     | 
    
         
            +
            					
         
     | 
| 
      
 36 
     | 
    
         
            +
            					def self.server(parent, server_window_bits: 15, server_no_context_takeover: false, **options)
         
     | 
| 
      
 37 
     | 
    
         
            +
            						self.new(parent,
         
     | 
| 
      
 38 
     | 
    
         
            +
            							window_bits: server_window_bits,
         
     | 
| 
      
 39 
     | 
    
         
            +
            							context_takeover: !server_no_context_takeover,
         
     | 
| 
      
 40 
     | 
    
         
            +
            							**options
         
     | 
| 
      
 41 
     | 
    
         
            +
            						)
         
     | 
| 
      
 42 
     | 
    
         
            +
            					end
         
     | 
| 
      
 43 
     | 
    
         
            +
            					
         
     | 
| 
      
 44 
     | 
    
         
            +
            					def initialize(parent, level: Zlib::DEFAULT_COMPRESSION, memory_level: Zlib::DEF_MEM_LEVEL, strategy: Zlib::DEFAULT_STRATEGY, window_bits: 15, context_takeover: true, **options)
         
     | 
| 
      
 45 
     | 
    
         
            +
            						@parent = parent
         
     | 
| 
      
 46 
     | 
    
         
            +
            						
         
     | 
| 
      
 47 
     | 
    
         
            +
            						@deflate = nil
         
     | 
| 
      
 48 
     | 
    
         
            +
            						
         
     | 
| 
      
 49 
     | 
    
         
            +
            						@compression_level = level
         
     | 
| 
      
 50 
     | 
    
         
            +
            						@memory_level = memory_level
         
     | 
| 
      
 51 
     | 
    
         
            +
            						@strategy = strategy
         
     | 
| 
      
 52 
     | 
    
         
            +
            						
         
     | 
| 
      
 53 
     | 
    
         
            +
            						@window_bits = window_bits
         
     | 
| 
      
 54 
     | 
    
         
            +
            						@context_takeover = context_takeover
         
     | 
| 
      
 55 
     | 
    
         
            +
            					end
         
     | 
| 
      
 56 
     | 
    
         
            +
            					
         
     | 
| 
      
 57 
     | 
    
         
            +
            					def text_message(buffer, compress: true, **options)
         
     | 
| 
      
 58 
     | 
    
         
            +
            						buffer = self.deflate(buffer)
         
     | 
| 
      
 59 
     | 
    
         
            +
            						
         
     | 
| 
      
 60 
     | 
    
         
            +
            						frame = @parent.text_message(buffer, **options)
         
     | 
| 
      
 61 
     | 
    
         
            +
            						
         
     | 
| 
      
 62 
     | 
    
         
            +
            						frame.flags |= Frame::RSV1
         
     | 
| 
      
 63 
     | 
    
         
            +
            						
         
     | 
| 
      
 64 
     | 
    
         
            +
            						return frame
         
     | 
| 
      
 65 
     | 
    
         
            +
            					end
         
     | 
| 
      
 66 
     | 
    
         
            +
            					
         
     | 
| 
      
 67 
     | 
    
         
            +
            					def binary_message(buffer, compress: false, **options)
         
     | 
| 
      
 68 
     | 
    
         
            +
            						message = self.deflate(buffer)
         
     | 
| 
      
 69 
     | 
    
         
            +
            						
         
     | 
| 
      
 70 
     | 
    
         
            +
            						frame = parent.binary_message(buffer, **options)
         
     | 
| 
      
 71 
     | 
    
         
            +
            						
         
     | 
| 
      
 72 
     | 
    
         
            +
            						frame.flags |= Frame::RSV1
         
     | 
| 
      
 73 
     | 
    
         
            +
            						
         
     | 
| 
      
 74 
     | 
    
         
            +
            						return frame
         
     | 
| 
      
 75 
     | 
    
         
            +
            					end
         
     | 
| 
      
 76 
     | 
    
         
            +
            					
         
     | 
| 
      
 77 
     | 
    
         
            +
            					private
         
     | 
| 
      
 78 
     | 
    
         
            +
            					
         
     | 
| 
      
 79 
     | 
    
         
            +
            					def deflate(buffer)
         
     | 
| 
      
 80 
     | 
    
         
            +
            						Console.logger.info(self, "Deflating #{buffer.size} bytes")
         
     | 
| 
      
 81 
     | 
    
         
            +
            						deflate = @deflate || Zlib::Deflate.new(@level, -@window_bits, @memory_level, @strategy)
         
     | 
| 
      
 82 
     | 
    
         
            +
            						
         
     | 
| 
      
 83 
     | 
    
         
            +
            						if @context_takeover
         
     | 
| 
      
 84 
     | 
    
         
            +
            							@deflate = deflate
         
     | 
| 
      
 85 
     | 
    
         
            +
            						end
         
     | 
| 
      
 86 
     | 
    
         
            +
            						
         
     | 
| 
      
 87 
     | 
    
         
            +
            						return @deflate.deflate(buffer, Zlib::SYNC_FLUSH)[0...-4]
         
     | 
| 
      
 88 
     | 
    
         
            +
            					end
         
     | 
| 
      
 89 
     | 
    
         
            +
            				end
         
     | 
| 
      
 90 
     | 
    
         
            +
            			end
         
     | 
| 
      
 91 
     | 
    
         
            +
            		end
         
     | 
| 
      
 92 
     | 
    
         
            +
            	end
         
     | 
| 
      
 93 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,85 @@ 
     | 
|
| 
      
 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 
     | 
    
         
            +
            				class Inflate
         
     | 
| 
      
 28 
     | 
    
         
            +
            					def self.client(parent, client_window_bits: 15, client_no_context_takeover: false, **options)
         
     | 
| 
      
 29 
     | 
    
         
            +
            						self.new(parent,
         
     | 
| 
      
 30 
     | 
    
         
            +
            							window_bits: client_window_bits,
         
     | 
| 
      
 31 
     | 
    
         
            +
            							context_takeover: !client_no_context_takeover,
         
     | 
| 
      
 32 
     | 
    
         
            +
            							**options
         
     | 
| 
      
 33 
     | 
    
         
            +
            						)
         
     | 
| 
      
 34 
     | 
    
         
            +
            					end
         
     | 
| 
      
 35 
     | 
    
         
            +
            					
         
     | 
| 
      
 36 
     | 
    
         
            +
            					def self.server(parent, server_window_bits: 15, server_no_context_takeover: false, **options)
         
     | 
| 
      
 37 
     | 
    
         
            +
            						self.new(parent,
         
     | 
| 
      
 38 
     | 
    
         
            +
            							window_bits: server_window_bits,
         
     | 
| 
      
 39 
     | 
    
         
            +
            							context_takeover: !server_no_context_takeover,
         
     | 
| 
      
 40 
     | 
    
         
            +
            							**options
         
     | 
| 
      
 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 
     | 
    
         
            +
            						@window_bits = window_bits
         
     | 
| 
      
 52 
     | 
    
         
            +
            						@context_takeover = context_takeover
         
     | 
| 
      
 53 
     | 
    
         
            +
            					end
         
     | 
| 
      
 54 
     | 
    
         
            +
            					
         
     | 
| 
      
 55 
     | 
    
         
            +
            					def read_message(frames, **options)
         
     | 
| 
      
 56 
     | 
    
         
            +
            						buffer = @parent.read_message(frames, **options)
         
     | 
| 
      
 57 
     | 
    
         
            +
            						
         
     | 
| 
      
 58 
     | 
    
         
            +
            						frame = frames.first
         
     | 
| 
      
 59 
     | 
    
         
            +
            						
         
     | 
| 
      
 60 
     | 
    
         
            +
            						if frame.flags & Frame::RSV1
         
     | 
| 
      
 61 
     | 
    
         
            +
            							buffer = self.inflate(buffer)
         
     | 
| 
      
 62 
     | 
    
         
            +
            						end
         
     | 
| 
      
 63 
     | 
    
         
            +
            						
         
     | 
| 
      
 64 
     | 
    
         
            +
            						frame.flags &= ~Frame::RSV1
         
     | 
| 
      
 65 
     | 
    
         
            +
            						
         
     | 
| 
      
 66 
     | 
    
         
            +
            						return buffer
         
     | 
| 
      
 67 
     | 
    
         
            +
            					end
         
     | 
| 
      
 68 
     | 
    
         
            +
            					
         
     | 
| 
      
 69 
     | 
    
         
            +
            					private
         
     | 
| 
      
 70 
     | 
    
         
            +
            					
         
     | 
| 
      
 71 
     | 
    
         
            +
            					def inflate(buffer)
         
     | 
| 
      
 72 
     | 
    
         
            +
            						Console.logger.info(self, "Inflating #{buffer.bytesize} bytes")
         
     | 
| 
      
 73 
     | 
    
         
            +
            						inflate = @inflate || Zlib::Inflate.new(-@window_bits)
         
     | 
| 
      
 74 
     | 
    
         
            +
            						
         
     | 
| 
      
 75 
     | 
    
         
            +
            						if @context_takeover
         
     | 
| 
      
 76 
     | 
    
         
            +
            							@inflate = inflate
         
     | 
| 
      
 77 
     | 
    
         
            +
            						end
         
     | 
| 
      
 78 
     | 
    
         
            +
            						
         
     | 
| 
      
 79 
     | 
    
         
            +
            						return inflate.inflate(buffer + TRAILER)
         
     | 
| 
      
 80 
     | 
    
         
            +
            					end
         
     | 
| 
      
 81 
     | 
    
         
            +
            				end
         
     | 
| 
      
 82 
     | 
    
         
            +
            			end
         
     | 
| 
      
 83 
     | 
    
         
            +
            		end
         
     | 
| 
      
 84 
     | 
    
         
            +
            	end
         
     | 
| 
      
 85 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,131 @@ 
     | 
|
| 
      
 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 
     | 
    
         
            +
            require_relative 'compression/inflate'
         
     | 
| 
      
 23 
     | 
    
         
            +
            require_relative 'compression/deflate'
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            module Protocol
         
     | 
| 
      
 26 
     | 
    
         
            +
            	module WebSocket
         
     | 
| 
      
 27 
     | 
    
         
            +
            		module Extension
         
     | 
| 
      
 28 
     | 
    
         
            +
            			module Compression
         
     | 
| 
      
 29 
     | 
    
         
            +
            				NAME = 'permessage-deflate'
         
     | 
| 
      
 30 
     | 
    
         
            +
            				
         
     | 
| 
      
 31 
     | 
    
         
            +
            				# Client offer to server, construct a list of requested compression parameters suitable for the `Sec-WebSocket-Extensions` header.
         
     | 
| 
      
 32 
     | 
    
         
            +
            				# @returns [Array(String)] a list of compression parameters suitable to send to the server.
         
     | 
| 
      
 33 
     | 
    
         
            +
            				def self.offer(client_window_bits: true, server_window_bits: true, client_no_context_takeover: false, server_no_context_takeover: false)
         
     | 
| 
      
 34 
     | 
    
         
            +
            					
         
     | 
| 
      
 35 
     | 
    
         
            +
            					header = [NAME]
         
     | 
| 
      
 36 
     | 
    
         
            +
            					
         
     | 
| 
      
 37 
     | 
    
         
            +
            					case client_window_bits
         
     | 
| 
      
 38 
     | 
    
         
            +
            					when 8..15
         
     | 
| 
      
 39 
     | 
    
         
            +
            						header << "client_max_window_bits=#{client_window_bits}"
         
     | 
| 
      
 40 
     | 
    
         
            +
            					when true
         
     | 
| 
      
 41 
     | 
    
         
            +
            						header << 'client_max_window_bits'
         
     | 
| 
      
 42 
     | 
    
         
            +
            					else
         
     | 
| 
      
 43 
     | 
    
         
            +
            						raise ArgumentError, "Invalid local maximum window bits!"
         
     | 
| 
      
 44 
     | 
    
         
            +
            					end
         
     | 
| 
      
 45 
     | 
    
         
            +
            					
         
     | 
| 
      
 46 
     | 
    
         
            +
            					if client_no_context_takeover
         
     | 
| 
      
 47 
     | 
    
         
            +
            						header << 'client_no_context_takeover'
         
     | 
| 
      
 48 
     | 
    
         
            +
            					end
         
     | 
| 
      
 49 
     | 
    
         
            +
            					
         
     | 
| 
      
 50 
     | 
    
         
            +
            					case server_window_bits
         
     | 
| 
      
 51 
     | 
    
         
            +
            					when 8..15
         
     | 
| 
      
 52 
     | 
    
         
            +
            						header << "server_max_window_bits=#{server_window_bits}"
         
     | 
| 
      
 53 
     | 
    
         
            +
            					when true
         
     | 
| 
      
 54 
     | 
    
         
            +
            						# Default (unspecified) to the server maximum window bits.
         
     | 
| 
      
 55 
     | 
    
         
            +
            					else
         
     | 
| 
      
 56 
     | 
    
         
            +
            						raise ArgumentError, "Invalid remote maximum window bits!"
         
     | 
| 
      
 57 
     | 
    
         
            +
            					end
         
     | 
| 
      
 58 
     | 
    
         
            +
            					
         
     | 
| 
      
 59 
     | 
    
         
            +
            					if server_no_context_takeover
         
     | 
| 
      
 60 
     | 
    
         
            +
            						header << 'server_no_context_takeover'
         
     | 
| 
      
 61 
     | 
    
         
            +
            					end
         
     | 
| 
      
 62 
     | 
    
         
            +
            					
         
     | 
| 
      
 63 
     | 
    
         
            +
            					return header
         
     | 
| 
      
 64 
     | 
    
         
            +
            				end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
            				# Negotiate on the server a response to client based on the incoming client offer.
         
     | 
| 
      
 67 
     | 
    
         
            +
            				# @parameter options [Hash] a hash of options which are accepted by the server.
         
     | 
| 
      
 68 
     | 
    
         
            +
            				# @returns [Array(String)] a list of compression parameters suitable to send back to the client.
         
     | 
| 
      
 69 
     | 
    
         
            +
            				def self.negotiate(arguments, **options)
         
     | 
| 
      
 70 
     | 
    
         
            +
            					header = [NAME]
         
     | 
| 
      
 71 
     | 
    
         
            +
            					
         
     | 
| 
      
 72 
     | 
    
         
            +
            					arguments.each do |key, value|
         
     | 
| 
      
 73 
     | 
    
         
            +
            						case key
         
     | 
| 
      
 74 
     | 
    
         
            +
            						when "server_no_context_takeover"
         
     | 
| 
      
 75 
     | 
    
         
            +
            							options[:server_no_context_takeover] = false
         
     | 
| 
      
 76 
     | 
    
         
            +
            							header << key
         
     | 
| 
      
 77 
     | 
    
         
            +
            						when "client_no_context_takeover"
         
     | 
| 
      
 78 
     | 
    
         
            +
            							options[:client_no_context_takeover] = false
         
     | 
| 
      
 79 
     | 
    
         
            +
            							header << key
         
     | 
| 
      
 80 
     | 
    
         
            +
            						when "server_max_window_bits"
         
     | 
| 
      
 81 
     | 
    
         
            +
            							options[:server_max_window_bits] = Integer(value || 15)
         
     | 
| 
      
 82 
     | 
    
         
            +
            						when "client_max_window_bits"
         
     | 
| 
      
 83 
     | 
    
         
            +
            							options[:client_max_window_bits] = Integer(value || 15)
         
     | 
| 
      
 84 
     | 
    
         
            +
            						else
         
     | 
| 
      
 85 
     | 
    
         
            +
            							raise ArgumentError, "Unknown option #{key}!"
         
     | 
| 
      
 86 
     | 
    
         
            +
            						end
         
     | 
| 
      
 87 
     | 
    
         
            +
            					end
         
     | 
| 
      
 88 
     | 
    
         
            +
            					
         
     | 
| 
      
 89 
     | 
    
         
            +
            					# The header which represents the final accepted/negotiated configuration.
         
     | 
| 
      
 90 
     | 
    
         
            +
            					return header
         
     | 
| 
      
 91 
     | 
    
         
            +
            				end
         
     | 
| 
      
 92 
     | 
    
         
            +
            				
         
     | 
| 
      
 93 
     | 
    
         
            +
            				# @parameter options [Hash] a hash of options which are accepted by the server.
         
     | 
| 
      
 94 
     | 
    
         
            +
            				def self.server(connection, **options)
         
     | 
| 
      
 95 
     | 
    
         
            +
            					connection.reserve!(Frame::RSV1)
         
     | 
| 
      
 96 
     | 
    
         
            +
            					
         
     | 
| 
      
 97 
     | 
    
         
            +
            					connection.reader = Inflate.server(connection.reader, **options)
         
     | 
| 
      
 98 
     | 
    
         
            +
            					connection.writer = Deflate.server(connection.writer, **options)
         
     | 
| 
      
 99 
     | 
    
         
            +
            				end
         
     | 
| 
      
 100 
     | 
    
         
            +
            				
         
     | 
| 
      
 101 
     | 
    
         
            +
            				# Accept on the client, the negotiated server response.
         
     | 
| 
      
 102 
     | 
    
         
            +
            				# @parameter options [Hash] a hash of options which are accepted by the client.
         
     | 
| 
      
 103 
     | 
    
         
            +
            				# @parameter arguments [Array(String)] a list of compression parameters as accepted/negotiated by the server.
         
     | 
| 
      
 104 
     | 
    
         
            +
            				def self.accept(arguments, **options)
         
     | 
| 
      
 105 
     | 
    
         
            +
            					arguments.each do |key, value|
         
     | 
| 
      
 106 
     | 
    
         
            +
            						case key
         
     | 
| 
      
 107 
     | 
    
         
            +
            						when "server_no_context_takeover"
         
     | 
| 
      
 108 
     | 
    
         
            +
            							options[:server_no_context_takeover] = false
         
     | 
| 
      
 109 
     | 
    
         
            +
            						when "client_no_context_takeover"
         
     | 
| 
      
 110 
     | 
    
         
            +
            							options[:client_no_context_takeover] = false
         
     | 
| 
      
 111 
     | 
    
         
            +
            						when "server_max_window_bits"
         
     | 
| 
      
 112 
     | 
    
         
            +
            							options[:server_max_window_bits] = Integer(value || 15)
         
     | 
| 
      
 113 
     | 
    
         
            +
            						when "client_max_window_bits"
         
     | 
| 
      
 114 
     | 
    
         
            +
            							options[:client_max_window_bits] = Integer(value || 15)
         
     | 
| 
      
 115 
     | 
    
         
            +
            						else
         
     | 
| 
      
 116 
     | 
    
         
            +
            							raise ArgumentError, "Unknown option #{key}!"
         
     | 
| 
      
 117 
     | 
    
         
            +
            						end
         
     | 
| 
      
 118 
     | 
    
         
            +
            					end
         
     | 
| 
      
 119 
     | 
    
         
            +
            				end
         
     | 
| 
      
 120 
     | 
    
         
            +
            				
         
     | 
| 
      
 121 
     | 
    
         
            +
            				# @parameter options [Hash] a hash of options which are accepted by the client.
         
     | 
| 
      
 122 
     | 
    
         
            +
            				def self.client(connection, **options)
         
     | 
| 
      
 123 
     | 
    
         
            +
            					connection.reserve!(Frame::RSV1)
         
     | 
| 
      
 124 
     | 
    
         
            +
            					
         
     | 
| 
      
 125 
     | 
    
         
            +
            					connection.reader = Inflate.client(connection.reader, **options)
         
     | 
| 
      
 126 
     | 
    
         
            +
            					connection.writer = Deflate.client(connection.writer, **options)
         
     | 
| 
      
 127 
     | 
    
         
            +
            				end
         
     | 
| 
      
 128 
     | 
    
         
            +
            			end
         
     | 
| 
      
 129 
     | 
    
         
            +
            		end
         
     | 
| 
      
 130 
     | 
    
         
            +
            	end
         
     | 
| 
      
 131 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -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,146 @@ 
     | 
|
| 
      
 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 
     | 
    
         
            +
            							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.server(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 header = klass.negotiate(arguments, **options)
         
     | 
| 
      
 125 
     | 
    
         
            +
            								# The extension is accepted and no further offers will be considered:
         
     | 
| 
      
 126 
     | 
    
         
            +
            								named.delete(name)
         
     | 
| 
      
 127 
     | 
    
         
            +
            								
         
     | 
| 
      
 128 
     | 
    
         
            +
            								yield header
         
     | 
| 
      
 129 
     | 
    
         
            +
            								
         
     | 
| 
      
 130 
     | 
    
         
            +
            								@accepted << [klass, options]
         
     | 
| 
      
 131 
     | 
    
         
            +
            							end
         
     | 
| 
      
 132 
     | 
    
         
            +
            						end
         
     | 
| 
      
 133 
     | 
    
         
            +
            					end
         
     | 
| 
      
 134 
     | 
    
         
            +
            					
         
     | 
| 
      
 135 
     | 
    
         
            +
            					return headers
         
     | 
| 
      
 136 
     | 
    
         
            +
            				end
         
     | 
| 
      
 137 
     | 
    
         
            +
            				
         
     | 
| 
      
 138 
     | 
    
         
            +
            				def apply(connection)
         
     | 
| 
      
 139 
     | 
    
         
            +
            					@accepted.reverse_each do |(klass, options)|
         
     | 
| 
      
 140 
     | 
    
         
            +
            						klass.server(connection, **options)
         
     | 
| 
      
 141 
     | 
    
         
            +
            					end
         
     | 
| 
      
 142 
     | 
    
         
            +
            				end
         
     | 
| 
      
 143 
     | 
    
         
            +
            			end
         
     | 
| 
      
 144 
     | 
    
         
            +
            		end
         
     | 
| 
      
 145 
     | 
    
         
            +
            	end
         
     | 
| 
      
 146 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -25,16 +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 
     | 
    
         | 
| 
      
 33 
     | 
    
         
            +
            			OPCODE = 0
         
     | 
| 
      
 34 
     | 
    
         
            +
            						
         
     | 
| 
       30 
35 
     | 
    
         
             
            			# @parameter length [Integer] The length of the payload, or nil if the header has not been read yet.
         
     | 
| 
       31 
36 
     | 
    
         
             
            			# @parameter mask [Boolean | String] An optional 4-byte string which is used to mask the payload.
         
     | 
| 
       32 
     | 
    
         
            -
            			def initialize(finished = true, payload = nil, opcode: self.class::OPCODE, mask: false)
         
     | 
| 
      
 37 
     | 
    
         
            +
            			def initialize(finished = true, payload = nil, flags: 0, opcode: self.class::OPCODE, mask: false)
         
     | 
| 
       33 
38 
     | 
    
         
             
            				if mask == true
         
     | 
| 
       34 
39 
     | 
    
         
             
            					mask = SecureRandom.bytes(4)
         
     | 
| 
       35 
40 
     | 
    
         
             
            				end
         
     | 
| 
       36 
41 
     | 
    
         | 
| 
       37 
42 
     | 
    
         
             
            				@finished = finished
         
     | 
| 
      
 43 
     | 
    
         
            +
            				@flags = flags
         
     | 
| 
       38 
44 
     | 
    
         
             
            				@opcode = opcode
         
     | 
| 
       39 
45 
     | 
    
         
             
            				@mask = mask
         
     | 
| 
       40 
46 
     | 
    
         
             
            				@length = payload&.bytesize
         
     | 
| 
         @@ -46,11 +52,11 @@ module Protocol 
     | 
|
| 
       46 
52 
     | 
    
         
             
            			end
         
     | 
| 
       47 
53 
     | 
    
         | 
| 
       48 
54 
     | 
    
         
             
            			def to_ary
         
     | 
| 
       49 
     | 
    
         
            -
            				[@finished, @opcode, @mask, @length, @payload]
         
     | 
| 
      
 55 
     | 
    
         
            +
            				[@finished, @flags, @opcode, @mask, @length, @payload]
         
     | 
| 
       50 
56 
     | 
    
         
             
            			end
         
     | 
| 
       51 
57 
     | 
    
         | 
| 
       52 
58 
     | 
    
         
             
            			def control?
         
     | 
| 
       53 
     | 
    
         
            -
            				@opcode & 0x8
         
     | 
| 
      
 59 
     | 
    
         
            +
            				@opcode & 0x8 != 0
         
     | 
| 
       54 
60 
     | 
    
         
             
            			end
         
     | 
| 
       55 
61 
     | 
    
         | 
| 
       56 
62 
     | 
    
         
             
            			def data?
         
     | 
| 
         @@ -87,6 +93,7 @@ module Protocol 
     | 
|
| 
       87 
93 
     | 
    
         
             
            			# +---------------------------------------------------------------+
         
     | 
| 
       88 
94 
     | 
    
         | 
| 
       89 
95 
     | 
    
         
             
            			attr_accessor :finished
         
     | 
| 
      
 96 
     | 
    
         
            +
            			attr_accessor :flags
         
     | 
| 
       90 
97 
     | 
    
         
             
            			attr_accessor :opcode
         
     | 
| 
       91 
98 
     | 
    
         
             
            			attr_accessor :mask
         
     | 
| 
       92 
99 
     | 
    
         
             
            			attr_accessor :length
         
     | 
| 
         @@ -98,9 +105,9 @@ module Protocol 
     | 
|
| 
       98 
105 
     | 
    
         
             
            				if length.bit_length > 63
         
     | 
| 
       99 
106 
     | 
    
         
             
            					raise ProtocolError, "Frame length #{@length} bigger than allowed maximum!"
         
     | 
| 
       100 
107 
     | 
    
         
             
            				end
         
     | 
| 
       101 
     | 
    
         
            -
             
     | 
| 
      
 108 
     | 
    
         
            +
            				
         
     | 
| 
       102 
109 
     | 
    
         
             
            				if @mask
         
     | 
| 
       103 
     | 
    
         
            -
            					@payload = String.new 
     | 
| 
      
 110 
     | 
    
         
            +
            					@payload = String.new(encoding: Encoding::BINARY)
         
     | 
| 
       104 
111 
     | 
    
         | 
| 
       105 
112 
     | 
    
         
             
            					for i in 0...data.bytesize do
         
     | 
| 
       106 
113 
     | 
    
         
             
            						@payload << (data.getbyte(i) ^ mask.getbyte(i % 4))
         
     | 
| 
         @@ -111,11 +118,13 @@ module Protocol 
     | 
|
| 
       111 
118 
     | 
    
         
             
            					@payload = data
         
     | 
| 
       112 
119 
     | 
    
         
             
            					@length = length
         
     | 
| 
       113 
120 
     | 
    
         
             
            				end
         
     | 
| 
      
 121 
     | 
    
         
            +
            				
         
     | 
| 
      
 122 
     | 
    
         
            +
            				return self
         
     | 
| 
       114 
123 
     | 
    
         
             
            			end
         
     | 
| 
       115 
124 
     | 
    
         | 
| 
       116 
125 
     | 
    
         
             
            			def unpack
         
     | 
| 
       117 
126 
     | 
    
         
             
            				if @mask and !@payload.empty?
         
     | 
| 
       118 
     | 
    
         
            -
            					data = String.new 
     | 
| 
      
 127 
     | 
    
         
            +
            					data = String.new(encoding: Encoding::BINARY)
         
     | 
| 
       119 
128 
     | 
    
         | 
| 
       120 
129 
     | 
    
         
             
            					for i in 0...@payload.bytesize do
         
     | 
| 
       121 
130 
     | 
    
         
             
            						data << (@payload.getbyte(i) ^ @mask.getbyte(i % 4))
         
     | 
| 
         @@ -135,19 +144,33 @@ module Protocol 
     | 
|
| 
       135 
144 
     | 
    
         
             
            				byte = buffer.unpack("C").first
         
     | 
| 
       136 
145 
     | 
    
         | 
| 
       137 
146 
     | 
    
         
             
            				finished = (byte & 0b1000_0000 != 0)
         
     | 
| 
       138 
     | 
    
         
            -
            				 
     | 
| 
      
 147 
     | 
    
         
            +
            				flags = (byte & 0b0111_0000) >> 4
         
     | 
| 
       139 
148 
     | 
    
         
             
            				opcode = byte & 0b0000_1111
         
     | 
| 
       140 
149 
     | 
    
         | 
| 
       141 
     | 
    
         
            -
            				 
     | 
| 
      
 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
         
     | 
| 
       142 
157 
     | 
    
         
             
            			end
         
     | 
| 
       143 
158 
     | 
    
         | 
| 
       144 
     | 
    
         
            -
            			def self.read(finished, opcode, stream, maximum_frame_size)
         
     | 
| 
      
 159 
     | 
    
         
            +
            			def self.read(finished, flags, opcode, stream, maximum_frame_size)
         
     | 
| 
       145 
160 
     | 
    
         
             
            				buffer = stream.read(1) or raise EOFError, "Could not read header!"
         
     | 
| 
       146 
161 
     | 
    
         
             
            				byte = buffer.unpack("C").first
         
     | 
| 
       147 
162 
     | 
    
         | 
| 
       148 
163 
     | 
    
         
             
            				mask = (byte & 0b1000_0000 != 0)
         
     | 
| 
       149 
164 
     | 
    
         
             
            				length = byte & 0b0111_1111
         
     | 
| 
       150 
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 
     | 
    
         
            +
            				
         
     | 
| 
       151 
174 
     | 
    
         
             
            				if length == 126
         
     | 
| 
       152 
175 
     | 
    
         
             
            					buffer = stream.read(2) or raise EOFError, "Could not read length!"
         
     | 
| 
       153 
176 
     | 
    
         
             
            					length = buffer.unpack('n').first
         
     | 
| 
         @@ -170,11 +193,11 @@ module Protocol 
     | 
|
| 
       170 
193 
     | 
    
         
             
            					raise EOFError, "Incorrect payload length: #{@length} != #{@payload.bytesize}!"
         
     | 
| 
       171 
194 
     | 
    
         
             
            				end
         
     | 
| 
       172 
195 
     | 
    
         | 
| 
       173 
     | 
    
         
            -
            				return self.new(finished, payload, opcode: opcode, mask: mask)
         
     | 
| 
      
 196 
     | 
    
         
            +
            				return self.new(finished, payload, flags: flags, opcode: opcode, mask: mask)
         
     | 
| 
       174 
197 
     | 
    
         
             
            			end
         
     | 
| 
       175 
198 
     | 
    
         | 
| 
       176 
199 
     | 
    
         
             
            			def write(stream)
         
     | 
| 
       177 
     | 
    
         
            -
            				buffer = String.new 
     | 
| 
      
 200 
     | 
    
         
            +
            				buffer = String.new(encoding: Encoding::BINARY)
         
     | 
| 
       178 
201 
     | 
    
         | 
| 
       179 
202 
     | 
    
         
             
            				if @payload&.bytesize != @length
         
     | 
| 
       180 
203 
     | 
    
         
             
            					raise ProtocolError, "Invalid payload length: #{@length} != #{@payload.bytesize} for #{self}!"
         
     | 
| 
         @@ -193,7 +216,7 @@ module Protocol 
     | 
|
| 
       193 
216 
     | 
    
         
             
            				end
         
     | 
| 
       194 
217 
     | 
    
         | 
| 
       195 
218 
     | 
    
         
             
            				buffer << [
         
     | 
| 
       196 
     | 
    
         
            -
            					(@finished ? 0b1000_0000 : 0) | @opcode,
         
     | 
| 
      
 219 
     | 
    
         
            +
            					(@finished ? 0b1000_0000 : 0) | (@flags << 4) | @opcode,
         
     | 
| 
       197 
220 
     | 
    
         
             
            					(@mask ? 0b1000_0000 : 0) | short_length,
         
     | 
| 
       198 
221 
     | 
    
         
             
            				].pack('CC')
         
     | 
| 
       199 
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 
     | 
    
         | 
| 
         @@ -26,10 +26,13 @@ module Protocol 
     | 
|
| 
       26 
26 
     | 
    
         
             
            		class PingFrame < Frame
         
     | 
| 
       27 
27 
     | 
    
         
             
            			OPCODE = 0x9
         
     | 
| 
       28 
28 
     | 
    
         | 
| 
      
 29 
     | 
    
         
            +
            			# Generate a suitable reply.
         
     | 
| 
      
 30 
     | 
    
         
            +
            			# @returns [PongFrame]
         
     | 
| 
       29 
31 
     | 
    
         
             
            			def reply(**options)
         
     | 
| 
       30 
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
         
     | 
| 
         @@ -22,6 +22,7 @@ require_relative 'frame' 
     | 
|
| 
       22 
22 
     | 
    
         | 
| 
       23 
23 
     | 
    
         
             
            module Protocol
         
     | 
| 
       24 
24 
     | 
    
         
             
            	module WebSocket
         
     | 
| 
      
 25 
     | 
    
         
            +
            		# Implements the text frame for sending and receiving text.
         
     | 
| 
       25 
26 
     | 
    
         
             
            		class TextFrame < Frame
         
     | 
| 
       26 
27 
     | 
    
         
             
            			OPCODE = 0x1
         
     | 
| 
       27 
28 
     | 
    
         | 
| 
         @@ -29,14 +30,19 @@ module Protocol 
     | 
|
| 
       29 
30 
     | 
    
         
             
            				true
         
     | 
| 
       30 
31 
     | 
    
         
             
            			end
         
     | 
| 
       31 
32 
     | 
    
         | 
| 
       32 
     | 
    
         
            -
            			 
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
            			 
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
            				 
     | 
| 
      
 33 
     | 
    
         
            +
            			# Decode the binary buffer into a suitable text message.
         
     | 
| 
      
 34 
     | 
    
         
            +
            			# @parameter buffer [String] The binary data to unpack.
         
     | 
| 
      
 35 
     | 
    
         
            +
            			def read_message(buffer)
         
     | 
| 
      
 36 
     | 
    
         
            +
            				buffer.force_encoding(Encoding::UTF_8)
         
     | 
| 
      
 37 
     | 
    
         
            +
            				
         
     | 
| 
      
 38 
     | 
    
         
            +
            				unless buffer.valid_encoding?
         
     | 
| 
      
 39 
     | 
    
         
            +
            					raise ProtocolError, "invalid UTF-8 in text frame!"
         
     | 
| 
      
 40 
     | 
    
         
            +
            				end
         
     | 
| 
      
 41 
     | 
    
         
            +
            				
         
     | 
| 
      
 42 
     | 
    
         
            +
            				buffer
         
     | 
| 
       38 
43 
     | 
    
         
             
            			end
         
     | 
| 
       39 
44 
     | 
    
         | 
| 
      
 45 
     | 
    
         
            +
            			# Apply this frame to the specified connection.
         
     | 
| 
       40 
46 
     | 
    
         
             
            			def apply(connection)
         
     | 
| 
       41 
47 
     | 
    
         
             
            				connection.receive_text(self)
         
     | 
| 
       42 
48 
     | 
    
         
             
            			end
         
     | 
    
        data/lib/protocol/websocket.rb
    CHANGED
    
    
    
        data.tar.gz.sig
    ADDED
    
    | 
         @@ -0,0 +1,3 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            }�	*>��K��X�a��*3��j���ss�U��0p2�����SE�j9�*�{�b��v��z��	�π�hf<}�>Ɩȶ����us9�v��i�P���TB<�s^�W�AP!��*{����c�P��_�9�K*�{"(�T&%(�hy�g�M3lR4�C�sά�m�ڤ����`�4*/v
         
     | 
| 
      
 2 
     | 
    
         
            +
            �`�LA0�d��R�U�ޥ��������������������8<I
         
     | 
| 
      
 3 
     | 
    
         
            +
            x���軦�_����%uW>2��Z�*��C�"`0Ƞ[�`�M�C���F�F���7.aq��f��u˄!��������'�]�Q@�c�
         
     | 
| 
      
 4 
     | 
    
         
            +
            ���(���l�33MΩ:�w�C'����~r�AfC*���4yp���z���\JZ�%�;]c��
         
     | 
    
        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.8.0
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Samuel Williams
         
     | 
| 
      
 8 
     | 
    
         
            +
            - Aurora
         
     | 
| 
      
 9 
     | 
    
         
            +
            - Soumya
         
     | 
| 
      
 10 
     | 
    
         
            +
            - Olle Jonsson
         
     | 
| 
      
 11 
     | 
    
         
            +
            - William T. Nelson
         
     | 
| 
       8 
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-20 00:00:00.000000000 Z
         
     | 
| 
       12 
45 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
46 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
47 
     | 
    
         
             
              name: protocol-http
         
     | 
| 
         @@ -92,6 +125,11 @@ files: 
     | 
|
| 
       92 
125 
     | 
    
         
             
            - lib/protocol/websocket/connection.rb
         
     | 
| 
       93 
126 
     | 
    
         
             
            - lib/protocol/websocket/continuation_frame.rb
         
     | 
| 
       94 
127 
     | 
    
         
             
            - lib/protocol/websocket/error.rb
         
     | 
| 
      
 128 
     | 
    
         
            +
            - lib/protocol/websocket/extension/compression.rb
         
     | 
| 
      
 129 
     | 
    
         
            +
            - lib/protocol/websocket/extension/compression/deflate.rb
         
     | 
| 
      
 130 
     | 
    
         
            +
            - lib/protocol/websocket/extension/compression/inflate.rb
         
     | 
| 
      
 131 
     | 
    
         
            +
            - lib/protocol/websocket/extensions.md
         
     | 
| 
      
 132 
     | 
    
         
            +
            - lib/protocol/websocket/extensions.rb
         
     | 
| 
       95 
133 
     | 
    
         
             
            - lib/protocol/websocket/frame.rb
         
     | 
| 
       96 
134 
     | 
    
         
             
            - lib/protocol/websocket/framer.rb
         
     | 
| 
       97 
135 
     | 
    
         
             
            - lib/protocol/websocket/headers.rb
         
     | 
| 
         @@ -118,7 +156,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement 
     | 
|
| 
       118 
156 
     | 
    
         
             
                - !ruby/object:Gem::Version
         
     | 
| 
       119 
157 
     | 
    
         
             
                  version: '0'
         
     | 
| 
       120 
158 
     | 
    
         
             
            requirements: []
         
     | 
| 
       121 
     | 
    
         
            -
            rubygems_version: 3. 
     | 
| 
      
 159 
     | 
    
         
            +
            rubygems_version: 3.3.7
         
     | 
| 
       122 
160 
     | 
    
         
             
            signing_key:
         
     | 
| 
       123 
161 
     | 
    
         
             
            specification_version: 4
         
     | 
| 
       124 
162 
     | 
    
         
             
            summary: A low level implementation of the WebSocket protocol.
         
     | 
    
        metadata.gz.sig
    ADDED
    
    | 
         Binary file 
     |