Class: EventMachine::WebSocketCodec::Decoder
- Inherits:
-
Object
- Object
- EventMachine::WebSocketCodec::Decoder
- Includes:
- Protocol
- Defined in:
- lib/em-ws-client/decoder.rb
Overview
Internal: A WebSocket frame decoder based on RFC 6455
Constant Summary
Constants included from Protocol
Protocol::BINARY_FRAME, Protocol::CLOSE, Protocol::CONTINUATION, Protocol::PING, Protocol::PONG, Protocol::TEXT_FRAME
Instance Method Summary collapse
-
#<<(data) ⇒ Object
Public: Feed the decoder raw data from the wire.
-
#initialize ⇒ Decoder
constructor
A new instance of Decoder.
- #onclose(&block) ⇒ Object
- #onerror(&block) ⇒ Object
- #onframe(&block) ⇒ Object
- #onping(&block) ⇒ Object
- #onpong(&block) ⇒ Object
Constructor Details
#initialize ⇒ Decoder
Returns a new instance of Decoder.
11 12 13 14 15 16 |
# File 'lib/em-ws-client/decoder.rb', line 11 def initialize @fragmented = false @buffer = "" @chunks = nil @callbacks = {} end |
Instance Method Details
#<<(data) ⇒ Object
Public: Feed the decoder raw data from the wire
data - The raw websocket frame data
Examples
decoder << raw
Returns nothing
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/em-ws-client/decoder.rb', line 33 def << data # put the data into the buffer, as # we might be replaying if data @buffer << data end # Don't do work if we don't have to if @buffer.length < 2 return end # decode the first 2 bytes, with # opcode, lengthgth, masking bit, and frag bit h1, h2 = @buffer.unpack("CC") # check the fragmentation bit to see # if this is a message fragment fin = ((h1 & 0x80) == 0x80) # used to keep track of our position in the buffer offset = 2 # see above for possible opcodes opcode = (h1 & 0x0F) # the leading length idicator length = (h2 & 0x7F) # masking bit, is the data masked with # a specified masking key? masked = ((h2 & 0x80) == 0x80) # Find errors and fail fast if h1 & 0b01110000 != 0 return emit :error, 1002, "RSV bits must be 0" end if opcode > 7 if !fin return emit :error, 1002, "Control frame cannot be fragmented" elsif length > 125 return emit :error, 1002, "Control frame is too large #{length}" elsif opcode > 0xA return emit :error, 1002, "Unexpected reserved opcode #{opcode}" elsif opcode == CLOSE && length == 1 return emit :error, 1002, "Close control frame with payload of length 1" end else if opcode != CONTINUATION && opcode != TEXT_FRAME && opcode != BINARY_FRAME return emit :error, 1002, "Unexpected reserved opcode #{opcode}" end end # Get the actual size of the payload if length > 125 if length == 126 length = @buffer.unpack("@#{offset}n").first offset += 2 else length = @buffer.unpack("@#{offset}L!>").first offset += 8 end end # unpack the masking key if masked key = @buffer.unpack("@#{offset}N").first offset += 4 end # replay on next frame if @buffer.size < (length + offset) return false end # Read the important bits payload = @buffer.unpack("@#{offset}C#{length}") # Unmask the data if it"s masked if masked payload.bytesize.times do |i| payload[i] = ((payload[i] ^ (key >> ((3 - (i % 4)) * 8))) & 0xFF) end end payload = payload.pack("C*") case opcode when CONTINUATION # We shouldn't get a contination without # knowing whether or not it's binary or text unless @fragmented return emit :error, 1002, "Unexepected continuation" end if @fragmented == :text @chunks << payload.force_encoding("UTF-8") else @chunks << payload end if fin if @fragmented == :text && !valid_utf8?(@chunks) return emit :error, 1007, "Invalid UTF" end emit :frame, @chunks, @fragmented == :binary @chunks = nil @fragmented = false end when TEXT_FRAME # We shouldn't get a text frame when we # are expecting a continuation if @fragmented return emit :error, 1002, "Unexepected frame" end # emit or buffer if fin unless valid_utf8?(payload) return emit :error, 1007, "Invalid UTF Hmm" end emit :frame, payload, false else @chunks = payload.force_encoding("UTF-8") @fragmented = :text end when BINARY_FRAME # We shouldn't get a text frame when we # are expecting a continuation if @fragmented return emit :error, 1002, "Unexepected frame" end # emit or buffer if fin emit :frame, payload, true else @chunks = payload @fragmented = :binary end when CLOSE code, explain = payload.unpack("nA*") if explain && !valid_utf8?(explain) emit :close, 1007 else emit :close, response_close_code(code) end when PING emit :ping, payload when PONG emit :pong, payload end # Remove data we made use of and call back # TODO: remove recursion @buffer = @buffer[offset + length..-1] || "" if not @buffer.empty? self << nil end end |
#onclose(&block) ⇒ Object
18 |
# File 'lib/em-ws-client/decoder.rb', line 18 def onclose █ @callbacks[:close] = block; end |
#onerror(&block) ⇒ Object
22 |
# File 'lib/em-ws-client/decoder.rb', line 22 def onerror █ @callbacks[:error] = block; end |
#onframe(&block) ⇒ Object
21 |
# File 'lib/em-ws-client/decoder.rb', line 21 def onframe █ @callbacks[:frame] = block; end |
#onping(&block) ⇒ Object
19 |
# File 'lib/em-ws-client/decoder.rb', line 19 def onping █ @callbacks[:ping] = block; end |
#onpong(&block) ⇒ Object
20 |
# File 'lib/em-ws-client/decoder.rb', line 20 def onpong █ @callbacks[:pong] = block; end |