97 lines
3.2 KiB
Lua
97 lines
3.2 KiB
Lua
|
-- Based on bencode.lua from the jeejah project by Phil Hagelberg
|
||
|
-- Distributed under the MIT license
|
||
|
-- https://gitlab.com/technomancy/jeejah/
|
||
|
|
||
|
local encode, decode
|
||
|
|
||
|
local function decode_list(str, t, total_len)
|
||
|
-- print("list", str, lume.serialize(t))
|
||
|
if #str == 0 then error("Incomplete") end
|
||
|
if(str:sub(1,1) == "e") then return t, total_len + 1 end
|
||
|
local value, v_len = decode(str)
|
||
|
table.insert(t, value)
|
||
|
total_len = total_len + v_len
|
||
|
return decode_list(str:sub(v_len + 1), t, total_len)
|
||
|
end
|
||
|
|
||
|
local function decode_table(str, t, total_len)
|
||
|
-- print("table", str, lume.serialize(t))
|
||
|
if #str == 0 then error("Incomplete") end
|
||
|
if(str:sub(1,1) == "e") then return t, total_len + 1 end
|
||
|
local key, k_len = decode(str)
|
||
|
local value, v_len = decode(str:sub(k_len+1))
|
||
|
local end_pos = 1 + k_len + v_len
|
||
|
t[key] = value
|
||
|
total_len = total_len + k_len + v_len
|
||
|
return decode_table(str:sub(end_pos), t, total_len)
|
||
|
end
|
||
|
|
||
|
function decode(str)
|
||
|
-- print("decoding", str)
|
||
|
if #str == 0 then
|
||
|
error("Incomplete")
|
||
|
elseif(str:sub(1,1) == "l") then
|
||
|
return decode_list(str:sub(2), {}, 1)
|
||
|
elseif(str:sub(1,1) == "d") then
|
||
|
return decode_table(str:sub(2), {}, 1)
|
||
|
elseif(str:sub(1,1) == "i") then
|
||
|
local iend = str:find("e")
|
||
|
if iend == nil then error("Incomplete") end
|
||
|
return(tonumber(str:sub(2, iend - 1))), iend
|
||
|
elseif(str:match("[0-9]+:")) then
|
||
|
local num_str = str:match("[0-9]+")
|
||
|
local beginning_of_string = #num_str + 2
|
||
|
local str_len = tonumber(num_str)
|
||
|
local total_len = beginning_of_string + str_len - 1
|
||
|
if #str < total_len then error("Incomplete") end
|
||
|
return str:sub(beginning_of_string, total_len), total_len
|
||
|
else
|
||
|
error("Could not parse "..str)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function encode_str(s) return #s .. ":" .. s end
|
||
|
local function encode_int(n) return "i" .. tostring(n) .. "e" end
|
||
|
|
||
|
local function encode_table(t)
|
||
|
-- sort keys by encoded value as per bencode spec
|
||
|
-- https://www.bittorrent.org/beps/bep_0003.html#bencoding
|
||
|
-- we assume that sorting the concatenated key-value pairs will result in the same ordering as just the keys, since keys are unique
|
||
|
-- even if this is untrue in some corner cases, the most important thing for our purposes is that the same table always results in the same encoding
|
||
|
local encoded_kvs = {}
|
||
|
for k,v in pairs(t) do table.insert(encoded_kvs, encode(k) .. encode(v)) end
|
||
|
table.sort(encoded_kvs)
|
||
|
table.insert(encoded_kvs, 1, "d")
|
||
|
table.insert(encoded_kvs, "e")
|
||
|
return table.concat(encoded_kvs)
|
||
|
end
|
||
|
|
||
|
local function encode_list(l)
|
||
|
local s = "l"
|
||
|
for _,x in ipairs(l) do s = s .. encode(x) end
|
||
|
return s .. "e"
|
||
|
end
|
||
|
|
||
|
local function count(tbl)
|
||
|
local i = 0
|
||
|
for _ in pairs(tbl) do i = i + 1 end
|
||
|
return i
|
||
|
end
|
||
|
|
||
|
function encode(x)
|
||
|
local unpack = unpack or table.unpack
|
||
|
if(type(x) == "table" and select("#", unpack(x)) == count(x)) then
|
||
|
return encode_list(x)
|
||
|
elseif(type(x) == "table") then
|
||
|
return encode_table(x)
|
||
|
elseif(type(x) == "number" and math.floor(x) == x) then
|
||
|
return encode_int(x)
|
||
|
elseif(type(x) == "string") then
|
||
|
return encode_str(x)
|
||
|
else
|
||
|
error("Could not encode " .. type(x) .. ": " .. tostring(x))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return {decode=decode, encode=encode}
|