local component = require("component") local computer = require("computer") local MineOSCore = require("MineOSCore") local MineOSPaths = require("MineOSPaths") local GUI = require("GUI") local event = require("event") local fs = require("filesystem") local MineOSNetwork = {} ---------------------------------------------------------------------------------------------------------------- local filesystemProxy = component.proxy(computer.getBootAddress()) MineOSNetwork.filesystemHandles = {} MineOSNetwork.modemProxy = nil MineOSNetwork.modemPort = 1488 MineOSNetwork.modemPacketReserve = 128 MineOSNetwork.modemTimeout = 2 MineOSNetwork.internetProxy = nil MineOSNetwork.internetDelay = 0.05 MineOSNetwork.internetTimeout = 0.25 MineOSNetwork.proxySpaceUsed = 0 MineOSNetwork.proxySpaceTotal = 1073741824 MineOSNetwork.mountPaths = { modem = MineOSPaths.network .. "Modem/", FTP = MineOSPaths.network .. "FTP/" } ---------------------------------------------------------------------------------------------------------------- local function umountProxy(type) for proxy in fs.mounts() do if proxy[type] then fs.umount(proxy) end end end function MineOSNetwork.updateComponents() local modemAddress, internetAddress = component.list("modem")(), component.list("internet")() if modemAddress then MineOSNetwork.modemProxy = component.proxy(modemAddress) MineOSNetwork.modemProxy.open(MineOSNetwork.modemPort) else MineOSNetwork.modemProxy = nil MineOSNetwork.umountModems() end if internetAddress then MineOSNetwork.internetProxy = component.proxy(internetAddress) else MineOSNetwork.internetProxy = nil MineOSNetwork.umountFTPs() end end ---------------------------------------------------------------------------------------------------------------- function MineOSNetwork.umountFTPs() umountProxy("MineOSNetworkFTP") end function MineOSNetwork.getFTPProxyName(address, port, user) return user .. "@" .. address .. ":" .. port end local function FTPSocketWrite(socketHandle, data) local success, result = pcall(socketHandle.write, data .. "\r\n") if success then return true, result else return false, result end end local function FTPSocketRead(socketHandle) os.sleep(MineOSNetwork.internetDelay) local deadline, data, success, result = computer.uptime() + MineOSNetwork.internetTimeout, "" while computer.uptime() < deadline do success, result = pcall(socketHandle.read, math.huge) if success then if not result or #result == 0 then if #data > 0 then return true, data end else data, deadline = data .. result, computer.uptime() + MineOSNetwork.internetTimeout end else return false, result end end return false, "Socket read time out" end local function FTPParseLines(data) local lines = {} for line in data:gmatch("[^\r\n]+") do table.insert(lines, line) end return lines end local function FTPLogin(socketHandle, user, password) FTPSocketWrite(socketHandle, "USER " .. user) FTPSocketWrite(socketHandle, "PASS " .. password) FTPSocketWrite(socketHandle, "TYPE I") local success, result = FTPSocketRead(socketHandle) if success then if result:match("TYPE okay") then return true else return false, "Authentication failed" end else return false, result end end local function FTPEnterPassiveModeAndRunCommand(commandSocketHandle, command, dataToWrite) FTPSocketWrite(commandSocketHandle, "PASV") local success, result = FTPSocketRead(commandSocketHandle) if success then local digits = {result:match("Entering Passive Mode %((%d+),(%d+),(%d+),(%d+),(%d+),(%d+)%)")} if #digits == 6 then local address, port = table.concat(digits, ".", 1, 4), tonumber(digits[5]) * 256 + tonumber(digits[6]) FTPSocketWrite(commandSocketHandle, command) local dataSocketHandle = MineOSNetwork.internetProxy.connect(address, port) if dataToWrite then os.sleep(MineOSNetwork.internetDelay) dataSocketHandle.read(1) dataSocketHandle.write(dataToWrite) dataSocketHandle.close() return true else local success, result = FTPSocketRead(dataSocketHandle) dataSocketHandle.close() if success then return true, result else return false, result end end else return false, "Entering passive mode failed: wrong address byte array. Socket response message was: " .. tostring(result) end else return false, result end end local function FTPParseFileInfo(result) local size, year, month, day, hour, minute, sec, type, name = result:match("Size=(%d+);Modify=(%d%d%d%d)(%d%d)(%d%d)(%d%d)(%d%d)(%d%d)[^;]*;Type=([^;]+);%s([^\r\n]+)") if size then return true, name, type == "dir", tonumber(size), os.time({ year = year, day = day, month = month, hour = hour, minute = minute, sec = sec }) else return false, "File not exists" end end local function FTPFileInfo(socketHandle, path, field) FTPSocketWrite(socketHandle, "MLST " .. path) local success, result = FTPSocketRead(socketHandle) if success then local success, name, isDirectory, size, lastModified = FTPParseFileInfo(result) if success then if field == "isDirectory" then return true, isDirectory elseif field == "lastModified" then return true, lastModified else return true, size end else return true, false end else return false, result end end local function check(...) local result = {...} if not result[1] then GUI.error(table.unpack(result, 2)) end return table.unpack(result) end function MineOSNetwork.connectToFTP(address, port, user, password) if MineOSNetwork.internetProxy then local socketHandle, reason = MineOSNetwork.internetProxy.connect(address, port) if socketHandle then FTPSocketRead(socketHandle) local result, reason = FTPLogin(socketHandle, user, password) if result then local proxy, fileHandles, label = {}, {}, MineOSNetwork.getFTPProxyName(address, port, user) proxy.type = "filesystem" proxy.slot = 0 proxy.address = label proxy.MineOSNetworkFTP = true proxy.getLabel = function() return label end proxy.spaceUsed = function() return MineOSNetwork.proxySpaceUsed end proxy.spaceTotal = function() return MineOSNetwork.proxySpaceTotal end proxy.setLabel = function(text) label = text return true end proxy.isReadOnly = function() return false end proxy.closeSocketHandle = function() fs.umount(proxy) return socketHandle.close() end proxy.list = function(path) local success, result = FTPEnterPassiveModeAndRunCommand(socketHandle, "MLSD -a " .. path) if success then local list = FTPParseLines(result) for i = 1, #list do local success, name, isDirectory = FTPParseFileInfo(list[i]) if success then list[i] = name .. (isDirectory and "/" or "") end end return list else return {} end end proxy.isDirectory = function(path) local success, result = check(FTPFileInfo(socketHandle, path, "isDirectory")) if success then return result else return false end end proxy.lastModified = function(path) local success, result = check(FTPFileInfo(socketHandle, path, "lastModified")) if success then return result else return 0 end end proxy.size = function(path) local success, result = check(FTPFileInfo(socketHandle, path, "size")) if success then return result else return 0 end end proxy.exists = function(path) local success, result = check(FTPFileInfo(socketHandle, path)) if success then return result else return false end end proxy.open = function(path, mode) local temporaryPath = MineOSCore.getTemporaryPath() if mode == "r" or mode == "rb" or mode == "a" or mode == "ab" then local success, result = FTPEnterPassiveModeAndRunCommand(socketHandle, "RETR " .. path) local fileHandle = io.open(temporaryPath, "wb") fileHandle:write(success and result or "") fileHandle:close() end local fileHandle, reason = filesystemProxy.open(temporaryPath, mode) if fileHandle then fileHandles[fileHandle] = { temporaryPath = temporaryPath, path = path, needUpload = mode ~= "r" and mode ~= "rb", } end return fileHandle, reason end proxy.close = function(fileHandle) filesystemProxy.close(fileHandle) if fileHandles[fileHandle].needUpload then local file = io.open(fileHandles[fileHandle].temporaryPath, "rb") local data = file:read("*a") file:close() check(FTPEnterPassiveModeAndRunCommand(socketHandle, "STOR " .. fileHandles[fileHandle].path, data)) end fs.remove(fileHandles[fileHandle].temporaryPath) fileHandles[fileHandle] = nil end proxy.write = function(...) return filesystemProxy.write(...) end proxy.read = function(...) return filesystemProxy.read(...) end proxy.seek = function(...) return filesystemProxy.seek(...) end proxy.remove = function(path) if proxy.isDirectory(path) then local list = proxy.list(path) for i = 1, #list do proxy.remove((path .. "/" .. list[i]):gsub("/+", "/")) end FTPSocketWrite(socketHandle, "RMD " .. path) else FTPSocketWrite(socketHandle, "DELE " .. path) end end proxy.makeDirectory = function(path) FTPSocketWrite(socketHandle, "MKD " .. path) return true end proxy.rename = function(oldPath, newPath) FTPSocketWrite(socketHandle, "RNFR " .. oldPath) FTPSocketWrite(socketHandle, "RNTO " .. newPath) return true end return proxy else return false, reason end else return false, reason end else return false, "Internet component is not available" end end ---------------------------------------------------------------------------------------------------------------- function MineOSNetwork.umountModems() umountProxy("MineOSNetworkModem") end function MineOSNetwork.getModemProxyName(proxy) return proxy.name and proxy.name .. " (" .. proxy.address .. ")" or proxy.address end function MineOSNetwork.getMountedModemProxy(address) for proxy, path in fs.mounts() do if proxy.MineOSNetworkModem and proxy.address == address then return proxy end end end function MineOSNetwork.sendMessage(address, ...) if MineOSNetwork.modemProxy then return MineOSNetwork.modemProxy.send(address, MineOSNetwork.modemPort, ...) else MineOSNetwork.modemProxy = nil return false, "Modem component is not available" end end function MineOSNetwork.broadcastMessage(...) if MineOSNetwork.modemProxy then return MineOSNetwork.modemProxy.broadcast(MineOSNetwork.modemPort, ...) else MineOSNetwork.modemProxy = nil return false, "Modem component is not available" end end function MineOSNetwork.setSignalStrength(strength) if MineOSNetwork.modemProxy then if MineOSNetwork.modemProxy.isWireless() then return MineOSNetwork.modemProxy.setStrength(strength) else return false, "Modem component is not wireless" end else MineOSNetwork.modemProxy = nil return false, "Modem component is not available" end end function MineOSNetwork.broadcastComputerState(state) return MineOSNetwork.broadcastMessage("MineOSNetwork", state and "computerAvailable" or "computerNotAvailable", MineOSCore.properties.network.name) end local function newModemProxy(address) local function request(method, returnOnFailure, ...) MineOSNetwork.sendMessage(address, "MineOSNetwork", "request", method, ...) while true do local eventData = { event.pull(MineOSNetwork.modemTimeout, "modem_message") } if eventData[3] == address and eventData[6] == "MineOSNetwork" then if eventData[7] == "response" and eventData[8] == method then return table.unpack(eventData, 9) elseif eventData[7] == "accessDenied" then computer.pushSignal("MineOSNetwork", "accessDenied", address) return returnOnFailure, "Access denied" end elseif not eventData[1] then local proxy = MineOSNetwork.getMountedModemProxy(address) if proxy then fs.umount(proxy) end computer.pushSignal("MineOSNetwork", "timeout") return returnOnFailure, "Network filesystem timeout" end end end local proxy = {} proxy.type = "filesystem" proxy.address = address proxy.slot = 0 proxy.MineOSNetworkModem = true proxy.getLabel = function() return request("getLabel", "N/A") end proxy.isReadOnly = function() return request("isReadOnly", 0) end proxy.spaceUsed = function() return request("spaceUsed", MineOSNetwork.proxySpaceUsed) end proxy.spaceTotal = function() return request("spaceTotal", MineOSNetwork.proxySpaceTotal) end proxy.exists = function(path) return request("exists", false, path) end proxy.isDirectory = function(path) return request("isDirectory", false, path) end proxy.makeDirectory = function(path) return request("makeDirectory", false, path) end proxy.setLabel = function(name) return request("setLabel", false, name) end proxy.remove = function(path) return request("remove", false, path) end proxy.lastModified = function(path) return request("lastModified", 0, path) end proxy.size = function(path) return request("size", 0, path) end proxy.list = function(path) return table.fromString(request("list", "{}", path)) end proxy.open = function(path, mode) return request("open", false, path, mode) end proxy.close = function(handle) return request("close", false, handle) end proxy.seek = function(...) return request("seek", 0, ...) end proxy.read = function(...) return request("read", "", ...) end write = function(handle, data) local maxPacketSize = MineOSNetwork.modemProxy.maxPacketSize() - MineOSNetwork.modemPacketReserve repeat if not request("write", false, handle, data:sub(1, maxPacketSize)) then return false end data = data:sub(maxPacketSize + 1) until #data == 0 return true end proxy.rename = function(from, to) local proxyFrom = fs.get(from) local proxyTo = fs.get(to) if proxyFrom.MineOSNetworkModem or proxyTo.MineOSNetworkModem then local success, handleFrom, handleTo, data, reason = true handleFrom, reason = proxyFrom.open(from, "rb") if handleFrom then handleTo, reason = proxyTo.open(to, "wb") if handleTo then while true do data, readReason = proxyFrom.read(handleFrom, 1024) if data then success, reason = proxyTo.write(handleTo, data) if not success then break end else success = false break end end proxyFrom.close(handleTo) else success = false end proxyFrom.close(handleFrom) else success = false end if success then success, reason = proxyFrom.remove(from) end return success, reason else return request("rename", false, from, to) end end return proxy end local exceptionMethods = { getLabel = function() return MineOSCore.properties.network.name or MineOSNetwork.modemProxy.address end, list = function(path) return table.toString(filesystemProxy.list(path)) end, open = function(path, mode) local ID while not ID do ID = math.random(1, 0x7FFFFFFF) for handleID in pairs(MineOSNetwork.filesystemHandles) do if handleID == ID then ID = nil end end end MineOSNetwork.filesystemHandles[ID] = filesystemProxy.open(path, mode) return ID end, close = function(ID) local data, reason = filesystemProxy.close(MineOSNetwork.filesystemHandles[ID]) MineOSNetwork.filesystemHandles[ID] = nil return data, reason end, read = function(ID, ...) return filesystemProxy.read(MineOSNetwork.filesystemHandles[ID], ...) end, write = function(ID, ...) return filesystemProxy.write(MineOSNetwork.filesystemHandles[ID], ...) end, seek = function(ID, ...) return filesystemProxy.seek(MineOSNetwork.filesystemHandles[ID], ...) end, } local function handleRequest(eventData) if MineOSCore.properties.network.users[eventData[3]].allowReadAndWrite then local result = { pcall(exceptionMethods[eventData[8]] or filesystemProxy[eventData[8]], table.unpack(eventData, 9)) } MineOSNetwork.sendMessage(eventData[3], "MineOSNetwork", "response", eventData[8], table.unpack(result, result[1] and 2 or 1)) else MineOSNetwork.sendMessage(eventData[3], "MineOSNetwork", "accessDenied") end end ---------------------------------------------------------------------------------------------------------------- function MineOSNetwork.update() MineOSNetwork.umountModems() MineOSNetwork.umountFTPs() MineOSNetwork.updateComponents() MineOSNetwork.setSignalStrength(MineOSCore.properties.network.signalStrength) MineOSNetwork.broadcastComputerState(MineOSCore.properties.network.enabled) if MineOSNetwork.eventHandlerID then event.removeHandler(MineOSNetwork.eventHandlerID) end if MineOSCore.properties.network.enabled then MineOSNetwork.eventHandlerID = event.addHandler(function(...) local eventData = {...} if (eventData[1] == "component_added" or eventData[1] == "component_removed") and (eventData[3] == "modem" or eventData[3] == "internet") then MineOSNetwork.updateComponents() elseif eventData[1] == "modem_message" and MineOSCore.properties.network.enabled and eventData[6] == "MineOSNetwork" then if eventData[7] == "request" then handleRequest(eventData) elseif eventData[7] == "computerAvailable" or eventData[7] == "computerAvailableRedirect" then for proxy in fs.mounts() do if proxy.MineOSNetworkModem and proxy.address == eventData[3] then fs.umount(proxy) end end proxy = newModemProxy(eventData[3]) proxy.name = eventData[8] fs.mount(proxy, MineOSNetwork.mountPaths.modem .. eventData[3] .. "/") if eventData[7] == "computerAvailable" then MineOSNetwork.sendMessage(eventData[3], "MineOSNetwork", "computerAvailableRedirect", MineOSCore.properties.network.name) end if not MineOSCore.properties.network.users[eventData[3]] then MineOSCore.properties.network.users[eventData[3]] = {} MineOSCore.saveProperties() end computer.pushSignal("MineOSNetwork", "updateProxyList") elseif eventData[7] == "computerNotAvailable" then local proxy = MineOSNetwork.getMountedModemProxy(eventData[3]) if proxy then fs.umount(proxy) end computer.pushSignal("MineOSNetwork", "updateProxyList") end end end) end end function MineOSNetwork.disable() MineOSCore.properties.network.enabled = false MineOSCore.saveProperties() MineOSNetwork.update() end function MineOSNetwork.enable() MineOSCore.properties.network.enabled = true MineOSCore.saveProperties() MineOSNetwork.update() end ---------------------------------------------------------------------------------------------------------------- -- MineOSNetwork.updateComponents() -- local proxy, reason = MineOSNetwork.FTPProxy("localhost", 8888, "root", "1234") -- print(proxy, reason) ---------------------------------------------------------------------------------------------------------------- return MineOSNetwork