Skip to content

replace struct pack/unpack with bitops #79

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Sep 13, 2015
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
replace struct pack/unpack with bitops
  • Loading branch information
lipp committed Aug 31, 2015
commit 4944f46026ac280a021b0a92d1761c192eaeb472
33 changes: 33 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
FROM ubuntu:14.04
# install autobahn tests suite (python)
RUN apt-get update -y && apt-get install build-essential libffi-dev libssl-dev python-pip -y
RUN apt-get install python-dev -y
RUN pip -V
RUN pip install autobahntestsuite
# install lua
ENV LUAROCKS_VERSION=2.0.13
ENV LUAROCKS_BASE=luarocks-$LUAROCKS_VERSION
ENV LUA luajit
ENV LUA_DEV libluajit-5.1-dev
ENV LUA_VER 5.1
ENV LUA_SFX jit
ENV LUA_INCDIR /usr/include/luajit-2.0

# - LUA=lua5.1 LUA_DEV=liblua5.1-dev LUA_VER=5.1 LUA_SFX=5.1 LUA_INCDIR=/usr/include/lua5.1
# - LUA=lua5.2 LUA_DEV=liblua5.2-dev LUA_VER=5.2 LUA_SFX=5.2 LUA_INCDIR=/usr/include/lua5.2
# - LUA=luajit LUA_DEV=libluajit-5.1-dev LUA_VER=5.1 LUA_SFX=jit LUA_INCDIR=/usr/include/luajit-2.0
RUN apt-get install ${LUA} ${LUA_DEV} wget libev-dev git-core unzip -y
RUN lua${LUA_SFX} -v
RUN wget --quiet https://github.com/keplerproject/luarocks/archive/v$LUAROCKS_VERSION.tar.gz -O $LUAROCKS_BASE.tar.gz
RUN tar zxpf $LUAROCKS_BASE.tar.gz
RUN cd $LUAROCKS_BASE && ./configure --lua-version=$LUA_VER --lua-suffix=$LUA_SFX --with-lua-include="$LUA_INCDIR" && make install && cd ..
RUN luarocks --version
RUN git clone http://github.com/brimworks/lua-ev && cd lua-ev && luarocks make LIBEV_LIBDIR=/usr/lib/x86_64-linux-gnu/ rockspec/lua-ev-scm-1.rockspec && cd ..
RUN luarocks install LuaCov
RUN luarocks install lua_cliargs 2.3-3
RUN luarocks install busted 1.10.0-1
ADD . /lua-websockets
WORKDIR /lua-websockets
RUN luarocks make rockspecs/lua-websockets-scm-1.rockspec
RUN ./test.sh

2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,7 @@ var echoWs = new WebSocket('ws://127.0.0.1:8002','echo');
The client and server modules depend on:

- luasocket
- struct
- luabitop (if not using Lua 5.2 nor luajit)
- luacrypto (optionally)
- copas (optionally)
- lua-ev (optionally)

Expand Down
3 changes: 1 addition & 2 deletions lua-websockets.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@ description = {
summary = "Websockets for Lua",
homepage = "http://github.com/lipp/lua-websockets",
license = "MIT/X11",
detailed = "Provides sync and async clients and servers for copas."
detailed = "Provides sync and async clients and servers for copas and lua-ev."
}

