qws
Version:
An HTML5 Web Sockets Server Module
228 lines (192 loc) • 5 kB
text/coffeescript
zlib = require 'zlib'
crypto = require 'crypto'
# mask & unmask data
unmask = (data, mask) ->
d1 = new Buffer data.length
for i in [0...data.length] by 4
d1[i+3] = data[i+3] ^ mask[3]
d1[i+2] = data[i+2] ^ mask[2]
d1[i+1] = data[i+1] ^ mask[1]
d1[i] = data[i] ^ mask[0]
mod = data.length % 4
d1[i] = data[i] ^ mask[0] if mod > 0
d1[i+1] = data[i+1] ^ mask[0] if mod > 1
d1[i+2] = data[i+2] ^ mask[0] if mod > 2
d1
# inflate frame
inflate = (frame, stream, cb) ->
return cb null, frame unless frame.rsv1
stream.once 'error', (err) ->
cb err
stream.once 'data', (data) ->
frame.data = data
cb null, frame
len = frame.data.length
b = new Buffer len + 4
b[len] = 0x00
b[len+1] = 0x00
b[len+2] = 0xff
b[len+3] = 0xff
frame.data.copy b
stream.write b
return
# zlib.inflateRaw frame.data, (err, data) ->
# return cb err if err
# frame.data = data
# cb null, frame
# opcodes defines
opcodes = [
'continue', 'text', 'binary'
'non-control 3', 'non-control 4', 'non-control 5', 'non-control 6',' non-control 7'
'close', 'ping', 'pong'
'control B', 'control C', 'control D', 'control E',' control F'
]
# unpack frame
unpack = (buf, frame) ->
# 2+ chunks
if frame
if buf.length > frame.left
frame.data.push buf.slice 0, frame.left
buf = buf.slice frame.left
frame.left = 0
else
frame.data.push buf
frame.left -= buf.length
buf = null
# first chunk
else
b1 = buf[0]
b2 = buf[1]
frame =
# first byte
fin : 0 < (b1 & 0x80)
rsv1 : 0 < (b1 & 0x40)
rsv2 : 0 < (b1 & 0x20)
rsv3 : 0 < (b1 & 0x10)
opcode : opcodes[b1 & 0xf]
# second byte
mask : 0 < (b2 & 128)
length : b2 & 127
data : []
left : 0
done : false
idx = 2
if frame.length > 0
# mid length
if frame.length is 126
frame.length = buf.readUInt16BE(2, true)
idx = 4
# long length
else if frame.length is 127
frame.length = buf.readUInt32BE(2, true) * 0xffffffff + buf.readUInt32BE(6, true)
idx = 10
# maskkey
frame.maskKey = buf.slice idx, idx +=4 if frame.mask
# set data chunk
frame.data.push buf.slice idx, idx += frame.length
# calc left bytes
frame.left = frame.length - frame.data[0].length
# cut buffer
if buf.length > idx
buf = buf.slice idx
else
buf = null
# check done
if frame.left is 0
# not empty frame
if frame.length > 0
# concat to one big buffer
frame.data = Buffer.concat frame.data
# unmask
frame.data = unmask frame.data, frame.maskKey if frame.mask and frame.length > 0
else frame.data = null
frame.done = true
# remove left
delete frame.left
[frame, buf]
pack = (frame, data) ->
# set first byte
b1 = opcodesMap[frame.opcode]
b1 |= 0x10 if frame.rsv3
b1 |= 0x20 if frame.rsv2
b1 |= 0x40 if frame.rsv1
b1 |= 0x80 if frame.fin
dlen = if data? then data.length else 0
# length flag
if dlen > 0xffff
lenFix = 10
plen = 127
else if dlen >= 126
lenFix = 4
plen = 126
else
lenFix = 2
plen = dlen
# no mask if no data
if dlen is 0
frame.mask = false
# second byte
b2 = plen
# use mask
if frame.mask
b2 |= 0x80
buf = new Buffer lenFix + 4 + dlen
frame.maskKey = crypto.randomBytes(4)
frame.maskKey.copy buf, lenFix
lenFix += 4
data = unmask(data, frame.maskKey)
else
buf = new Buffer lenFix + dlen
buf[0] = b1
buf[1] = b2
# length extends
if dlen > 0xffff
buf.writeUInt32BE Math.floor(dlen / 0xffffffff), 2, true
buf.writeUInt32BE dlen % 0xffffffff, 6, true
else if dlen >= 126
buf.writeUInt16BE dlen, 2, true
if dlen > 0
# set data
data.copy buf, lenFix
buf
opcodesMap =
'continue' : 0, 'text' : 1
'binary' : 2, 'close' : 8
'ping' : 9, 'pong' : 10
class Frame
constructor : (prop) ->
@minDeflateLength = 32
@opcode = 'text'
@fin = false
@rsv1 = false
@rsv2 = false
@rsv3 = false
@mask = false
@data = null
(@[k] = prop[k] if prop) for k of prop
pack : (deflate, cb) ->
return cb new Error "Opcode Not Found \"#{@opcode}\"" unless opcodesMap[@opcode]?
# data to buffer
unless @data?
data = null
else if @data instanceof Buffer
data = @data
else
data = new Buffer if typeof @data is 'string' then @data else @data.toString()
# check length for deflate
if deflate && data.length < @minDeflateLength
deflate = false
# if use deflate
if deflate
@rsv1 = true
zlib.deflateRaw data, (err, data) =>
# return cb err if err
cb null, pack @, data
return
else
@rsv1 = false
cb null, pack @, data
return
exports.unpack = unpack
exports.inflate = inflate
exports.Frame = Frame