Compare commits

..

No commits in common. "92084e8c90a7e0719671f56e8287f86d185cd473" and "6a39fe17430a86cd4f898e7c292c9f0593321123" have entirely different histories.

4 changed files with 435 additions and 49 deletions

63
lib/interminitel.lua Normal file
View File

@ -0,0 +1,63 @@
local imt = {}
imt.ttypes = {}
imt.ttypes.string=1
imt.ttypes.number=2
imt.ftypes = {tostring,tonumber}
function imt.to16bn(n)
return string.char(math.floor(n/256))..string.char(math.floor(n%256))
end
function imt.from16bn(s)
return (string.byte(s,1,1)*256)+string.byte(s,2,2)
end
function imt.encodePacket(...)
local tArgs = {...}
local packet = string.char(#tArgs%256)
for _,segment in ipairs(tArgs) do
local segtype = type(segment)
segment = tostring(segment)
packet = packet .. imt.to16bn(segment:len()) .. string.char(imt.ttypes[segtype]) .. tostring(segment)
end
packet = imt.to16bn(packet:len()) .. packet
return packet
end
function imt.decodePacket(s)
local function getfirst(n)
local ns = s:sub(1,n)
s=s:sub(n+1)
return ns
end
if s:len() < 2 then return false end
local plen = imt.from16bn(getfirst(2))
local segments = {}
if s:len() < plen then return false end
local nsegments = string.byte(getfirst(1))
--print(tostring(plen).." bytes, "..tostring(nsegments).." segments")
for i = 1, nsegments do
local seglen = imt.from16bn(getfirst(2))
local segtype = imt.ftypes[string.byte(getfirst(1))]
local segment = segtype(getfirst(seglen))
--print(seglen,segtype,segment,type(segment))
segments[#segments+1] = segment
end
return table.unpack(segments)
end
function imt.getRemainder(s)
local function getfirst(n)
local ns = s:sub(1,n)
s=s:sub(n+1)
return ns
end
local plen = imt.from16bn(getfirst(2))
if s:len() > plen then
getfirst(plen)
return s
end
return nil
end
return imt

View File

@ -1,13 +1,9 @@
local serial = require "serialization" local serial = require "serialization"
local dl = require "download" local dl = require "download"
local mtar = require "libmtar"
local pkg = {} local pkg = {}
pkg.cfgPath = "/boot/cfg/pkg" pkg.cfgPath = "/boot/cfg/pkg"
pkg.sourcePath = pkg.cfgPath .. "/sources.cfg" pkg.sourcePath = pkg.cfgPath .. "/sources.cfg"
pkg.installedPath = pkg.cfgPath .. "/installed.cfg" pkg.installedPath = pkg.cfgPath .. "/installed.cfg"
local w, lz16 = pcall(require,"liblz16")
if not w then lz16 = nil end
require "pkgfs"
local function getSources() local function getSources()
local f = io.open(pkg.sourcePath,"rb") local f = io.open(pkg.sourcePath,"rb")
@ -57,31 +53,6 @@ end
local function deactivatePackage(path) local function deactivatePackage(path)
require("pkgfs").remove(path) require("pkgfs").remove(path)
end end
local function fnormalize(s)
return table.concat(fs.segments(s),"/")
end
local function installSystemPackage(path,comp)
local f
if comp and lz16 then
f = lz16.open(path,"rb")
else
f = io.open(path,"rb")
end
for fname, read, size in mtar.iter(f) do
local opath = "/boot/"..fnormalize(fname)
print(opath..": "..tostring(size))
fs.makeDirectory(opath:match("(.+)/[^/]+"))
local of = io.open(opath,"wb")
if not of then error("unable to open "..opath.." for writing") end
local tmp
repeat
tmp = read(2048) or ""
of:write(tmp)
until not tmp or tmp:len() < 1
of:close()
end
return true
end
function pkg.addRepo(name,path,cache) -- string string boolean -- boolean -- Adds a repository, referred to as *name*, to the list of package sources, with the remote path *path*. If *cache* is set, keep a local copy of the repository index. function pkg.addRepo(name,path,cache) -- string string boolean -- boolean -- Adds a repository, referred to as *name*, to the list of package sources, with the remote path *path*. If *cache* is set, keep a local copy of the repository index.
local sources = getSources() local sources = getSources()
@ -104,7 +75,7 @@ function pkg.update() -- Re-download cached repository indexes.
end end
end end
function pkg.list(filter,installed) -- string boolean -- -- Print a list of available packages matching *filter*, optionally filtering to only installed packages if *installed* is set. function pkg.list(filter) -- string -- -- Print a list of available packages matching *filter*.
filter = filter or "" filter = filter or ""
local pkglist = {} local pkglist = {}
for repo,_ in pairs(getSources()) do for repo,_ in pairs(getSources()) do
@ -116,8 +87,7 @@ function pkg.list(filter,installed) -- string boolean -- -- Print a list of avai
end end
end end
for k,v in pairs(pkglist) do for k,v in pairs(pkglist) do
if v.system then io.write("\27[31m") end print(string.format("%s/%s\n %s",v.repo,k,v.description))
print(string.format("%s/%s: %s\27[0m\n %s\n Authors: %s",v.repo,k,v.name,v.description,v.authors))
end end
end end
@ -133,54 +103,48 @@ function pkg.getMeta(pkgname) -- string -- table -- Returns the metadata for a t
end end
end end
function pkg.get(pkgname,auto) -- string boolean -- boolean -- Downloads and mounts a package, identified as *pkgname,* onto the pkgfs. Setting *auto* will flag the package as automatically installed; this is used for dependencies. function pkg.get(pkgname,auto) -- string boolean -- boolean -- Downloads and mounts a package, identified as *pkgname*, onto the pkgfs.
local pkginfo = pkg.getMeta(pkgname) local pkginfo = pkg.getMeta(pkgname)
if not pkginfo then error("unable to locate package "..pkgname) end if not pkginfo then error("unable to locate package "..pkgname) end
pkginfo.manual = not auto pkginfo.manual = not auto
fs.makeDirectory("/boot/pkg") fs.makeDirectory("/boot/pkg")
for k,v in ipairs(pkginfo.dependencies or {}) do for k,v in ipairs(pkginfo.dependencies or {}) do
if not getInstalled()[v] then if not getInstalled()[v] then
pkg.get(v,true) pkg.get(v)
end end
end end
dl(pkginfo.repository.path.."/"..pkginfo.filename,"/boot/pkg/"..pkginfo.filename) dl(pkginfo.repository.path.."/"..pkginfo.filename,"/boot/pkg/"..pkginfo.filename)
local installed = getInstalled() local installed = getInstalled()
installed[pkgname] = pkginfo installed[pkgname] = pkginfo
saveInstalled(installed) saveInstalled(installed)
if pkginfo.system then
local rv = installSystemPackage("/boot/pkg/"..pkginfo.filename,pkginfo.compressed)
fs.remove("/boot/pkg/"..pkginfo.filename)
return rv
end
pcall(activatePackage,"/boot/pkg/"..pkginfo.filename,pkginfo.compressed) pcall(activatePackage,"/boot/pkg/"..pkginfo.filename,pkginfo.compressed)
return true return true
end end
function pkg.upgrade(force) -- boolean -- -- Upgrades all packages on the system to the current version stored in the relevant repository. If *force* is set, re-download all packages. function pkg.upgrade(force) -- boolean -- boolean -- Upgrades all packages on the system to the current version stored in the relevant repository. If *force* is set, re-download all packages.
pkg.update() pkg.update()
fs.makeDirectory("/boot/pkg") fs.makeDirectory("/boot/pkg")
local installed = getInstalled() local installed = getInstalled()
for repo,info in pairs(getSources()) do for repo,info in pairs(getSources()) do
for pkgname,pkginfo in pairs(getRepoMeta(repo)) do for pkgname,pkg in pairs(getRepoMeta(repo)) do
if installed[pkgname] and pkginfo.version ~= installed[pkgname].version or force then if pkg.version ~= installed[pkgname].version or force then
pkg.remove(pkgname) pkg.remove(pkgname)
pkg.get(pkgname,pkginfo.auto) dl(info.path.."/"..pkg.filename,"/boot/pkg/"..pkg.filename)
installed[pkgname] = pkg
pcall(activatePackage,"/boot/pkg/"..pkg.filename,pkg.compressed)
return true
end end
end end
end end
saveInstalled(installed)
end end
function pkg.remove(pkgname) -- string -- boolean -- Remove the package *pkgname* from the pkgfs and package directory. function pkg.remove(pkgname) -- string -- boolean -- Remove the package *pkgname* from the pkgfs and package directory.
local installed = getInstalled() local installed = getInstalled()
local pkginfo = installed[pkgname] local pkginfo = installed[pkgname]
if not pkginfo then return true end if not pkginfo then error(pkgname .." not installed") end
pcall(deactivatePackage,"/boot/pkg/"..pkginfo.filename) pcall(deactivatePackage,"/boot/pkg/"..pkginfo.filename)
fs.remove("/boot/pkg/"..pkginfo.filename) fs.remove("/boot/pkg/"..pkginfo.filename)
if pkginfo.system then
for k,v in pairs(pkginfo.files) do
fs.remove(v)
end
end
installed[pkgname] = nil installed[pkgname] = nil
saveInstalled(installed) saveInstalled(installed)
return true return true

197
lib/vcomponent.lua Normal file
View File

@ -0,0 +1,197 @@
local proxylist = {}
local proxyobjs = {}
local typelist = {}
local doclist = {}
local oproxy = component.proxy
function component.proxy(address)
checkArg(1,address,"string")
if proxyobjs[address] ~= nil then
return proxyobjs[address]
end
return oproxy(address)
end
local olist = component.list
function component.list(filter, exact)
checkArg(1,filter,"string","nil")
local result = {}
local data = {}
for k,v in olist(filter, exact) do
data[#data + 1] = k
data[#data + 1] = v
result[k] = v
end
for k,v in pairs(typelist) do
if filter == nil or (exact and v == filter) or (not exact and v:find(filter, nil, true)) then
data[#data + 1] = k
data[#data + 1] = v
result[k] = v
end
end
local place = 1
return setmetatable(result,
{__call=function()
local addr,type = data[place], data[place + 1]
place = place + 2
return addr, type
end}
)
end
local otype = component.type
function component.type(address)
checkArg(1,address,"string")
if typelist[address] ~= nil then
return typelist[address]
end
return otype(address)
end
local odoc = component.doc
function component.doc(address, method)
checkArg(1,address,"string")
checkArg(2,method,"string")
if proxylist[address] ~= nil then
if proxylist[address][method] == nil then
error("no such method",2)
end
if doclist[address] ~= nil then
return doclist[address][method]
end
return nil
end
return odoc(address, method)
end
local oslot = component.slot
function component.slot(address)
checkArg(1,address,"string")
if proxylist[address] ~= nil then
return -1 -- vcomponents do not exist in a slot
end
return oslot(address)
end
local omethods = component.methods
function component.methods(address)
checkArg(1,address,"string")
if proxylist[address] ~= nil then
local methods = {}
for k,v in pairs(proxylist[address]) do
if type(v) == "function" then
methods[k] = true -- All vcomponent methods are direct
end
end
return methods
end
return omethods(address)
end
local oinvoke = component.invoke
function component.invoke(address, method, ...)
checkArg(1,address,"string")
checkArg(2,method,"string")
if proxylist[address] ~= nil then
if proxylist[address][method] == nil then
error("no such method",2)
end
return proxylist[address][method](...)
end
return oinvoke(address, method, ...)
end
local ofields = component.fields
function component.fields(address)
checkArg(1,address,"string")
if proxylist[address] ~= nil then
return {} -- What even is this?
end
return ofields(address)
end
local componentCallback =
{
__call = function(self, ...) return proxylist[self.address][self.name](...) end,
__tostring = function(self) return (doclist[self.address] ~= nil and doclist[self.address][self.name] ~= nil) and doclist[self.address][self.name] or "function" end
}
local vcomponent = {}
function vcomponent.register(address, ctype, proxy, doc)
checkArg(1,address,"string")
checkArg(2,ctype,"string")
checkArg(3,proxy,"table")
if proxylist[address] ~= nil then
return nil, "component already at address"
elseif component.type(address) ~= nil then
return nil, "cannot register over real component"
end
proxy.address = address
proxy.type = ctype
local proxyobj = {}
for k,v in pairs(proxy) do
if type(v) == "function" then
proxyobj[k] = setmetatable({name=k,address=address},componentCallback)
else
proxyobj[k] = v
end
end
proxylist[address] = proxy
proxyobjs[address] = proxyobj
typelist[address] = ctype
doclist[address] = doc
computer.pushSignal("component_added",address,ctype)
return true
end
function vcomponent.unregister(address)
checkArg(1,address,"string")
if proxylist[address] == nil then
if component.type(address) ~= nil then
return nil, "cannot unregister real component"
else
return nil, "no component at address"
end
end
local thetype = typelist[address]
proxylist[address] = nil
proxyobjs[address] = nil
typelist[address] = nil
doclist[address] = nil
computer.pushSignal("component_removed",address,thetype)
return true
end
function vcomponent.list()
local list = {}
for k,v in pairs(proxylist) do
list[#list + 1] = {k,typelist[k],v}
end
return list
end
function vcomponent.resolve(address, componentType)
checkArg(1, address, "string")
checkArg(2, componentType, "string", "nil")
for k,v in pairs(typelist) do
if componentType == nil or v == componentType then
if k:sub(1, #address) == address then
return k
end
end
end
return nil, "no such component"
end
local r = math.random
function vcomponent.uuid()
return string.format("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
r(0,255),r(0,255),r(0,255),r(0,255),
r(0,255),r(0,255),
r(64,79),r(0,255),
r(128,191),r(0,255),
r(0,255),r(0,255),r(0,255),r(0,255),r(0,255),r(0,255))
end
return vcomponent

162
service/vtunnel.lua Normal file
View File

@ -0,0 +1,162 @@
local vcomponent = require "vcomponent"
local serial = require "serialization"
local component = require "component"
local computer = require "computer"
local event = require "event"
local imt = require "interminitel"
local cfg = {}
cfg.peers = {}
cfg.rtimer = 5
cfg.katimer = 30
local listeners = {}
local proxies = {}
local function loadcfg()
local f = io.open("/boot/cfg/vtunnel.cfg","rb")
if not f then return false end
for k,v in pairs(serial.unserialize(f:read("*a")) or {}) do
cfg[k] = v
end
f:close()
end
local function savecfg()
local f = io.open("/boot/cfg/vtunnel.cfg","wb")
if not f then
print("Warning: unable to save configuration.")
return false
end
f:write(serial.serialize(cfg))
f:close()
end
local function createTunnel(host,port,addr,raddr)
local proxy = {address=addr,buffer=""}
function proxy.connect()
if proxy.socket then
proxy.socket.close()
end
proxy.socket = component.invoke(component.list("internet")(),"connect",host,port)
local st = computer.uptime()
repeat
coroutine.yield()
until proxy.socket.finishConnect() or computer.uptime() > st+5
end
function proxy.send(...)
rt = 0
while not proxy.socket.write(imt.encodePacket(...)) and rt < 10 do
proxy.connect()
rt = rt + 1
end
proxy.last = computer.uptime()
end
function proxy.read()
local rb, r
local rt = 0
while true do
rb,r = proxy.socket.read(4096)
if rb or rt > 10 then break end
if type(rb) == "nil" then
proxy.connect()
end
rt = rt + 1
end
proxy.buffer = proxy.buffer .. rb
while imt.decodePacket(proxy.buffer) do
computer.pushSignal("modem_message",addr,raddr,0,0,imt.decodePacket(proxy.buffer))
proxy.buffer = imt.getRemainder(proxy.buffer) or ""
end
if computer.uptime() > proxy.last + cfg.katimer then
proxy.socket.write("\0\1\0")
proxy.last = computer.uptime()
end
end
function proxy.getWakeMessage()
return false
end
proxy.setWakeMessage = proxy.getWakeMessage
function proxy.maxPacketSize()
return 8192
end
function proxy.getChannel()
return host..":"..tostring(port)
end
proxy.connect()
proxy.last = computer.uptime()
return proxy
end
vt = {}
function start()
loadcfg()
for k,v in pairs(cfg.peers) do
print(string.format("Connecting to %s:%d",v.host,v.port))
v.addr = v.addr or vcomponent.uuid()
v.raddr = v.raddr or vcomponent.uuid()
local px = createTunnel(v.host, v.port, v.addr, v.raddr)
vcomponent.register(v.addr, "tunnel", px)
proxies[v.addr] = px
end
for k,v in pairs(os.tasks()) do
if os.taskInfo(v).name:match("minitel") then
os.kill(v)
end
end
end
function vt.stop()
for k,v in pairs(proxies) do
vcomponent.unregister(k)
end
end
function vt.listpeers()
for k,v in pairs(cfg.peers) do
print(string.format("#%d (%s:%d)\n Local address: %s\n Remote address: %s",k,v.host,v.port,v.addr,v.raddr))
end
end
function vt.addpeer(host,port)
port = tonumber(port) or 4096
local t = {}
t.host = host
t.port = port
t.addr = vcomponent.uuid()
t.raddr = vcomponent.uuid()
cfg.peers[#cfg.peers+1] = t
print(string.format("Added peer #%d (%s:%d) to the configuration.\nRestart to apply changes.",#cfg.peers,host,port))
savecfg()
end
function vt.delpeer(n)
n=tonumber(n)
if not n then
print("delpeer requires a number, representing the peer number, as an argument.")
return false
end
local dp = table.remove(cfg.peers, n)
savecfg()
print(string.format("Removed peer %s:%d",dp.host, dp.port))
end
function vt.settimer(time)
time = tonumber(time)
if not time then
print("Timer must be a number.")
return false
end
cfg.rtime = time
savecfg()
end
vt.start = start
_G.libs.vtunnel = vt
start()
local last = computer.uptime()
while true do
local tE = {coroutine.yield()}
if computer.uptime() > last + cfg.rtimer then
for k,v in pairs(proxies) do
v.read()
end
last = computer.uptime()
end
end