mirror of
https://github.com/Quad4-Software/Reticulum-Go
synced 2026-08-29 23:48:44 -04:00
219 lines
5.6 KiB
Lua
219 lines
5.6 KiB
Lua
-- SPDX-License-Identifier: Apache-2.0
|
|
-- Copyright (c) 2024-2026 Quad4.io
|
|
--
|
|
-- NomadNet-style page fetch over LuaJIT librns bindings.
|
|
-- Usage: luajit main.lua -c config [-t timeout_sec] <dest_hash>:<page_path>
|
|
|
|
local rns = require("rns")
|
|
|
|
local PAGE_BUF_CAP = 512 * 1024
|
|
local DEFAULT_TIMEOUT_SEC = 60
|
|
local PATH_RETRY_SEC = 2
|
|
|
|
local function die(msg)
|
|
io.stderr:write(msg .. "\n")
|
|
os.exit(1)
|
|
end
|
|
|
|
local function print_last_error(what)
|
|
local msg = rns.last_error()
|
|
if msg ~= "" then
|
|
io.stderr:write(what .. ": " .. msg .. "\n")
|
|
else
|
|
io.stderr:write(what .. "\n")
|
|
end
|
|
end
|
|
|
|
local function parse_args(argv)
|
|
local config_path, target
|
|
local timeout_sec = DEFAULT_TIMEOUT_SEC
|
|
local i = 1
|
|
while i <= #argv do
|
|
local a = argv[i]
|
|
if a == "-c" then
|
|
i = i + 1
|
|
config_path = argv[i]
|
|
elseif a == "-t" then
|
|
i = i + 1
|
|
timeout_sec = tonumber(argv[i]) or 0
|
|
elseif not target then
|
|
target = a
|
|
else
|
|
die("unexpected argument: " .. a)
|
|
end
|
|
i = i + 1
|
|
end
|
|
if not config_path or not target then
|
|
die("usage: luajit main.lua -c config [-t timeout] <dest_hash>:<page_path>")
|
|
end
|
|
if timeout_sec <= 0 then
|
|
die("timeout must be positive")
|
|
end
|
|
return config_path, target, timeout_sec
|
|
end
|
|
|
|
local function parse_target(target)
|
|
local hex_part, page_path = target:match("^([^:]+):(.+)$")
|
|
if not hex_part or not page_path then
|
|
error("bad target", 0)
|
|
end
|
|
return rns.hex_to_hash(hex_part), page_path
|
|
end
|
|
|
|
local config_path, target, timeout_sec = parse_args(arg)
|
|
|
|
local ver = rns.version()
|
|
if ver ~= rns.API_VERSION then
|
|
die("librns version mismatch: got " .. ver)
|
|
end
|
|
|
|
local dest_hash, page_path = parse_target(target)
|
|
local dest_hex = rns.hash_to_hex(dest_hash)
|
|
|
|
local node = rns.Node.create(config_path)
|
|
local identity = rns.Identity.generate()
|
|
node:set_identity(identity)
|
|
node:start()
|
|
|
|
print(string.format("librns %s fetching %s from %s", ver, page_path, dest_hex))
|
|
|
|
local deadline = os.time() + timeout_sec
|
|
local last_path_req = 0
|
|
local need_path_req = true
|
|
local saw_announce = false
|
|
local link = nil
|
|
|
|
local function cleanup()
|
|
if link then
|
|
link:close()
|
|
end
|
|
identity:close()
|
|
node:close()
|
|
end
|
|
|
|
while os.time() < deadline and link == nil do
|
|
local t = os.time()
|
|
if need_path_req or t - last_path_req >= PATH_RETRY_SEC then
|
|
local ok = pcall(rns.path_request, node, dest_hash)
|
|
if not ok then
|
|
print_last_error("path_request failed")
|
|
end
|
|
last_path_req = t
|
|
need_path_req = false
|
|
if rns.path_known(node, dest_hash) then
|
|
io.stderr:write("path known, waiting for destination identity announce\n")
|
|
else
|
|
io.stderr:write("requesting path to " .. dest_hex .. "\n")
|
|
end
|
|
end
|
|
|
|
local ok_ev, ev_or_err = pcall(rns.Event.poll, node, 200, PAGE_BUF_CAP)
|
|
if not ok_ev then
|
|
if type(ev_or_err) == "table" and ev_or_err.code == rns.Error.TIMEOUT then
|
|
if saw_announce or rns.path_known(node, dest_hash) then
|
|
local ok_link, opened = pcall(rns.Link.open, node, dest_hash)
|
|
if ok_link then
|
|
link = opened
|
|
end
|
|
end
|
|
else
|
|
print_last_error("Event.poll failed")
|
|
cleanup()
|
|
os.exit(1)
|
|
end
|
|
else
|
|
local ev = ev_or_err
|
|
if ev:kind() == rns.EventKind.ANNOUNCE and rns.hash_eq(ev:destination_hash(), dest_hash) then
|
|
saw_announce = true
|
|
io.stderr:write("announce for target (hops=" .. ev:hops() .. ")\n")
|
|
local ok_link, opened = pcall(rns.Link.open, node, dest_hash)
|
|
if ok_link then
|
|
link = opened
|
|
else
|
|
print_last_error("Link.open after announce")
|
|
end
|
|
elseif ev:kind() == rns.EventKind.LINK_FAILED then
|
|
io.stderr:write("link failed while opening: " .. ev:error_message() .. "\n")
|
|
end
|
|
end
|
|
end
|
|
|
|
if link == nil then
|
|
cleanup()
|
|
die("timed out before link open")
|
|
end
|
|
|
|
local established = false
|
|
while os.time() < deadline and not established do
|
|
local ok_ev, ev_or_err = pcall(rns.Event.poll, node, 500, PAGE_BUF_CAP)
|
|
if not ok_ev then
|
|
if type(ev_or_err) ~= "table" or ev_or_err.code ~= rns.Error.TIMEOUT then
|
|
print_last_error("Event.poll failed")
|
|
cleanup()
|
|
os.exit(1)
|
|
end
|
|
else
|
|
local ev = ev_or_err
|
|
if ev:kind() == rns.EventKind.LINK_ESTABLISHED then
|
|
established = true
|
|
io.stderr:write("link established\n")
|
|
elseif ev:kind() == rns.EventKind.LINK_FAILED then
|
|
cleanup()
|
|
die("link establishment failed: " .. ev:error_message())
|
|
elseif ev:kind() == rns.EventKind.LINK_CLOSED then
|
|
cleanup()
|
|
die("link closed before establish")
|
|
end
|
|
end
|
|
end
|
|
|
|
if not established then
|
|
cleanup()
|
|
die("timed out waiting for link establishment")
|
|
end
|
|
|
|
local remaining_ms = math.max((deadline - os.time()) * 1000, 1000)
|
|
if not pcall(function()
|
|
link:request(node, page_path, "", remaining_ms)
|
|
end) then
|
|
print_last_error("link.request failed")
|
|
cleanup()
|
|
os.exit(1)
|
|
end
|
|
io.stderr:write("request sent for " .. page_path .. "\n")
|
|
|
|
while os.time() < deadline do
|
|
local ok_ev, ev_or_err = pcall(rns.Event.poll, node, 500, PAGE_BUF_CAP)
|
|
if not ok_ev then
|
|
if type(ev_or_err) ~= "table" or ev_or_err.code ~= rns.Error.TIMEOUT then
|
|
print_last_error("Event.poll failed")
|
|
cleanup()
|
|
os.exit(1)
|
|
end
|
|
else
|
|
local ev = ev_or_err
|
|
if ev:kind() == rns.EventKind.REQUEST_RESPONSE then
|
|
local data = ev:app_data()
|
|
print(string.format("\n=== Page Content (%d bytes) ===", #data))
|
|
io.write(data)
|
|
if #data == 0 or data:sub(-1) ~= "\n" then
|
|
io.write("\n")
|
|
end
|
|
if ev:app_data_truncated() then
|
|
io.stderr:write("warning: response truncated\n")
|
|
end
|
|
print("=== End of Page ===")
|
|
cleanup()
|
|
os.exit(0)
|
|
elseif ev:kind() == rns.EventKind.REQUEST_FAILED then
|
|
cleanup()
|
|
die("request failed: " .. ev:error_message())
|
|
elseif ev:kind() == rns.EventKind.LINK_CLOSED then
|
|
cleanup()
|
|
die("link closed before response")
|
|
end
|
|
end
|
|
end
|
|
|
|
cleanup()
|
|
die("timed out waiting for page response")
|