1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
|
-- Copyright (C) Yichun Zhang (agentzh)
local bit = require "bit"
local ffi = require "ffi"
local byte = string.byte
local char = string.char
local sub = string.sub
local band = bit.band
local bor = bit.bor
local bxor = bit.bxor
local lshift = bit.lshift
local rshift = bit.rshift
--local tohex = bit.tohex
local tostring = tostring
local concat = table.concat
local rand = math.random
local type = type
local debug = ngx.config.debug
local ngx_log = ngx.log
local ngx_DEBUG = ngx.DEBUG
local ffi_new = ffi.new
local ffi_string = ffi.string
local ok, new_tab = pcall(require, "table.new")
if not ok then
new_tab = function (narr, nrec) return {} end
end
local _M = new_tab(0, 5)
_M.new_tab = new_tab
_M._VERSION = '0.13'
local types = {
[0x0] = "continuation",
[0x1] = "text",
[0x2] = "binary",
[0x8] = "close",
[0x9] = "ping",
[0xa] = "pong",
}
local str_buf_size = 4096
local str_buf
local c_buf_type = ffi.typeof("char[?]")
local function get_string_buf(size)
if size > str_buf_size then
return ffi_new(c_buf_type, size)
end
if not str_buf then
str_buf = ffi_new(c_buf_type, str_buf_size)
end
return str_buf
end
function _M.recv_frame(sock, max_payload_len, force_masking)
local data, err = sock:receive(2)
if not data then
return nil, nil, "failed to receive the first 2 bytes: " .. err
end
local fst, snd = byte(data, 1, 2)
local fin = band(fst, 0x80) ~= 0
-- print("fin: ", fin)
if band(fst, 0x70) ~= 0 then
return nil, nil, "bad RSV1, RSV2, or RSV3 bits"
end
local opcode = band(fst, 0x0f)
-- print("opcode: ", tohex(opcode))
if opcode >= 0x3 and opcode <= 0x7 then
return nil, nil, "reserved non-control frames"
end
if opcode >= 0xb and opcode <= 0xf then
return nil, nil, "reserved control frames"
end
local mask = band(snd, 0x80) ~= 0
if debug then
ngx_log(ngx_DEBUG, "recv_frame: mask bit: ", mask and 1 or 0)
end
if force_masking and not mask then
return nil, nil, "frame unmasked"
end
local payload_len = band(snd, 0x7f)
-- print("payload len: ", payload_len)
if payload_len == 126 then
local data, err = sock:receive(2)
if not data then
return nil, nil, "failed to receive the 2 byte payload length: "
.. (err or "unknown")
end
payload_len = bor(lshift(byte(data, 1), 8), byte(data, 2))
elseif payload_len == 127 then
local data, err = sock:receive(8)
if not data then
return nil, nil, "failed to receive the 8 byte payload length: "
.. (err or "unknown")
end
if byte(data, 1) ~= 0
or byte(data, 2) ~= 0
or byte(data, 3) ~= 0
or byte(data, 4) ~= 0
then
return nil, nil, "payload len too large"
end
local fifth = byte(data, 5)
if band(fifth, 0x80) ~= 0 then
return nil, nil, "payload len too large"
end
payload_len = bor(lshift(fifth, 24),
lshift(byte(data, 6), 16),
lshift(byte(data, 7), 8),
byte(data, 8))
end
if band(opcode, 0x8) ~= 0 then
-- being a control frame
if payload_len > 125 then
return nil, nil, "too long payload for control frame"
end
if not fin then
return nil, nil, "fragmented control frame"
end
end
-- print("payload len: ", payload_len, ", max payload len: ",
-- max_payload_len)
if payload_len > max_payload_len then
return nil, nil, "exceeding max payload len"
end
local rest
if mask then
rest = payload_len + 4
else
rest = payload_len
end
-- print("rest: ", rest)
local data
if rest > 0 then
data, err = sock:receive(rest)
if not data then
return nil, nil, "failed to read masking-len and payload: "
.. (err or "unknown")
end
else
data = ""
end
-- print("received rest")
if opcode == 0x8 then
-- being a close frame
if payload_len > 0 then
if payload_len < 2 then
return nil, nil, "close frame with a body must carry a 2-byte"
.. " status code"
end
local msg, code
if mask then
local fst = bxor(byte(data, 4 + 1), byte(data, 1))
local snd = bxor(byte(data, 4 + 2), byte(data, 2))
code = bor(lshift(fst, 8), snd)
if payload_len > 2 then
-- TODO string.buffer optimizations
local bytes = get_string_buf(payload_len - 2)
for i = 3, payload_len do
bytes[i - 3] = bxor(byte(data, 4 + i),
byte(data, (i - 1) % 4 + 1))
end
msg = ffi_string(bytes, payload_len - 2)
else
msg = ""
end
else
local fst = byte(data, 1)
local snd = byte(data, 2)
code = bor(lshift(fst, 8), snd)
-- print("parsing unmasked close frame payload: ", payload_len)
if payload_len > 2 then
msg = sub(data, 3)
else
msg = ""
end
end
return msg, "close", code
end
return "", "close", nil
end
local msg
if mask then
-- TODO string.buffer optimizations
local bytes = get_string_buf(payload_len)
for i = 1, payload_len do
bytes[i - 1] = bxor(byte(data, 4 + i),
byte(data, (i - 1) % 4 + 1))
end
msg = ffi_string(bytes, payload_len)
else
msg = data
end
return msg, types[opcode], not fin and "again" or nil
end
local function build_frame(fin, opcode, payload_len, payload, masking)
-- XXX optimize this when we have string.buffer in LuaJIT 2.1
local fst
if fin then
fst = bor(0x80, opcode)
else
fst = opcode
end
local snd, extra_len_bytes
if payload_len <= 125 then
snd = payload_len
extra_len_bytes = ""
elseif payload_len <= 65535 then
snd = 126
extra_len_bytes = char(band(rshift(payload_len, 8), 0xff),
band(payload_len, 0xff))
else
if band(payload_len, 0x7fffffff) < payload_len then
return nil, "payload too big"
end
snd = 127
-- XXX we only support 31-bit length here
extra_len_bytes = char(0, 0, 0, 0, band(rshift(payload_len, 24), 0xff),
band(rshift(payload_len, 16), 0xff),
band(rshift(payload_len, 8), 0xff),
band(payload_len, 0xff))
end
local masking_key
if masking then
-- set the mask bit
snd = bor(snd, 0x80)
local key = rand(0xffffffff)
masking_key = char(band(rshift(key, 24), 0xff),
band(rshift(key, 16), 0xff),
band(rshift(key, 8), 0xff),
band(key, 0xff))
-- TODO string.buffer optimizations
local bytes = get_string_buf(payload_len)
for i = 1, payload_len do
bytes[i - 1] = bxor(byte(payload, i),
byte(masking_key, (i - 1) % 4 + 1))
end
payload = ffi_string(bytes, payload_len)
else
masking_key = ""
end
return char(fst, snd) .. extra_len_bytes .. masking_key .. payload
end
_M.build_frame = build_frame
function _M.send_frame(sock, fin, opcode, payload, max_payload_len, masking)
-- ngx.log(ngx.WARN, ngx.var.uri, ": masking: ", masking)
if not payload then
payload = ""
elseif type(payload) ~= "string" then
payload = tostring(payload)
end
local payload_len = #payload
if payload_len > max_payload_len then
return nil, "payload too big"
end
if band(opcode, 0x8) ~= 0 then
-- being a control frame
if payload_len > 125 then
return nil, "too much payload for control frame"
end
if not fin then
return nil, "fragmented control frame"
end
end
local frame, err = build_frame(fin, opcode, payload_len, payload,
masking)
if not frame then
return nil, "failed to build frame: " .. err
end
local bytes, err = sock:send(frame)
if not bytes then
return nil, "failed to send frame: " .. err
end
return bytes
end
return _M
|