-- 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 = rawget(_G, "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}