Class: Tep::WebSocket::Frame

Inherits:
Object
  • Object
show all
Defined in:
lib/tep/websocket/frame.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(fin, opcode, payload) ⇒ Frame

Returns a new instance of Frame.



26
27
28
29
30
# File 'lib/tep/websocket/frame.rb', line 26

def initialize(fin, opcode, payload)
  @fin     = fin
  @opcode  = opcode
  @payload = payload
end

Instance Attribute Details

#finObject

Returns the value of attribute fin.



24
25
26
# File 'lib/tep/websocket/frame.rb', line 24

def fin
  @fin
end

#opcodeObject

Returns the value of attribute opcode.



24
25
26
# File 'lib/tep/websocket/frame.rb', line 24

def opcode
  @opcode
end

#payloadObject

Returns the value of attribute payload.



24
25
26
# File 'lib/tep/websocket/frame.rb', line 24

def payload
  @payload
end

Class Method Details

.byte_to_chr(n) ⇒ Object

Convert a single byte value (0..255) to a 1-char String.



58
59
60
# File 'lib/tep/websocket/frame.rb', line 58

def self.byte_to_chr(n)
  (n & 0xff).chr
end

.control_opcode?(op) ⇒ Boolean

Returns:

  • (Boolean)


213
214
215
216
217
# File 'lib/tep/websocket/frame.rb', line 213

def self.control_opcode?(op)
  op == Tep::WebSocket::OPCODE_CLOSE ||
    op == Tep::WebSocket::OPCODE_PING ||
    op == Tep::WebSocket::OPCODE_PONG
end

.parse_from_buf(start, avail) ⇒ Object

Parse one frame from the sphttp recv frame buffer. ‘start` is the byte offset to begin reading; `avail` is the count of valid bytes in the buffer. Byte reads go through the Ruby String binding sphttp_recv_frame_buf returns; matz/spinel#657 made slice / bytes survive embedded NULs so binary payloads parse correctly without the per-byte C accessor we used before.

Returns a ParseResult with one of three shapes:

.status == "ok"      -> .frame populated + .consumed bytes used
.status == "need"    -> need more bytes (consumed == 0)
.status == "close"   -> protocol violation; close with .close_code


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
# File 'lib/tep/websocket/frame.rb', line 74

def self.parse_from_buf(start, avail)
  out = Tep::WebSocket::ParseResult.new
  if avail - start < 2
    out.outcome = "need"
    return out
  end

  buf = Sock.sphttp_recv_frame_buf
  bs  = buf.bytes
  b0 = bs[start]
  b1 = bs[start + 1]
  fin    = (b0 & 0x80) != 0
  rsv    = b0 & 0x70
  opcode = b0 & 0x0f
  masked = (b1 & 0x80) != 0
  len7   = b1 & 0x7f

  if rsv != 0
    out.outcome = "close"
    out.close_code = Tep::WebSocket::CLOSE_PROTOCOL_ERROR
    return out
  end
  if Frame.reserved_opcode?(opcode)
    out.outcome = "close"
    out.close_code = Tep::WebSocket::CLOSE_PROTOCOL_ERROR
    return out
  end
  if Frame.control_opcode?(opcode)
    if !fin
      out.outcome = "close"
      out.close_code = Tep::WebSocket::CLOSE_PROTOCOL_ERROR
      return out
    end
    if len7 > 125
      out.outcome = "close"
      out.close_code = Tep::WebSocket::CLOSE_PROTOCOL_ERROR
      return out
    end
  end
  if !masked
    # Server MUST close on unmasked client frame (§5.3).
    out.outcome = "close"
    out.close_code = Tep::WebSocket::CLOSE_PROTOCOL_ERROR
    return out
  end

  # Decode payload length.
  pos = start + 2
  plen = 0
  if len7 < 126
    plen = len7
  elsif len7 == 126
    if avail - pos < 2
      out.outcome = "need"
      return out
    end
    h = bs[pos]
    l = bs[pos + 1]
    plen = (h << 8) | l
    pos += 2
  else
    # 64-bit length
    if avail - pos < 8
      out.outcome = "need"
      return out
    end
    plen = 0
    i = 0
    while i < 8
      plen = (plen << 8) | bs[pos + i]
      i += 1
    end
    pos += 8
  end

  # 4-byte mask key.
  if avail - pos < 4
    out.outcome = "need"
    return out
  end
  m0 = bs[pos]
  m1 = bs[pos + 1]
  m2 = bs[pos + 2]
  m3 = bs[pos + 3]
  pos += 4

  # Payload bytes.
  if avail - pos < plen
    out.outcome = "need"
    return out
  end

  # Decode + unmask in one pass.
  payload = ""
  i = 0
  while i < plen
    b = bs[pos + i]
    mask_byte = 0
    if (i & 3) == 0
      mask_byte = m0
    elsif (i & 3) == 1
      mask_byte = m1
    elsif (i & 3) == 2
      mask_byte = m2
    else
      mask_byte = m3
    end
    payload = payload + Frame.byte_to_chr(b ^ mask_byte)
    i += 1
  end

  out.outcome   = "ok"
  out.frame    = Tep::WebSocket::Frame.new(fin, opcode, payload)
  out.consumed = pos + plen - start
  out
end

.reserved_opcode?(op) ⇒ Boolean

Returns:

  • (Boolean)


191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/tep/websocket/frame.rb', line 191

def self.reserved_opcode?(op)
  if op == Tep::WebSocket::OPCODE_CONTINUATION
    return false
  end
  if op == Tep::WebSocket::OPCODE_TEXT
    return false
  end
  if op == Tep::WebSocket::OPCODE_BINARY
    return false
  end
  if op == Tep::WebSocket::OPCODE_CLOSE
    return false
  end
  if op == Tep::WebSocket::OPCODE_PING
    return false
  end
  if op == Tep::WebSocket::OPCODE_PONG
    return false
  end
  true
end

Instance Method Details

#encode_unmaskedObject

Build the unmasked server-side wire bytes. Length-encoding picks the smallest form that fits the payload. No mask.



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/tep/websocket/frame.rb', line 34

def encode_unmasked
  head = ""
  b0 = (@fin ? 0x80 : 0x00) | (@opcode & 0x0f)
  head = head + Frame.byte_to_chr(b0)

  plen = @payload.length
  if plen <= 125
    head = head + Frame.byte_to_chr(plen)
  elsif plen <= 65535
    head = head + Frame.byte_to_chr(126)
    head = head + Frame.byte_to_chr((plen >> 8) & 0xff)
    head = head + Frame.byte_to_chr(plen & 0xff)
  else
    head = head + Frame.byte_to_chr(127)
    i = 7
    while i >= 0
      head = head + Frame.byte_to_chr((plen >> (i * 8)) & 0xff)
      i -= 1
    end
  end
  head + @payload
end