Skip to content

Instantly share code, notes, and snippets.

@joshkunz joshkunz/tracker.lua
Last active Jan 25, 2016

Embed
What would you like to do?
Lua Tracker. A tracker in lua using the http://webscript.io interface. I believe it works, but I think there's a bug in the webscript system that's throwing 500s for url-encoded binary in GET requests basically making this useless.
function split(s, on)
local elems = {}
local buffer = ""
local i, c
for i = 1, #s do
c = string.sub(s, i,i)
if c == on then
table.insert(elems, buffer)
buffer = ""
else
buffer = buffer .. c
end
end
if buffer ~= "" then table.insert(elems, buffer) end
return elems
end
function format_table(t, indent)
local k, v, kstr, vstr
local full = ""
for k, v in pairs(t) do
if type(k) == "table" then
kstr = "\n" .. format_table(k, indent + 1)
else kstr = tostring(k) end
if type(v) == "table" then
vstr = "\n" .. format_table(v, indent + 1)
else vstr = tostring(v) end
full = full ..string.format( string.rep(" ", indent * 4)
.. "<%s %s> = <%s %s>\n"
, type(k), kstr, type(v), vstr)
end
return full
end
function display_table(t)
io.write(format_table(t, 0))
end
function char_class_from_string(str)
local tbl = {}
local i
for i = 1, #str do
tbl[string.sub(str,i,i)] = true
end
return tbl
end
DIGITS = char_class_from_string("0123456789")
-- For 'nil' elimination
function isdigit(c)
if DIGITS[c] == true then
return true
else
return false
end
end
function bdecode(str)
local peek = function ()
return string.sub(str, 1, 1)
end
local eat = function ()
str = string.sub(str, 2)
end
local get = function (what)
if peek() == what then
eat()
return true
else return false end
end
function _d_int ()
local istr = ""
local c = peek()
local first = true
while (first and c == "-") or isdigit(c) do
first = false
get(c)
istr = istr .. c
c = peek()
end
return tonumber(istr)
end
function d_byte_str ()
local length = _d_int()
local gotten = ""
assert(length >= 0)
get(":")
if length ~= 0 then
gotten = string.sub(str, 1, length)
str = string.sub(str, 1 + length)
end
return gotten
end
function d_int()
get("i")
local n = _d_int()
get("e")
return n
end
function d_value()
if peek() == "e" then
return nil
elseif isdigit(peek()) then
return d_byte_str()
elseif peek() == "i" then
return d_int()
elseif peek() == "l" then
return d_list()
elseif peek() == "d" then
return d_dict()
else
error("Invalid specifier in list")
end
end
function d_list()
get("l")
local list = {}
while true do
local value = d_value()
if value == nil then
get("e")
return list
else
table.insert(list, value)
end
end
end
function d_dict()
get("d")
local dict = {}
while true do
if peek() == "e" then
get("e")
return dict
else
local key = d_byte_str()
local value = d_value()
assert(value ~= nil)
dict[key] = value
end
end
end
local parsed = d_value()
assert(parsed ~= nil)
return parsed
end
function bencode(struct)
function e_str(s) return string.format("%s:%s", #s, s) end
function e_int(i) return string.format("i%se", i) end
function e_list(l)
local list = "l"
local val
for _, val in ipairs(l) do
list = list .. e_value(val)
end
list = list .. "e"
return list
end
function e_dict(d)
local k
local keys = {}
for k, _ in pairs(d) do
assert(type(k) == "string")
table.insert(keys, k)
end
table.sort(keys)
local dict = "d"
for _, k in ipairs(keys) do
dict = dict .. e_str(k)
dict = dict .. e_value(d[k])
end
dict = dict .. "e"
return dict
end
function e_value(v)
if type(v) == "string" then
return e_str(v)
elseif type(v) == "number" then
return e_int(v)
elseif type(v) == "table" then
local allstr = true
for k, _ in pairs(v) do
allstr = type(k) == "string"
if not allstr then break end
end
if allstr then
return e_dict(v)
else return e_list(v) end
else
error("Un-bencodable type")
end
end
return e_value(struct)
end
-- Peer Interface
EXPECTED_PEER_FIELDS = {
"id", "host", "port", "uploaded", "downloaded"
, "left", "event", "numwant"
}
function peer_from_query()
local peer = {}
peer.id = request.query.peer_id
peer.host = request.remote_addr
peer.port = tonumber(request.query.port)
peer.uploaded = tonumber(request.query.uploaded)
peer.downloaded = tonumber(request.query.downloaded)
peer.left = tonumber(request.query.left)
peer.event = request.query.event
peer.numwant = 50
if request.query.numwant ~= nil then
peer.numwant = tonumber(request.query.numwant)
end
return peer
end
function peer_is_valid(peer)
local k, reason
for _, k in ipairs(EXPECTED_PEER_FIELDS) do
if peer[k] == nil then
reason = string.format( "Required parameter %q not supplied"
, k )
return { ["status"] = false, ["reason"] = reason }
end
return { ["status"] = true }
end
end
function peer_render_compact(peer)
local ipv4_octets = split(peer.host, ".")
assert(#ipv4_octets == 4)
local rendered = string.char(tonumber(ipv4_octets[1]))
.. string.char(tonumber(ipv4_octets[2]))
.. string.char(tonumber(ipv4_octets[3]))
.. string.char(tonumber(ipv4_octets[4]))
local port_lower = peer.port % 256
local port_higher = peer.port / 256
rendered = rendered .. string.char(port_higher)
.. string.char(port_lower)
return rendered
end
function peer_render(peer)
local rendered = {}
rendered["peer id"] = peer.id
rendered["ip"] = peer.host
rendered["port"] = peer.port
return rendered
end
-- Swarm interface
SWARM_PREFIX = "swarm_"
function swarm_id_for_hash(info_hash)
return SWARM_PREFIX .. info_hash
end
function swarm_get(swarm_id)
if storage[swarm_id] == nil then
return {}
else
return json.parse(storage[swarm_id])
end
end
function swarm_commit(swarm_id, swarm)
storage[swarm_id] = json.stringify(swarm)
end
function swarm_add_peer(swarm, peer)
log("peer id: " .. peer.id)
swarm[peer.id] = peer
end
function swarm_render_info(swarm, is_compact)
local result = {}
if is_compact then
result.peer_list = ""
else
result.peer_list = {} end
result.complete = 0
result.incomplete = 0
local peer
for _, peer in pairs(swarm) do
if peer.left == 0 then
result.complete = result.complete + 1
else result.incomplete = result.incomplete + 1 end
if is_compact then
local rendered = peer_render_compact(peer)
result.peer_list = result.peer_list .. rendered
else
table.insert(result.peer_list, peer_render(peer))
end
end
return result
end
HEADERS = { ["Content-Type"] = "text/plain" }
info_hash = request.query.info_hash
peer = peer_from_query()
is_compact = tonumber(request.query.compact) == 1
-- Make sure the peer is at least trivialy valid
assert(info_hash ~= nil)
peer_valid = peer_is_valid(peer)
if not peer_valid["status"] then
body = bencode({ ["failure reason"] = peer_valid["reason"] })
return body, 400, HEADERS
end
swarm_id = swarm_id_for_hash(info_hash)
swarm = swarm_get(swarm_id)
swarm_add_peer(swarm, peer)
swarm_info = swarm_render_info(swarm, is_compact)
swarm_commit(swarm_id, swarm)
response = {}
response.interval = 10
response["tracker id"] = "Lua Tracker v0.1"
response.complete = swarm_info.complete
response.incomplete = swarm_info.incomplete
response.peers = swarm_info.peer_list
return bencode(response), 200, HEADERS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.