dependencies = {
"lua >= 5.1",
"struct",
"luasocket",
"luabitop",
"copas"
Expand Down
1 change: 0 additions & 1 deletion rockspecs/lua-websockets-scm-1.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ description = {

dependencies = {
"lua >= 5.1",
"struct",
"luasocket",
"luabitop",
"lua-ev",
Expand Down
52 changes: 32 additions & 20 deletions src/websocket/frame.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
-- Following Websocket RFC: http://tools.ietf.org/html/rfc6455
local struct = require'struct'
local bit = require'websocket.bit'
local band = bit.band
local bxor = bit.bxor
Expand All @@ -12,11 +11,16 @@ local schar = string.char
local tinsert = table.insert
local tconcat = table.concat
local mmin = math.min
local strpack = struct.pack
local strunpack = struct.unpack
local mfloor = math.floor
local mrandom = math.random
local unpack = unpack or table.unpack
local tools = require'websocket.tools'
local write_int8 = tools.write_int8
local write_int16 = tools.write_int16
local write_int32 = tools.write_int32
local read_int8 = tools.read_int8
local read_int16 = tools.read_int16
local read_int32 = tools.read_int32

local bits = function(...)
local n = 0
Expand All @@ -30,6 +34,7 @@ local bit_7 = bits(7)
local bit_0_3 = bits(0,1,2,3)
local bit_0_6 = bits(0,1,2,3,4,5,6)

-- TODO: improve performance
local xor_mask = function(encoded,mask,payload)
local transformed,transformed_arr = {},{}
-- xor chunk-wise to prevent stack overflow.
Expand All @@ -49,7 +54,6 @@ local xor_mask = function(encoded,mask,payload)
end

local encode = function(data,opcode,masked,fin)
local encoded
local header = opcode or 1-- TEXT is default opcode
if fin == nil or fin == true then
header = bor(header,bit_7)
Expand All @@ -59,41 +63,44 @@ local encode = function(data,opcode,masked,fin)
payload = bor(payload,bit_7)
end
local len = #data
local chunks = {}
if len < 126 then
payload = bor(payload,len)
encoded = strpack('bb',header,payload)
tinsert(chunks,write_int8(header,payload))
elseif len <= 0xffff then
payload = bor(payload,126)
encoded = strpack('bb>H',header,payload,len)
tinsert(chunks,write_int8(header,payload))
tinsert(chunks,write_int16(len))
elseif len < 2^53 then
local high = mfloor(len/2^32)
local low = len - high*2^32
payload = bor(payload,127)
encoded = strpack('bb>I>I',header,payload,high,low)
tinsert(chunks,write_int8(header,payload))
tinsert(chunks,write_int32(high))
tinsert(chunks,write_int32(low))
end
if not masked then
encoded = encoded..data
tinsert(chunks,data)
else
local m1 = mrandom(0,0xff)
local m2 = mrandom(0,0xff)
local m3 = mrandom(0,0xff)
local m4 = mrandom(0,0xff)
local mask = {m1,m2,m3,m4}
encoded = tconcat({
encoded,
strpack('BBBB',m1,m2,m3,m4),
xor_mask(data,mask,#data)
})
tinsert(chunks,write_int8(m1,m2,m3,m4))
tinsert(chunks,xor_mask(data,mask,#data))
end
return encoded
return tconcat(chunks)
end

local decode = function(encoded)
local encoded_bak = encoded
if #encoded < 2 then
return nil,2-#encoded
end
local header,payload,pos = strunpack('bb',encoded)
local pos,header,payload
pos,header = read_int8(encoded,1)
pos,payload = read_int8(encoded,pos)
local high,low
encoded = ssub(encoded,pos)
local bytes = 2
Expand All @@ -106,12 +113,13 @@ local decode = function(encoded)
if #encoded < 2 then
return nil,2-#encoded
end
payload,pos = strunpack('>H',encoded)
pos,payload = read_int16(encoded,1)
elseif payload == 127 then
if #encoded < 8 then
return nil,8-#encoded
end
high,low,pos = strunpack('>I>I',encoded)
pos,high = read_int32(encoded,1)
pos,low = read_int32(encoded,pos)
payload = high*2^32 + low
if payload < 0xffff or payload > 2^53 then
assert(false,'INVALID PAYLOAD '..payload)
Expand All @@ -128,7 +136,11 @@ local decode = function(encoded)
if bytes_short > 0 then
return nil,bytes_short
end
local m1,m2,m3,m4,pos = strunpack('BBBB',encoded)
local m1,m2,m3,m4
pos,m1 = read_int8(encoded,1)
pos,m2 = read_int8(encoded,pos)
pos,m3 = read_int8(encoded,pos)
pos,m4 = read_int8(encoded,pos)
encoded = ssub(encoded,pos)
local mask = {
m1,m2,m3,m4
Expand All @@ -152,7 +164,7 @@ end

local encode_close = function(code,reason)
if code then
local data = strpack('>H',code)
local data = write_int16(code)
if reason then
data = data..tostring(reason)
end
Expand All @@ -165,7 +177,7 @@ local decode_close = function(data)
local _,code,reason
if data then
if #data > 1 then
code = strunpack('>H',data)
_,code = read_int16(data,1)
end
if #data > 2 then
reason = data:sub(3)
Expand Down
87 changes: 68 additions & 19 deletions src/websocket/tools.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
local struct = require'struct'
local bit = require'websocket.bit'
local rol = bit.rol
local bxor = bit.bxor
Expand All @@ -15,6 +14,46 @@ local tinsert = table.insert
local tconcat = table.concat
local mrandom = math.random

local read_n_bytes = function(str, pos, n)
pos = pos or 1
return pos+n, string.byte(str, pos, pos + n - 1)
end

local read_int8 = function(str, pos)
return read_n_bytes(str, pos, 1)
end

local read_int16 = function(str, pos)
local new_pos,a,b = read_n_bytes(str, pos, 2)
return new_pos, lshift(a, 8) + b
end

local read_int32 = function(str, pos)
local new_pos,a,b,c,d = read_n_bytes(str, pos, 4)
return new_pos,
lshift(a, 24) +
lshift(b, 16) +
lshift(c, 8 ) +
d
end

local pack_bytes = string.char

local write_int8 = pack_bytes

local write_int16 = function(v)
return pack_bytes(rshift(v, 8), band(v, 0xFF))
end

local write_int32 = function(v)
return pack_bytes(
band(rshift(v, 24), 0xFF),
band(rshift(v, 16), 0xFF),
band(rshift(v, 8), 0xFF),
band(v, 0xFF)
)
end

-- used for generate key random ops
math.randomseed(os.time())

Expand All @@ -34,33 +73,37 @@ local sha1_wiki = function(msg)
local h2 = 0x98BADCFE
local h3 = 0x10325476
local h4 = 0xC3D2E1F0

local bits = #msg * 8
-- append b10000000
msg = msg..schar(0x80)

-- 64 bit length will be appended
local bytes = #msg + 8

-- 512 bit append stuff
local fill_bytes = 64 - (bytes % 64)
if fill_bytes ~= 64 then
msg = msg..srep(schar(0),fill_bytes)
end

-- append 64 big endian length
local high = math.floor(bits/2^32)
local low = bits - high*2^32
msg = msg..struct.pack('>I>I',high,low)
msg = msg..write_int32(high)..write_int32(low)

assert(#msg % 64 == 0,#msg % 64)

for j=1,#msg,64 do
local chunk = msg:sub(j,j+63)
assert(#chunk==64,#chunk)
local words = {struct.unpack(srep('>I',16),chunk)}
-- last item contains the index in chunk where it stopped reading
tremove(words,17)
local words = {}
local next = 1
local word
repeat
next,word = read_int32(chunk, next)
tinsert(words, word)
until next > 64
assert(#words==16)
for i=17,80 do
words[i] = bxor(words[i-3],words[i-8],words[i-14],words[i-16])
Expand All @@ -71,7 +114,7 @@ local sha1_wiki = function(msg)
local c = h2
local d = h3
local e = h4

for i=1,80 do
local k,f
if i > 0 and i < 21 then
Expand All @@ -87,31 +130,31 @@ local sha1_wiki = function(msg)
f = bxor(b,c,d)
k = 0xCA62C1D6
end

local temp = rol(a,5) + f + e + k + words[i]
e = d
d = c
c = rol(b,30)
b = a
a = temp
end

h0 = h0 + a
h1 = h1 + b
h2 = h2 + c
h3 = h3 + d
h4 = h4 + e

end

-- necessary on sizeof(int) == 32 machines
h0 = band(h0,0xffffffff)
h1 = band(h1,0xffffffff)
h2 = band(h2,0xffffffff)
h3 = band(h3,0xffffffff)
h4 = band(h4,0xffffffff)
return struct.pack('>i>i>i>i>i',h0,h1,h2,h3,h4)

return write_int32(h0)..write_int32(h1)..write_int32(h2)..write_int32(h3)..write_int32(h4)
end

local base64_encode = function(data)
Expand Down Expand Up @@ -139,7 +182,7 @@ local generate_key = function()
local r2 = mrandom(0,0xfffffff)
local r3 = mrandom(0,0xfffffff)
local r4 = mrandom(0,0xfffffff)
local key = struct.pack('IIII',r1,r2,r3,r4)
local key = write_int32(r1)..write_int32(r2)..write_int32(r3)..write_int32(r4)
assert(#key==16,#key)
return base64_encode(key)
end
Expand All @@ -151,4 +194,10 @@ return {
},
parse_url = parse_url,
generate_key = generate_key,
read_int8 = read_int8,
read_int16 = read_int16,
read_int32 = read_int32,
write_int8 = write_int8,
write_int16 = write_int16,
write_int32 = write_int32,
}