├── .DS_Store
├── Doc
└── graph.png
├── .gitignore
├── DockerResources
├── .DS_Store
├── build.sh
├── lua
│ ├── lib
│ │ ├── ws
│ │ │ ├── derivedfrom.txt
│ │ │ ├── websocketServer.lua
│ │ │ └── websocketProtocol.lua
│ │ ├── disque
│ │ │ └── disque.lua
│ │ └── sha1
│ │ │ └── sha1.lua
│ └── sample_disque_client.lua
└── nginx.conf
├── README.md
├── centos.dockerfile
├── ubuntu.dockerfile
└── client.html
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sassembla/nginx-luajit-ws/HEAD/.DS_Store
--------------------------------------------------------------------------------
/Doc/graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sassembla/nginx-luajit-ws/HEAD/Doc/graph.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | logs/.DS_Store
2 | logs/error.log
3 | logs/host.access.log
4 | logs/nginx.pid
5 |
--------------------------------------------------------------------------------
/DockerResources/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sassembla/nginx-luajit-ws/HEAD/DockerResources/.DS_Store
--------------------------------------------------------------------------------
/DockerResources/build.sh:
--------------------------------------------------------------------------------
1 | PROJECT_PATH=$(pwd)
2 |
3 | NGINX_VERSION=1.11.9
4 | NGX_DEVEL_KIT="dependencies/ngx_devel_kit-0.3.0"
5 | LUA_NGX_MOD="dependencies/lua-nginx-module-0.10.7"
6 |
7 | export LUAJIT_LIB=/usr/local/lib
8 | export LUAJIT_INC=/usr/local/include/luajit-2.1
9 |
10 | # make & install nginx to PROJECT_PATH/NGINX_VERSION
11 | ./configure \
12 | --with-ld-opt="-Wl,-rpath,$LUAJIT_LIB" \
13 | --prefix=$PROJECT_PATH/$NGINX_VERSION \
14 | --add-module=$NGX_DEVEL_KIT \
15 | --add-module=$LUA_NGX_MOD
16 |
17 | make -j2
18 | make install
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # nginx-luajit-ws
2 |
3 | 
4 |
5 | nginxでluaを使ってWebSocketを受け付ける。
6 |
7 |
8 | 1. nginx上のluaでClient-ServerをWebSocket接続
9 | 2. すべての接続がmessageQueueを介して一箇所のcontextに収束
10 | 3. contextはmessageQueueにアクセスできさえすれば要件を満たせる。どんな言語でも環境でも書けるはず
11 | 4. contextとWebSocket接続が疎結合なので、接続保ったままcontextの更新が可能(単に別なだけ)
12 |
13 |
14 | # Build image
15 |
16 | ```shellscript
17 | docker build -f ubuntu.dockerfile -t nginx-luajit-ubuntu .
18 | ```
19 |
20 | # Create container from image
21 |
22 | ```shellscript
23 | docker run -ti -d --name nginx_luajit -p 8080:80 -v $(pwd)/logs:/nginx-1.11.9/1.11.9/logs nginx-luajit-ubuntu
24 | ```
25 |
26 | # Connect to connnection server
27 |
28 | open client.html by web browser.
29 |
30 |
31 | # Logs
32 |
33 | all nginx logs are located in ./logs folder.
--------------------------------------------------------------------------------
/DockerResources/lua/lib/ws/derivedfrom.txt:
--------------------------------------------------------------------------------
1 | https://github.com/openresty/lua-resty-websocket
2 |
3 | This module is licensed under the BSD license.
4 |
5 | Copyright (C) 2013-2017, by Yichun Zhang (agentzh) agentzh@gmail.com, OpenResty Inc.
6 |
7 | All rights reserved.
8 |
9 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
10 |
11 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
12 |
13 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16 |
17 |
18 | added: continue frame feature.
--------------------------------------------------------------------------------
/DockerResources/nginx.conf:
--------------------------------------------------------------------------------
1 | #user nobody;
2 | worker_processes auto;
3 |
4 | error_log logs/error.log;
5 | # error_log logs/error.log notice;
6 | # error_log logs/error.log info;
7 |
8 | pid logs/nginx.pid;
9 |
10 | worker_rlimit_nofile 2048;
11 |
12 | events {
13 | worker_connections 1024;
14 | }
15 |
16 |
17 | http {
18 | include mime.types;
19 | default_type application/octet-stream;
20 |
21 | #log_format main '$remote_addr - $remote_user [$time_local] "$request" '
22 | # '$status $body_bytes_sent "$http_referer" '
23 | # '"$http_user_agent" "$http_x_forwarded_for"';
24 |
25 | #access_log logs/access.log main;
26 |
27 | sendfile on;
28 | #tcp_nopush on;
29 |
30 | #keepalive_timeout 0;
31 | keepalive_timeout 65;
32 |
33 | #gzip on;
34 |
35 | # lua_code_cache off;
36 |
37 | # set search paths for pure Lua external libraries (';;' is the default path):
38 | lua_package_path ";;$prefix/lua/lib/?.lua;";
39 |
40 | # set search paths for Lua external libraries written in C (can also use ';;'):
41 | # lua_package_cpath ';;$prefix/lua/shared/?.so;';
42 |
43 | server {
44 | listen 80;
45 | server_name localhost;
46 |
47 | access_log logs/host.access.log;
48 |
49 | location / {
50 | root html;
51 | index index.html index.htm;
52 | }
53 |
54 | # sample disque client route.
55 | location /sample_disque_client {
56 | content_by_lua_file lua/sample_disque_client.lua;
57 | }
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/centos.dockerfile:
--------------------------------------------------------------------------------
1 | FROM centos
2 |
3 | ENV NGINX_VERSION 1.11.9
4 | ENV LUAJIT_VERSION 2.1.0-beta2
5 | ENV NGINX_DEVEL_KIT_VERSION v0.3.0
6 | ENV NGINX_LUAJIT_VERSION v0.10.7
7 |
8 | # ready tools.
9 | RUN yum -y install \
10 | gcc \
11 | gcc-c++ \
12 | make \
13 | zlib-devel \
14 | pcre-devel \
15 | openssl-devel \
16 | wget \
17 | unzip
18 |
19 | # download nginx.
20 | RUN wget http://nginx.org/download/nginx-$NGINX_VERSION.tar.gz && tar -xzvf nginx-$NGINX_VERSION.tar.gz && rm nginx-$NGINX_VERSION.tar.gz
21 |
22 | # add luajit module.
23 | RUN wget http://luajit.org/download/LuaJIT-$LUAJIT_VERSION.tar.gz && tar -xzvf LuaJIT-$LUAJIT_VERSION.tar.gz && rm LuaJIT-$LUAJIT_VERSION.tar.gz && cd LuaJIT-$LUAJIT_VERSION/ && make && make install
24 |
25 | # add nginx tools and lua module.
26 | RUN mkdir nginx-$NGINX_VERSION/dependencies && cd nginx-$NGINX_VERSION/dependencies && \
27 | wget https://github.com/simpl/ngx_devel_kit/archive/$NGINX_DEVEL_KIT_VERSION.zip && unzip $NGINX_DEVEL_KIT_VERSION.zip && rm $NGINX_DEVEL_KIT_VERSION.zip && \
28 | wget https://github.com/openresty/lua-nginx-module/archive/$NGINX_LUAJIT_VERSION.zip && unzip $NGINX_LUAJIT_VERSION.zip && rm $NGINX_LUAJIT_VERSION.zip
29 |
30 | # add shell.
31 | COPY ./DockerResources/build.sh nginx-$NGINX_VERSION/build.sh
32 |
33 | # build nginx.
34 | RUN cd nginx-$NGINX_VERSION && sh build.sh
35 |
36 | # download and make disque.
37 | RUN wget https://github.com/antirez/disque/archive/master.zip && unzip master.zip && rm master.zip && ls -l && cd disque-master/src && make
38 |
39 |
40 | # add lua sources.
41 | RUN mkdir nginx-$NGINX_VERSION/$NGINX_VERSION/lua && ls -l
42 | COPY ./DockerResources/lua nginx-$NGINX_VERSION/$NGINX_VERSION/lua
43 |
44 | # overwrite nginx conf.
45 | COPY ./DockerResources/nginx.conf nginx-$NGINX_VERSION/$NGINX_VERSION/conf/
46 |
47 |
48 | # run nginx & disque-server.
49 | ENTRYPOINT /nginx-$NGINX_VERSION/$NGINX_VERSION/sbin/nginx && /disque-master/src/disque-server
50 |
--------------------------------------------------------------------------------
/ubuntu.dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu
2 |
3 | ENV NGINX_VERSION 1.11.9
4 | ENV LUAJIT_VERSION 2.1.0-beta2
5 | ENV NGINX_DEVEL_KIT_VERSION v0.3.0
6 | ENV NGINX_LUAJIT_VERSION v0.10.7
7 |
8 | # ready tools.
9 | RUN apt-get update && apt-get install -y \
10 | gcc \
11 | g++ \
12 | make \
13 | zlib1g-dev \
14 | libpcre3-dev \
15 | libssl-dev\
16 | wget \
17 | unzip
18 |
19 | # download nginx.
20 | RUN wget http://nginx.org/download/nginx-$NGINX_VERSION.tar.gz && tar -xzvf nginx-$NGINX_VERSION.tar.gz && rm nginx-$NGINX_VERSION.tar.gz
21 |
22 | # add luajit module.
23 | RUN wget http://luajit.org/download/LuaJIT-$LUAJIT_VERSION.tar.gz && tar -xzvf LuaJIT-$LUAJIT_VERSION.tar.gz && rm LuaJIT-$LUAJIT_VERSION.tar.gz && cd LuaJIT-$LUAJIT_VERSION/ && make && make install
24 |
25 | # add nginx tools and lua module.
26 | RUN mkdir nginx-$NGINX_VERSION/dependencies && cd nginx-$NGINX_VERSION/dependencies && \
27 | wget https://github.com/simpl/ngx_devel_kit/archive/$NGINX_DEVEL_KIT_VERSION.zip && unzip $NGINX_DEVEL_KIT_VERSION.zip && rm $NGINX_DEVEL_KIT_VERSION.zip && \
28 | wget https://github.com/openresty/lua-nginx-module/archive/$NGINX_LUAJIT_VERSION.zip && unzip $NGINX_LUAJIT_VERSION.zip && rm $NGINX_LUAJIT_VERSION.zip
29 |
30 | # add shell.
31 | COPY ./DockerResources/build.sh nginx-$NGINX_VERSION/build.sh
32 |
33 | # build nginx.
34 | RUN cd nginx-$NGINX_VERSION && sh build.sh
35 |
36 | # download and make disque.
37 | RUN wget https://github.com/antirez/disque/archive/master.zip && unzip master.zip && rm master.zip && ls -l && cd disque-master/src && make
38 |
39 |
40 | # add lua sources.
41 | RUN mkdir nginx-$NGINX_VERSION/$NGINX_VERSION/lua && ls -l
42 | COPY ./DockerResources/lua nginx-$NGINX_VERSION/$NGINX_VERSION/lua
43 |
44 | # overwrite nginx conf.
45 | COPY ./DockerResources/nginx.conf nginx-$NGINX_VERSION/$NGINX_VERSION/conf/
46 |
47 |
48 | # run nginx & disque-server.
49 | ENTRYPOINT /nginx-$NGINX_VERSION/$NGINX_VERSION/sbin/nginx && /disque-master/src/disque-server
50 |
--------------------------------------------------------------------------------
/client.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/DockerResources/lua/lib/ws/websocketServer.lua:
--------------------------------------------------------------------------------
1 | -- Copyright (C) Yichun Zhang (agentzh)
2 |
3 | local sha1 = require 'sha1.sha1'
4 | local bit = require "bit"
5 | local protocol = require "ws.websocketProtocol"
6 |
7 | local M = {[0] = "websocketServer"}
8 | local mt = { __index = M }
9 |
10 | local connecting = false
11 |
12 | function M.new(self, opts)
13 | if ngx.headers_sent then
14 | return nil, "response header already sent"
15 | end
16 |
17 | -- construct WebSocket connect from server to client.
18 |
19 | -- discard body
20 | ngx.req.read_body()
21 |
22 | -- check header
23 | if ngx.req.http_version() ~= 1.1 then
24 | return nil, "bad http version"
25 | end
26 |
27 | local headers = ngx.req.get_headers()
28 |
29 | local val = headers.upgrade
30 | if type(val) == "table" then
31 | val = val[1]
32 | end
33 | if not val or string.lower(val) ~= "websocket" then
34 | return nil, "bad \"upgrade\" request header"
35 | end
36 |
37 | val = headers.connection
38 | if type(val) == "table" then
39 | val = val[1]
40 | end
41 | if not val or not string.find(string.lower(val), "upgrade", 1, true) then
42 | return nil, "bad \"connection\" request header"
43 | end
44 |
45 | local key = headers["sec-websocket-key"]
46 | if type(key) == "table" then
47 | key = key[1]
48 | end
49 | if not key then
50 | return nil, "bad \"sec-websocket-key\" request header"
51 | end
52 |
53 | local ver = headers["sec-websocket-version"]
54 | if type(ver) == "table" then
55 | ver = ver[1]
56 | end
57 | if not ver or ver ~= "13" then
58 | return nil, "bad \"sec-websocket-version\" request header"
59 | end
60 |
61 | local protocols = headers["sec-websocket-protocol"]
62 | if type(protocols) == "table" then
63 | protocols = protocols[1]
64 | end
65 |
66 | if protocols then
67 | ngx.header["Sec-WebSocket-Protocol"] = protocols
68 | end
69 | ngx.header["Upgrade"] = "websocket"
70 |
71 | local sha1 = sha1.binary(key .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
72 | ngx.header["Sec-WebSocket-Accept"] = ngx.encode_base64(sha1)
73 |
74 | ngx.header["Content-Type"] = nil
75 |
76 | ngx.status = 101
77 | local ok, err = ngx.send_headers()
78 | if not ok then
79 | return nil, "failed to send response header: " .. (err or "unknonw")
80 | end
81 | ok, err = ngx.flush(true)
82 | if not ok then
83 | return nil, "failed to flush response header: " .. (err or "unknown")
84 | end
85 |
86 | local sock
87 | sock, err = ngx.req.socket(true)
88 | if not sock then
89 | return nil, err
90 | end
91 |
92 | local max_payload_len, send_masked, timeout
93 | if opts then
94 | max_payload_len = opts.max_payload_len
95 | send_masked = opts.send_masked
96 | timeout = opts.timeout
97 |
98 | if timeout then
99 | sock:settimeout(timeout)
100 | end
101 | end
102 |
103 | connecting = true
104 |
105 | return setmetatable({
106 | sock = sock,
107 | max_payload_len = max_payload_len or 65535,
108 | send_masked = send_masked,
109 | }, mt)
110 | end
111 |
112 |
113 | function M.set_timeout(self, time)
114 | local sock = self.sock
115 | if not sock then
116 | return nil, nil, "not initialized yet"
117 | end
118 |
119 | return sock:settimeout(time)
120 | end
121 |
122 |
123 | function M.recv_frame(self)
124 | if self.fatal then
125 | return nil, nil, "fatal error already happened"
126 | end
127 |
128 | local sock = self.sock
129 | if not sock then
130 | return nil, nil, "not initialized yet"
131 | end
132 |
133 | local data, typ, err = protocol.recv_frame(sock, self.max_payload_len, true)
134 | if not data and not string.find(err, ": timeout", 1, true) then
135 | self.fatal = true
136 | end
137 | return data, typ, err
138 | end
139 |
140 |
141 | local function send_frame(self, fin, opcode, payload)
142 | if self.fatal then
143 | return nil, "fatal error already happened"
144 | end
145 |
146 | local sock = self.sock
147 | if not sock then
148 | return nil, "not initialized yet"
149 | end
150 |
151 | local bytes, err = protocol.send_frame(sock, fin, opcode, payload,
152 | self.max_payload_len, self.send_masked)
153 | if not bytes then
154 | self.fatal = true
155 | end
156 | return bytes, err
157 | end
158 |
159 |
160 | M.send_frame = send_frame
161 |
162 |
163 | function M.send_continue(self, data)
164 | return send_frame(self, true, 0x0, data)
165 | end
166 |
167 |
168 | function M.send_text(self, data)
169 | return send_frame(self, true, 0x1, data)
170 | end
171 |
172 |
173 | function M.send_binary(self, data)
174 | return send_frame(self, true, 0x2, data)
175 | end
176 |
177 |
178 | function M.send_close(self, code, msg)
179 | local payload
180 | if code then
181 | if type(code) ~= "number" or code > 0x7fff then
182 | end
183 | payload = string.char(bit.band(bit.rshift(code, 8), 0xff), bit.band(code, 0xff))
184 | .. (msg or "")
185 | end
186 |
187 | connecting = false
188 |
189 | return send_frame(self, true, 0x8, payload)
190 | end
191 |
192 |
193 | function M.send_ping(self, data)
194 | return send_frame(self, true, 0x9, data)
195 | end
196 |
197 |
198 | function M.send_pong(self, data)
199 | return send_frame(self, true, 0xa, data)
200 | end
201 |
202 | function M.is_connecting(self)
203 | return connecting
204 | end
205 |
206 |
207 | return M
208 |
--------------------------------------------------------------------------------
/DockerResources/lua/sample_disque_client.lua:
--------------------------------------------------------------------------------
1 | -- get identity of game from url. e.g. http://somewhere/game_key -> game_key
2 | local identity = string.gsub (ngx.var.uri, "/", "")
3 |
4 | -- generate identity of queue for target context.
5 | IDENTIFIER_CONTEXT = identity .. "_context"
6 |
7 |
8 | STATE_CONNECT = 1
9 | STATE_STRING_MESSAGE = 2
10 | STATE_BINARY_MESSAGE = 3
11 | STATE_DISCONNECT_INTENT = 4
12 | STATE_DISCONNECT_ACCIDT = 5
13 | STATE_DISCONNECT_DISQUE_ACKFAILED = 6
14 | STATE_DISCONNECT_DISQUE_ACCIDT_SENDFAILED = 7
15 |
16 | local the_id = ngx.req.get_headers()["id"]
17 | if not the_id then
18 | the_id = "_empty_"
19 | end
20 |
21 |
22 | ip = "127.0.0.1"-- localhost.
23 | port = 7711
24 |
25 |
26 | -- entrypoint for WebSocket client connection.
27 |
28 | -- setup Disque get-add
29 | local disque = require "disque.disque"
30 |
31 | -- connectionId is nginx's request id. that len is 32 + 4.
32 | local connectionId = ngx.var.request_id .. "0000"
33 |
34 | receiveJobConn = disque:new()
35 | local ok, err = receiveJobConn:connect(ip, port)
36 | if not ok then
37 | ngx.log(ngx.ERR, "connection:", connectionId, " failed to generate receiveJob client")
38 | return
39 | end
40 |
41 | receiveJobConn:set_timeout(1000 * 60 * 60)
42 |
43 |
44 | addJobCon = disque:new()
45 | local ok, err = addJobCon:connect(ip, port)
46 | if not ok then
47 | ngx.log(ngx.ERR, "connection:", connectionId, " failed to generate addJob client")
48 | return
49 | end
50 |
51 | local maxLen = 1024
52 |
53 | -- setup websocket client
54 | local wsServer = require "ws.websocketServer"
55 |
56 | ws, wErr = wsServer:new{
57 | timeout = 10000000,-- this should be set good value.
58 | max_payload_len = maxLen
59 | }
60 |
61 | if not ws then
62 | ngx.log(ngx.ERR, "connection:", connectionId, " failed to new websocket: ", wErr)
63 | return
64 | end
65 |
66 | ngx.log(ngx.ERR, "connection:", connectionId, " start connect.")
67 |
68 | function connectWebSocket()
69 | -- start receiving message from context.
70 | ngx.thread.spawn(contextReceiving)
71 |
72 | ngx.log(ngx.ERR, "connection:", connectionId, " established. the_id:", the_id, " to context:", IDENTIFIER_CONTEXT)
73 |
74 | -- send connected to gameContext.
75 | local data = STATE_CONNECT..connectionId..the_id
76 | addJobCon:addjob(IDENTIFIER_CONTEXT, data, 0)
77 |
78 | -- start websocket serving.
79 | while true do
80 | local recv_data, typ, err = ws:recv_frame()
81 |
82 | if ws.fatal then
83 | ngx.log(ngx.ERR, "connection:", connectionId, " closing accidentially. ", err)
84 | local data = STATE_DISCONNECT_ACCIDT..connectionId..the_id
85 | addJobCon:addjob(IDENTIFIER_CONTEXT, data, 0)
86 | break
87 | end
88 |
89 | if not recv_data then
90 | ngx.log(ngx.ERR, "connection:", connectionId, " received empty data.")
91 | -- log only. do nothing.
92 | end
93 |
94 | if typ == "close" then
95 | ngx.log(ngx.ERR, "connection:", connectionId, " closing intentionally.")
96 | local data = STATE_DISCONNECT_INTENT..connectionId..the_id
97 | addJobCon:addjob(IDENTIFIER_CONTEXT, data, 0)
98 |
99 | -- start close.
100 | break
101 | elseif typ == "ping" then
102 | local bytes, err = ws:send_pong(recv_data)
103 | -- ngx.log(ngx.ERR, "connection:", serverId, " ping received.")
104 | if not bytes then
105 | ngx.log(ngx.ERR, "connection:", serverId, " failed to send pong: ", err)
106 | break
107 | end
108 |
109 | elseif typ == "pong" then
110 | ngx.log(ngx.INFO, "client ponged")
111 |
112 | elseif typ == "text" then
113 | -- post message to central.
114 | local data = STATE_STRING_MESSAGE..connectionId..recv_data
115 | addJobCon:addjob(IDENTIFIER_CONTEXT, data, 0)
116 | elseif typ == "binary" then
117 | -- post binary data to central.
118 | local binData = STATE_BINARY_MESSAGE..connectionId..recv_data
119 | addJobCon:addjob(IDENTIFIER_CONTEXT, binData, 0)
120 | end
121 | end
122 |
123 | ws:send_close()
124 | ngx.log(ngx.ERR, "connection:", connectionId, " connection closed")
125 |
126 | ngx.exit(200)
127 | end
128 |
129 | -- loop for receiving messages from game context.
130 | function contextReceiving ()
131 | local localWs = ws
132 | local localMaxLen = maxLen
133 | while true do
134 | -- receive message from disque queue, through connectionId.
135 | -- game context will send message via connectionId.
136 | local res, err = receiveJobConn:getjob("from", connectionId)
137 |
138 | if not res then
139 | ngx.log(ngx.ERR, "err:", err)
140 | break
141 | else
142 | local datas = res[1]
143 | -- ngx.log(ngx.ERR, "client datas1:", datas[1])-- connectionId
144 | -- ngx.log(ngx.ERR, "client datas2:", datas[2])-- messageId
145 | -- ngx.log(ngx.ERR, "client datas3:", datas[3])-- data
146 | local messageId = datas[2]
147 | local sendingData = datas[3]
148 |
149 | -- fastack to disque
150 | local ackRes, ackErr = receiveJobConn:fastack(messageId)
151 | if not ackRes then
152 | ngx.log(ngx.ERR, "disque, ackに失敗したケース connection:", connectionId, " ackErr:", ackErr)
153 | local data = STATE_DISCONNECT_DISQUE_ACKFAILED..connectionId..the_id
154 | addJobCon:addjob(IDENTIFIER_CONTEXT, data, 0)
155 | break
156 | end
157 | -- ngx.log(ngx.ERR, "messageId:", messageId, " ackRes:", ackRes)
158 |
159 | -- というわけで、ここまででデータは取得できているが、ここで先頭を見て、、みたいなのが必要になってくる。
160 | -- 入れる側にもなんかデータ接続が出ちゃうんだなあ。うーん、、まあでもサーバ側なんでいいや。CopyがN回増えるだけだ。
161 | -- 残る課題は、ここでヘッダを見る、ってことだね。
162 |
163 | if (localMaxLen < #sendingData) then
164 | local count = math.floor(#sendingData / localMaxLen)
165 | local rest = #sendingData % localMaxLen
166 |
167 | local index = 1
168 | local failed = false
169 | for i = 1, count do
170 | -- send. from index to index + localMaxLen.
171 | local continueData = string.sub(sendingData, index, index + localMaxLen - 1)
172 |
173 | local bytes, err = localWs:send_continue(continueData)
174 | if not bytes then
175 | ngx.log(ngx.ERR, "disque, continue送付の失敗。 connection:", connectionId, " failed to send text to client. err:", err)
176 | local data = STATE_DISCONNECT_DISQUE_ACCIDT_SENDFAILED..connectionId..sendingData
177 | addJobCon:addjob(IDENTIFIER_CONTEXT, data, 0)
178 | failed = true
179 | break
180 | end
181 | index = index + localMaxLen
182 | end
183 |
184 | if failed then
185 | break
186 | end
187 |
188 | -- send rest data as binary.
189 |
190 | local lastData = string.sub(sendingData, index)
191 |
192 | local bytes, err = localWs:send_binary(lastData)
193 | if not bytes then
194 | ngx.log(ngx.ERR, "disque, continue送付の失敗。 connection:", connectionId, " failed to send text to client. err:", err)
195 | local data = STATE_DISCONNECT_DISQUE_ACCIDT_SENDFAILED..connectionId..sendingData
196 | addJobCon:addjob(IDENTIFIER_CONTEXT, data, 0)
197 | break
198 | end
199 |
200 | else
201 | -- send data to client
202 | local bytes, err = localWs:send_binary(sendingData)
203 |
204 | if not bytes then
205 | ngx.log(ngx.ERR, "disque, 未解決の、送付失敗時にすべきこと。 connection:", connectionId, " failed to send text to client. err:", err)
206 | local data = STATE_DISCONNECT_DISQUE_ACCIDT_SENDFAILED..connectionId..sendingData
207 | addJobCon:addjob(IDENTIFIER_CONTEXT, data, 0)
208 | break
209 | end
210 | end
211 | end
212 | end
213 |
214 | ngx.log(ngx.ERR, "connection:", connectionId, " connection closed by disque error.")
215 | ngx.exit(200)
216 | end
217 |
218 | connectWebSocket()
219 |
220 |
221 | -- 別の話、ここに受け入れバッファを持つことは可能か
222 |
223 | -- -> なんか切断時コンテキスト混同イレギュラーがあったんだよな〜〜あれの原因探さないとなー
224 | -- 何が起きていたかっていうと、切断確認が別のクライアントのものをつかんでいた、っていうやつで、
225 | -- 受け取り時にコネクション状態を見るとおかしくなっている、ていうやつ。
226 | -- 、、、コネクション状態に関して見るフラッグをngx.thread内で扱ってはいけない、みたいなのがありそう。
227 | -- ということは、それ以外であれば混同しないのでは。
228 |
229 | -- それが解消したらできそうかな?できそうだな。
230 | -- パラメータを保持させて、か、、まあ親のインスタンスのパラメータに触れるのはしんどいんで、やっぱりluaだと厳しいねっていう話になるのがいい気がする。
231 | -- 本当にあると嬉しいのは、TCP以外が喋れる、フロントになれるメッセージキューか。まあErlangにはあるんだけどな。
232 |
233 |
234 |
235 |
236 |
237 |
--------------------------------------------------------------------------------
/DockerResources/lua/lib/disque/disque.lua:
--------------------------------------------------------------------------------
1 | -- Disque client for lua.
2 |
3 | local sub = string.sub
4 | local byte = string.byte
5 | local tcp = ngx.socket.tcp
6 | local concat = table.concat
7 | local null = ngx.null
8 | local pairs = pairs
9 | local unpack = unpack
10 | local setmetatable = setmetatable
11 | local tonumber = tonumber
12 | local error = error
13 |
14 |
15 | local ok, new_tab = pcall(require, "table.new")
16 | if not ok or type(new_tab) ~= "function" then
17 | new_tab = function (narr, nrec) return {} end
18 | end
19 |
20 |
21 | local _M = new_tab(0, 155)
22 | _M._VERSION = '0.1'
23 |
24 |
25 | local commands = {
26 | "addjob", "getjob", "fastack"
27 | }
28 |
29 |
30 | local mt = { __index = _M }
31 |
32 |
33 | function _M.new(self)
34 | local sock, err = tcp()
35 | if not sock then
36 | return nil, err
37 | end
38 | return setmetatable({ sock = sock }, mt)
39 | end
40 |
41 |
42 | function _M.set_timeout(self, timeout)
43 | local sock = self.sock
44 | if not sock then
45 | return nil, "not initialized"
46 | end
47 |
48 | return sock:settimeout(timeout)
49 | end
50 |
51 |
52 | function _M.connect(self, ...)
53 | local sock = self.sock
54 | if not sock then
55 | return nil, "not initialized"
56 | end
57 |
58 | self.subscribed = nil
59 |
60 | return sock:connect(...)
61 | end
62 |
63 |
64 | function _M.set_keepalive(self, ...)
65 | local sock = self.sock
66 | if not sock then
67 | return nil, "not initialized"
68 | end
69 |
70 | if self.subscribed then
71 | return nil, "subscribed state"
72 | end
73 |
74 | return sock:setkeepalive(...)
75 | end
76 |
77 |
78 | function _M.get_reused_times(self)
79 | local sock = self.sock
80 | if not sock then
81 | return nil, "not initialized"
82 | end
83 |
84 | return sock:getreusedtimes()
85 | end
86 |
87 |
88 | local function close(self)
89 | local sock = self.sock
90 | if not sock then
91 | return nil, "not initialized"
92 | end
93 |
94 | return sock:close()
95 | end
96 | _M.close = close
97 |
98 |
99 | local function _read_reply(self, sock)
100 | local line, err = sock:receive()
101 | if not line then
102 | if err == "timeout" and not self.subscribed then
103 | sock:close()
104 | end
105 | return nil, err
106 | end
107 |
108 | local prefix = byte(line)
109 |
110 | if prefix == 36 then -- char '$'
111 | -- print("bulk reply")
112 |
113 | local size = tonumber(sub(line, 2))
114 | if size < 0 then
115 | return null
116 | end
117 |
118 | local data, err = sock:receive(size)
119 | if not data then
120 | if err == "timeout" then
121 | sock:close()
122 | end
123 | return nil, err
124 | end
125 |
126 | local dummy, err = sock:receive(2) -- ignore CRLF
127 | if not dummy then
128 | return nil, err
129 | end
130 |
131 | return data
132 |
133 | elseif prefix == 43 then -- char '+'
134 | -- ngx.log(ngx.ERR, "status reply line:", line)
135 |
136 | return sub(line, 2)
137 |
138 | elseif prefix == 42 then -- char '*'
139 | local n = tonumber(sub(line, 2))
140 |
141 | -- ngx.log(ngx.ERR, "multi-bulk reply: ", n)
142 | if n < 0 then
143 | return null
144 | end
145 |
146 | local vals = new_tab(n, 0);
147 | local nvals = 0
148 | for i = 1, n do
149 | local res, err = _read_reply(self, sock)
150 | if res then
151 | nvals = nvals + 1
152 | vals[nvals] = res
153 |
154 | elseif res == nil then
155 | return nil, err
156 |
157 | else
158 | -- be a valid redis error value
159 | nvals = nvals + 1
160 | vals[nvals] = {false, err}
161 | end
162 | end
163 |
164 | return vals
165 |
166 | elseif prefix == 58 then -- char ':'
167 | -- ngx.log(ngx.ERR, "integer reply")
168 | return tonumber(sub(line, 2))
169 |
170 | elseif prefix == 45 then -- char '-'
171 | -- ngx.log(ngx.ERR, "error reply: ", n)
172 |
173 | return false, sub(line, 2)
174 |
175 | else
176 | return nil, "unkown prefix: \"" .. prefix .. "\""
177 | end
178 | end
179 |
180 |
181 | local function _gen_req(args)
182 | local nargs = #args
183 |
184 | local req = new_tab(nargs + 1, 0)
185 | req[1] = "*" .. nargs .. "\r\n"
186 | local nbits = 1
187 |
188 | for i = 1, nargs do
189 | local arg = args[i]
190 | nbits = nbits + 1
191 |
192 | if not arg then
193 | req[nbits] = "$-1\r\n"
194 |
195 | else
196 | if type(arg) ~= "string" then
197 | arg = tostring(arg)
198 | end
199 | req[nbits] = "$" .. #arg .. "\r\n" .. arg .. "\r\n"
200 | end
201 | end
202 |
203 | -- it is faster to do string concatenation on the Lua land
204 | return concat(req)
205 | end
206 |
207 |
208 | local function _do_cmd(self, ...)
209 | local args = {...}
210 |
211 | local sock = self.sock
212 | if not sock then
213 | return nil, "not initialized"
214 | end
215 |
216 | local req = _gen_req(args)
217 |
218 | local reqs = self._reqs
219 | if reqs then
220 | reqs[#reqs + 1] = req
221 | return
222 | end
223 |
224 | -- ngx.log(ngx.ERR, "request:", req)
225 |
226 | local bytes, err = sock:send(req)
227 | if not bytes then
228 | return nil, err
229 | end
230 |
231 | -- ngx.log(ngx.ERR, "result bytes:", bytes, "err:", err)
232 |
233 | return _read_reply(self, sock)
234 | end
235 |
236 |
237 | local function _check_subscribed(self, res)
238 | if type(res) == "table"
239 | and (res[1] == "unsubscribe" or res[1] == "punsubscribe")
240 | and res[3] == 0
241 | then
242 | self.subscribed = nil
243 | end
244 | end
245 |
246 |
247 | function _M.read_reply(self)
248 | local sock = self.sock
249 | if not sock then
250 | return nil, "not initialized"
251 | end
252 |
253 | if not self.subscribed then
254 | return nil, "not subscribed"
255 | end
256 |
257 | local res, err = _read_reply(self, sock)
258 | _check_subscribed(self, res)
259 |
260 | return res, err
261 | end
262 |
263 |
264 | for i = 1, #commands do
265 | local cmd = commands[i]
266 |
267 | _M[cmd] =
268 | function (self, ...)
269 | return _do_cmd(self, cmd, ...)
270 | end
271 | end
272 |
273 |
274 |
275 | function _M.hmset(self, hashname, ...)
276 | local args = {...}
277 | if #args == 1 then
278 | local t = args[1]
279 |
280 | local n = 0
281 | for k, v in pairs(t) do
282 | n = n + 2
283 | end
284 |
285 | local array = new_tab(n, 0)
286 |
287 | local i = 0
288 | for k, v in pairs(t) do
289 | array[i + 1] = k
290 | array[i + 2] = v
291 | i = i + 2
292 | end
293 | -- print("key", hashname)
294 | return _do_cmd(self, "hmset", hashname, unpack(array))
295 | end
296 |
297 | -- backwards compatibility
298 | return _do_cmd(self, "hmset", hashname, ...)
299 | end
300 |
301 |
302 | function _M.init_pipeline(self, n)
303 | self._reqs = new_tab(n or 4, 0)
304 | end
305 |
306 |
307 | function _M.cancel_pipeline(self)
308 | self._reqs = nil
309 | end
310 |
311 |
312 | function _M.commit_pipeline(self)
313 | local reqs = self._reqs
314 | if not reqs then
315 | return nil, "no pipeline"
316 | end
317 |
318 | self._reqs = nil
319 |
320 | local sock = self.sock
321 | if not sock then
322 | return nil, "not initialized"
323 | end
324 |
325 | local bytes, err = sock:send(reqs)
326 | if not bytes then
327 | return nil, err
328 | end
329 |
330 | local nvals = 0
331 | local nreqs = #reqs
332 | local vals = new_tab(nreqs, 0)
333 | for i = 1, nreqs do
334 | local res, err = _read_reply(self, sock)
335 | if res then
336 | nvals = nvals + 1
337 | vals[nvals] = res
338 |
339 | elseif res == nil then
340 | if err == "timeout" then
341 | close(self)
342 | end
343 | return nil, err
344 |
345 | else
346 | -- be a valid redis error value
347 | nvals = nvals + 1
348 | vals[nvals] = {false, err}
349 | end
350 | end
351 |
352 | return vals
353 | end
354 |
355 |
356 | function _M.array_to_hash(self, t)
357 | local n = #t
358 | -- print("n = ", n)
359 | local h = new_tab(0, n / 2)
360 | for i = 1, n, 2 do
361 | h[t[i]] = t[i + 1]
362 | end
363 | return h
364 | end
365 |
366 |
367 | function _M.add_commands(...)
368 | local cmds = {...}
369 | for i = 1, #cmds do
370 | local cmd = cmds[i]
371 | _M[cmd] =
372 | function (self, ...)
373 | return _do_cmd(self, cmd, ...)
374 | end
375 | end
376 | end
377 |
378 |
379 | return _M
380 |
--------------------------------------------------------------------------------
/DockerResources/lua/lib/ws/websocketProtocol.lua:
--------------------------------------------------------------------------------
1 | -- Copyright (C) Yichun Zhang (agentzh)
2 |
3 | local bit = require "bit"
4 |
5 | local M = {[0] = "websocketProtocol"}
6 |
7 | local types = {
8 | [0x0] = "continuation",
9 | [0x1] = "text",
10 | [0x2] = "binary",
11 | [0x8] = "close",
12 | [0x9] = "ping",
13 | [0xa] = "pong",
14 | }
15 |
16 | function M.recv_frame(sock, max_payload_len, force_masking)
17 | local data, err = sock:receive(2)
18 | if not data then
19 | return nil, nil, "failed to receive the first 2 bytes: " .. err
20 | end
21 |
22 | local fst, snd = string.byte(data, 1, 2)
23 |
24 | local fin = bit.band(fst, 0x80) ~= 0
25 |
26 | if bit.band(fst, 0x70) ~= 0 then
27 | return nil, nil, "bad RSV1, RSV2, or RSV3 bits"
28 | end
29 |
30 | local opcode = bit.band(fst, 0x0f)
31 |
32 | if opcode >= 0x3 and opcode <= 0x7 then
33 | return nil, nil, "reserved non-control frames"
34 | end
35 |
36 | if opcode >= 0xb and opcode <= 0xf then
37 | return nil, nil, "reserved control frames"
38 | end
39 |
40 | local mask = bit.band(snd, 0x80) ~= 0
41 |
42 | if force_masking and not mask then
43 | return nil, nil, "frame unmasked"
44 | end
45 |
46 | local payload_len = bit.band(snd, 0x7f)
47 |
48 | if payload_len == 126 then
49 | local data, err = sock:receive(2)
50 | if not data then
51 | return nil, nil, "failed to receive the 2 byte payload length: "
52 | .. (err or "unknown")
53 | end
54 |
55 | payload_len = bit.bor(bit.lshift(string.byte(data, 1), 8), string.byte(data, 2))
56 |
57 | elseif payload_len == 127 then
58 | local data, err = sock:receive(8)
59 | if not data then
60 | return nil, nil, "failed to receive the 8 byte payload length: "
61 | .. (err or "unknown")
62 | end
63 |
64 | if string.byte(data, 1) ~= 0
65 | or string.byte(data, 2) ~= 0
66 | or string.byte(data, 3) ~= 0
67 | or string.byte(data, 4) ~= 0
68 | then
69 | return nil, nil, "payload len too large"
70 | end
71 |
72 | local fifth = string.byte(data, 5)
73 | if bit.band(fifth, 0x80) ~= 0 then
74 | return nil, nil, "payload len too large"
75 | end
76 |
77 | payload_len = bit.bor(bit.lshift(fifth, 24),
78 | bit.lshift(string.byte(data, 6), 16),
79 | bit.lshift(string.byte(data, 7), 8),
80 | string.byte(data, 8))
81 | end
82 |
83 | if bit.band(opcode, 0x8) ~= 0 then
84 | -- being a control frame
85 | if payload_len > 125 then
86 | return nil, nil, "too long payload for control frame"
87 | end
88 |
89 | if not fin then
90 | return nil, nil, "fragmented control frame"
91 | end
92 | end
93 |
94 | -- print("payload len: ", payload_len, ", max payload len: ",
95 | -- max_payload_len)
96 |
97 | if payload_len > max_payload_len then
98 | return nil, nil, "exceeding max payload len"
99 | end
100 |
101 | local rest
102 | if mask then
103 | rest = payload_len + 4
104 |
105 | else
106 | rest = payload_len
107 | end
108 | -- print("rest: ", rest)
109 |
110 | local data
111 | if rest > 0 then
112 | data, err = sock:receive(rest)
113 | if not data then
114 | return nil, nil, "failed to read masking-len and payload: "
115 | .. (err or "unknown")
116 | end
117 | else
118 | data = ""
119 | end
120 |
121 | -- print("received rest")
122 |
123 | if opcode == 0x8 then
124 | -- being a close frame
125 | if payload_len > 0 then
126 | if payload_len < 2 then
127 | return nil, nil, "close frame with a body must carry a 2-byte"
128 | .. " status code"
129 | end
130 |
131 | local msg, code
132 | if mask then
133 | local fst = bit.bxor(byte(data, 4 + 1), string.byte(data, 1))
134 | local snd = bit.bxor(byte(data, 4 + 2), string.byte(data, 2))
135 | code = bit.bor(bit.lshift(fst, 8), snd)
136 |
137 | if payload_len > 2 then
138 | -- TODO string.buffer optimizations
139 | local bytes = table.new(payload_len - 2, 0)
140 | for i = 3, payload_len do
141 | bytes[i - 2] = string.char(bit.bxor(string.byte(data, 4 + i),
142 | string.byte(data,
143 | (i - 1) % 4 + 1)))
144 | end
145 | msg = table.concat(bytes)
146 |
147 | else
148 | msg = ""
149 | end
150 |
151 | else
152 | local fst = string.byte(data, 1)
153 | local snd = string.byte(data, 2)
154 | code = bit.bor(bit.lshift(fst, 8), snd)
155 |
156 | -- print("parsing unmasked close frame payload: ", payload_len)
157 |
158 | if payload_len > 2 then
159 | msg = string.sub(data, 3)
160 |
161 | else
162 | msg = ""
163 | end
164 | end
165 |
166 | return msg, "close", code
167 | end
168 |
169 | return "", "close", nil
170 | end
171 |
172 | local msg
173 | if mask then
174 | -- TODO string.buffer optimizations
175 | local bytes = table.new(payload_len, 0)
176 | for i = 1, payload_len do
177 | bytes[i] = string.char(bit.bxor(string.byte(data, 4 + i), string.byte(data, (i - 1) % 4 + 1)))
178 | end
179 | msg = table.concat(bytes)
180 |
181 | else
182 | msg = data
183 | end
184 |
185 | return msg, types[opcode], not fin and "again" or nil
186 | end
187 |
188 |
189 | local function build_frame(fin, opcode, payload_len, payload, masking)
190 | -- TODO optimize this when we have string.buffer in LuaJIT 2.1
191 | local fst
192 | if fin then
193 | fst = bit.bor(0x80, opcode)
194 | else
195 | fst = opcode
196 | end
197 |
198 | local snd, extra_len_bytes
199 | if payload_len <= 125 then
200 | snd = payload_len
201 | extra_len_bytes = ""
202 |
203 | elseif payload_len <= 65535 then
204 | snd = 126
205 | extra_len_bytes = string.char(bit.band(bit.rshift(payload_len, 8), 0xff),
206 | bit.band(payload_len, 0xff))
207 |
208 | else
209 | if bit.band(payload_len, 0x7fffffff) < payload_len then
210 | return nil, "payload too big"
211 | end
212 |
213 | snd = 127
214 | -- XXX we only support 31-bit length here
215 | extra_len_bytes = string.char(0, 0, 0, 0, bit.band(bit.rshift(payload_len, 24), 0xff),
216 | bit.band(bit.rshift(payload_len, 16), 0xff),
217 | bit.band(bit.rshift(payload_len, 8), 0xff),
218 | bit.band(payload_len, 0xff))
219 | end
220 |
221 | local masking_key
222 | if masking then
223 | -- set the mask bit
224 | snd = bit.bor(snd, 0x80)
225 | local key = math.random(0xffffffff)
226 | masking_key = string.char(bit.band(bit.rshift(key, 24), 0xff),
227 | bit.band(bit.rshift(key, 16), 0xff),
228 | bit.band(bit.rshift(key, 8), 0xff),
229 | bit.band(key, 0xff))
230 |
231 | -- TODO string.buffer optimizations
232 | local bytes = table.new(payload_len, 0)
233 | for i = 1, payload_len do
234 | bytes[i] = string.char(bit.bxor(string.byte(payload, i),
235 | string.byte(masking_key, (i - 1) % 4 + 1)))
236 | end
237 | payload = table.concat(bytes)
238 |
239 | else
240 | masking_key = ""
241 | end
242 |
243 | return string.char(fst, snd) .. extra_len_bytes .. masking_key .. payload
244 | end
245 | M.build_frame = build_frame
246 |
247 |
248 | function M.send_frame(sock, fin, opcode, payload, max_payload_len, masking)
249 | if not payload then
250 | payload = ""
251 |
252 | elseif type(payload) ~= "string" then
253 | payload = tostring(payload)
254 | end
255 |
256 | local payload_len = #payload
257 |
258 | if payload_len > max_payload_len then
259 | return nil, "payload too big"
260 | end
261 |
262 | if bit.band(opcode, 0x8) ~= 0 then
263 | -- being a control frame
264 | if payload_len > 125 then
265 | return nil, "too much payload for control frame"
266 | end
267 | if not fin then
268 | return nil, "fragmented control frame"
269 | end
270 | end
271 |
272 | local frame, err = build_frame(fin, opcode, payload_len, payload,
273 | masking)
274 | if not frame then
275 | return nil, "failed to build frame: " .. err
276 | end
277 |
278 | local bytes, err = sock:send(frame)
279 | if not bytes then
280 | return nil, "failed to send frame: " .. err
281 | end
282 | return bytes
283 | end
284 |
285 |
286 | return M
287 |
--------------------------------------------------------------------------------
/DockerResources/lua/lib/sha1/sha1.lua:
--------------------------------------------------------------------------------
1 | local sha1 = {
2 | _VERSION = "sha.lua 0.5.0",
3 | _URL = "https://github.com/kikito/sha.lua",
4 | _DESCRIPTION = [[
5 | SHA-1 secure hash computation, and HMAC-SHA1 signature computation in Lua (5.1)
6 | Based on code originally by Jeffrey Friedl (http://regex.info/blog/lua/sha1)
7 | And modified by Eike Decker - (http://cube3d.de/uploads/Main/sha1.txt)
8 | ]],
9 | _LICENSE = [[
10 | MIT LICENSE
11 |
12 | Copyright (c) 2013 Enrique García Cota + Eike Decker + Jeffrey Friedl
13 |
14 | Permission is hereby granted, free of charge, to any person obtaining a
15 | copy of this software and associated documentation files (the
16 | "Software"), to deal in the Software without restriction, including
17 | without limitation the rights to use, copy, modify, merge, publish,
18 | distribute, sublicense, and/or sell copies of the Software, and to
19 | permit persons to whom the Software is furnished to do so, subject to
20 | the following conditions:
21 |
22 | The above copyright notice and this permission notice shall be included
23 | in all copies or substantial portions of the Software.
24 |
25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
26 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
28 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
29 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
30 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
31 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 | ]]
33 | }
34 |
35 | -----------------------------------------------------------------------------------
36 |
37 | -- loading this file (takes a while but grants a boost of factor 13)
38 | local PRELOAD_CACHE = true
39 |
40 | local BLOCK_SIZE = 64 -- 512 bits
41 |
42 | -- local storing of global functions (minor speedup)
43 | local floor,modf = math.floor,math.modf
44 | local char,format,rep = string.char,string.format,string.rep
45 |
46 | -- merge 4 bytes to an 32 bit word
47 | local function bytes_to_w32(a,b,c,d) return a*0x1000000+b*0x10000+c*0x100+d end
48 | -- split a 32 bit word into four 8 bit numbers
49 | local function w32_to_bytes(i)
50 | return floor(i/0x1000000)%0x100,floor(i/0x10000)%0x100,floor(i/0x100)%0x100,i%0x100
51 | end
52 |
53 | -- shift the bits of a 32 bit word. Don't use negative values for "bits"
54 | local function w32_rot(bits,a)
55 | local b2 = 2^(32-bits)
56 | local a,b = modf(a/b2)
57 | return a+b*b2*(2^(bits))
58 | end
59 |
60 | -- caching function for functions that accept 2 arguments, both of values between
61 | -- 0 and 255. The function to be cached is passed, all values are calculated
62 | -- during loading and a function is returned that returns the cached values (only)
63 | local function cache2arg(fn)
64 | if not PRELOAD_CACHE then return fn end
65 | local lut = {}
66 | for i=0,0xffff do
67 | local a,b = floor(i/0x100),i%0x100
68 | lut[i] = fn(a,b)
69 | end
70 | return function(a,b)
71 | return lut[a*0x100+b]
72 | end
73 | end
74 |
75 | -- splits an 8-bit number into 8 bits, returning all 8 bits as booleans
76 | local function byte_to_bits(b)
77 | local b = function(n)
78 | local b = floor(b/n)
79 | return b%2==1
80 | end
81 | return b(1),b(2),b(4),b(8),b(16),b(32),b(64),b(128)
82 | end
83 |
84 | -- builds an 8bit number from 8 booleans
85 | local function bits_to_byte(a,b,c,d,e,f,g,h)
86 | local function n(b,x) return b and x or 0 end
87 | return n(a,1)+n(b,2)+n(c,4)+n(d,8)+n(e,16)+n(f,32)+n(g,64)+n(h,128)
88 | end
89 |
90 | -- bitwise "and" function for 2 8bit number
91 | local band = cache2arg (function(a,b)
92 | local A,B,C,D,E,F,G,H = byte_to_bits(b)
93 | local a,b,c,d,e,f,g,h = byte_to_bits(a)
94 | return bits_to_byte(
95 | A and a, B and b, C and c, D and d,
96 | E and e, F and f, G and g, H and h)
97 | end)
98 |
99 | -- bitwise "or" function for 2 8bit numbers
100 | local bor = cache2arg(function(a,b)
101 | local A,B,C,D,E,F,G,H = byte_to_bits(b)
102 | local a,b,c,d,e,f,g,h = byte_to_bits(a)
103 | return bits_to_byte(
104 | A or a, B or b, C or c, D or d,
105 | E or e, F or f, G or g, H or h)
106 | end)
107 |
108 | -- bitwise "xor" function for 2 8bit numbers
109 | local bxor = cache2arg(function(a,b)
110 | local A,B,C,D,E,F,G,H = byte_to_bits(b)
111 | local a,b,c,d,e,f,g,h = byte_to_bits(a)
112 | return bits_to_byte(
113 | A ~= a, B ~= b, C ~= c, D ~= d,
114 | E ~= e, F ~= f, G ~= g, H ~= h)
115 | end)
116 |
117 | -- bitwise complement for one 8bit number
118 | local function bnot(x)
119 | return 255-(x % 256)
120 | end
121 |
122 | -- creates a function to combine to 32bit numbers using an 8bit combination function
123 | local function w32_comb(fn)
124 | return function(a,b)
125 | local aa,ab,ac,ad = w32_to_bytes(a)
126 | local ba,bb,bc,bd = w32_to_bytes(b)
127 | return bytes_to_w32(fn(aa,ba),fn(ab,bb),fn(ac,bc),fn(ad,bd))
128 | end
129 | end
130 |
131 | -- create functions for and, xor and or, all for 2 32bit numbers
132 | local w32_and = w32_comb(band)
133 | local w32_xor = w32_comb(bxor)
134 | local w32_or = w32_comb(bor)
135 |
136 | -- xor function that may receive a variable number of arguments
137 | local function w32_xor_n(a,...)
138 | local aa,ab,ac,ad = w32_to_bytes(a)
139 | for i=1,select('#',...) do
140 | local ba,bb,bc,bd = w32_to_bytes(select(i,...))
141 | aa,ab,ac,ad = bxor(aa,ba),bxor(ab,bb),bxor(ac,bc),bxor(ad,bd)
142 | end
143 | return bytes_to_w32(aa,ab,ac,ad)
144 | end
145 |
146 | -- combining 3 32bit numbers through binary "or" operation
147 | local function w32_or3(a,b,c)
148 | local aa,ab,ac,ad = w32_to_bytes(a)
149 | local ba,bb,bc,bd = w32_to_bytes(b)
150 | local ca,cb,cc,cd = w32_to_bytes(c)
151 | return bytes_to_w32(
152 | bor(aa,bor(ba,ca)), bor(ab,bor(bb,cb)), bor(ac,bor(bc,cc)), bor(ad,bor(bd,cd))
153 | )
154 | end
155 |
156 | -- binary complement for 32bit numbers
157 | local function w32_not(a)
158 | return 4294967295-(a % 4294967296)
159 | end
160 |
161 | -- adding 2 32bit numbers, cutting off the remainder on 33th bit
162 | local function w32_add(a,b) return (a+b) % 4294967296 end
163 |
164 | -- adding n 32bit numbers, cutting off the remainder (again)
165 | local function w32_add_n(a,...)
166 | for i=1,select('#',...) do
167 | a = (a+select(i,...)) % 4294967296
168 | end
169 | return a
170 | end
171 | -- converting the number to a hexadecimal string
172 | local function w32_to_hexstring(w) return format("%08x",w) end
173 |
174 | local function hex_to_binary(hex)
175 | return hex:gsub('..', function(hexval)
176 | return string.char(tonumber(hexval, 16))
177 | end)
178 | end
179 |
180 | -- building the lookuptables ahead of time (instead of littering the source code
181 | -- with precalculated values)
182 | local xor_with_0x5c = {}
183 | local xor_with_0x36 = {}
184 | for i=0,0xff do
185 | xor_with_0x5c[char(i)] = char(bxor(i,0x5c))
186 | xor_with_0x36[char(i)] = char(bxor(i,0x36))
187 | end
188 |
189 | -----------------------------------------------------------------------------
190 |
191 | -- calculating the SHA1 for some text
192 | function sha1.sha1(msg)
193 | local H0,H1,H2,H3,H4 = 0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476,0xC3D2E1F0
194 | local msg_len_in_bits = #msg * 8
195 |
196 | local first_append = char(0x80) -- append a '1' bit plus seven '0' bits
197 |
198 | local non_zero_message_bytes = #msg +1 +8 -- the +1 is the appended bit 1, the +8 are for the final appended length
199 | local current_mod = non_zero_message_bytes % 64
200 | local second_append = current_mod>0 and rep(char(0), 64 - current_mod) or ""
201 |
202 | -- now to append the length as a 64-bit number.
203 | local B1, R1 = modf(msg_len_in_bits / 0x01000000)
204 | local B2, R2 = modf( 0x01000000 * R1 / 0x00010000)
205 | local B3, R3 = modf( 0x00010000 * R2 / 0x00000100)
206 | local B4 = 0x00000100 * R3
207 |
208 | local L64 = char( 0) .. char( 0) .. char( 0) .. char( 0) -- high 32 bits
209 | .. char(B1) .. char(B2) .. char(B3) .. char(B4) -- low 32 bits
210 |
211 | msg = msg .. first_append .. second_append .. L64
212 |
213 | assert(#msg % 64 == 0)
214 |
215 | local chunks = #msg / 64
216 |
217 | local W = { }
218 | local start, A, B, C, D, E, f, K, TEMP
219 | local chunk = 0
220 |
221 | while chunk < chunks do
222 | --
223 | -- break chunk up into W[0] through W[15]
224 | --
225 | start,chunk = chunk * 64 + 1,chunk + 1
226 |
227 | for t = 0, 15 do
228 | W[t] = bytes_to_w32(msg:byte(start, start + 3))
229 | start = start + 4
230 | end
231 |
232 | --
233 | -- build W[16] through W[79]
234 | --
235 | for t = 16, 79 do
236 | -- For t = 16 to 79 let Wt = S1(Wt-3 XOR Wt-8 XOR Wt-14 XOR Wt-16).
237 | W[t] = w32_rot(1, w32_xor_n(W[t-3], W[t-8], W[t-14], W[t-16]))
238 | end
239 |
240 | A,B,C,D,E = H0,H1,H2,H3,H4
241 |
242 | for t = 0, 79 do
243 | if t <= 19 then
244 | -- (B AND C) OR ((NOT B) AND D)
245 | f = w32_or(w32_and(B, C), w32_and(w32_not(B), D))
246 | K = 0x5A827999
247 | elseif t <= 39 then
248 | -- B XOR C XOR D
249 | f = w32_xor_n(B, C, D)
250 | K = 0x6ED9EBA1
251 | elseif t <= 59 then
252 | -- (B AND C) OR (B AND D) OR (C AND D
253 | f = w32_or3(w32_and(B, C), w32_and(B, D), w32_and(C, D))
254 | K = 0x8F1BBCDC
255 | else
256 | -- B XOR C XOR D
257 | f = w32_xor_n(B, C, D)
258 | K = 0xCA62C1D6
259 | end
260 |
261 | -- TEMP = S5(A) + ft(B,C,D) + E + Wt + Kt;
262 | A,B,C,D,E = w32_add_n(w32_rot(5, A), f, E, W[t], K),
263 | A, w32_rot(30, B), C, D
264 | end
265 | -- Let H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E.
266 | H0,H1,H2,H3,H4 = w32_add(H0, A),w32_add(H1, B),w32_add(H2, C),w32_add(H3, D),w32_add(H4, E)
267 | end
268 | local f = w32_to_hexstring
269 | return f(H0) .. f(H1) .. f(H2) .. f(H3) .. f(H4)
270 | end
271 |
272 |
273 | function sha1.binary(msg)
274 | return hex_to_binary(sha1.sha1(msg))
275 | end
276 |
277 | function sha1.hmac(key, text)
278 | assert(type(key) == 'string', "key passed to sha1.hmac should be a string")
279 | assert(type(text) == 'string', "text passed to sha1.hmac should be a string")
280 |
281 | if #key > BLOCK_SIZE then
282 | key = sha1.binary(key)
283 | end
284 |
285 | local key_xord_with_0x36 = key:gsub('.', xor_with_0x36) .. string.rep(string.char(0x36), BLOCK_SIZE - #key)
286 | local key_xord_with_0x5c = key:gsub('.', xor_with_0x5c) .. string.rep(string.char(0x5c), BLOCK_SIZE - #key)
287 |
288 | return sha1.sha1(key_xord_with_0x5c .. sha1.binary(key_xord_with_0x36 .. text))
289 | end
290 |
291 | function sha1.hmac_binary(key, text)
292 | return hex_to_binary(sha1.hmac(key, text))
293 | end
294 |
295 | setmetatable(sha1, {__call = function(_,msg) return sha1.sha1(msg) end })
296 |
297 | return sha1
298 |
--------------------------------------------------------------------------------