Advertisement
Tatantyler

Common Routing Framework (CRF) v1

Jan 30th, 2013
448
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 14.78 KB | None | 0 0
  1. -- Common Router Framework
  2. -- By KillaVanilla
  3.  
  4. local args = {...}
  5.  
  6. -- Arg variables
  7. local parameters = {}
  8.  
  9. for i=2, #args do
  10.     if string.sub(args[i], 1,2) == "--" then
  11.         parameters[string.sub(args[i],3)] = true
  12.     elseif string.sub(args[i], 1,1) == "-" then
  13.         local combinedParameters = string.sub(args[i],2)
  14.         if #combinedParameters == 1 then
  15.             parameters[combinedParameters] = true
  16.         elseif #combinedParameters > 1 then
  17.             for letter in string.gmatch(combinedParameters, "%a") do
  18.                 parameters[letter] = true
  19.             end
  20.         end
  21.     end
  22. end
  23.  
  24. if (parameters["h"] or parameters["help"]) or #args == 0 then
  25.     print("Common Router Framework")
  26.     print("This provides a framework for link-state routing.")
  27.     print("Usage:")
  28.     print(fs.getName(shell.getRunningProgram()).." [path/to/routing/function] [-phtfvb]")
  29.     print("Parameters:")
  30.     print("-b / --background : Disable all output.")
  31.     print("-v / --verbose : Show debug information.")
  32.     print("-f / --fake_rednet_events : Fake rednet_message events.")
  33.     print("-s / --hyperspeed : Fast Event Tick Loop")
  34.     print("-p / --show_pings : Show ping received messages.")
  35.     print("-t / --show_timeout_check : Show Timeout Checks")
  36.     print("-h / --help : Show this help screen.")
  37.     print("The -v, -p, and -t flags are all independent from one another.")
  38.     print("Passing -v automatically disables -b.")
  39.     print("Routing functions may also take parameters.")
  40.     return
  41. end
  42.  
  43. -- Set up our tables:
  44. local routerNodeGraph = {} -- Dictionary of who's connected to who. f.e. routerNodeGraph[5] = {1,2,3,4} means that router 5 is connected to routers 1, 2, 3, and 4.
  45. local localRouterNodeGraph = {} -- Table with IDs of who we're connected. f.e localRouterNodeGraph = {1,2,3,4} means that we're connected to routers 1, 2, 3, and 4 (aka we're router 5).
  46. local nodeStateAdvertList = {} -- Dictionary of the last sequence IDs from all the nodes that we know of.
  47. local routerAdvertTimestamps = {} -- Dictionary containing the timestamps of the every received node advert
  48.  
  49. -- Set up a few constants:
  50. local pingMsg = textutils.serialize({"ROUTING", "LocalPing"})
  51. local pingAckMsg = textutils.serialize({"ROUTING", "LocalPingAck"})
  52. local nodeTimeout = 60 -- [user-specified / changeable] Time To Live for each node entry in routerNodeGraph (we can't just clear the graph every minute or so, like we do with localRouterNodeGraph.)
  53. local sequenceID = 0
  54.  
  55. if parameters["s"] or parameters["hyperspeed"] then
  56.     assert(nodeTimeout > 5, "Please set nodeTimeout to something above the clock tick rate.")
  57. else
  58.     assert(nodeTimeout > 30, "Please set nodeTimeout to something above the clock tick rate.")
  59. end
  60.  
  61. local rFnHandle = fs.open(args[1], "r")
  62.  
  63. -- Our routing function:
  64. local routeFunc = loadstring(rFnHandle.readAll())
  65.  
  66. rFnHandle.close()
  67.  
  68. -- Save some functions we're going to overwrite:
  69. local oldRednetSend = rednet.send
  70. local oldRednetBroadcast = rednet.broadcast
  71. local oldRednetReceive = rednet.receive
  72.  
  73. -- Statistics:
  74. local packets_in = 0
  75. local packets_out = 0
  76. local packets_thru = 0
  77.  
  78. -- UI Stuff:
  79. local oldPrint = print
  80. local logFile = "CRF.log" -- feel free to change this
  81.  
  82.  
  83. local function refreshUI()
  84.     local x, y = term.getCursorPos()
  85.     term.setCursorPos(1,1)
  86.     term.clearLine()
  87.     term.write("In: "..packets_in.." | Thru: "..packets_thru.." | Out: "..packets_out.." | Nodes: "..#routerNodeGraph.." | Local: "..#localRouterNodeGraph)
  88.     term.setCursorPos(x,y)
  89. end
  90.  
  91.  
  92. print = function(...)
  93.     local args = {...}
  94.     local opStr = args[1]
  95.     for i=2, #args do
  96.         opStr = opStr..args[i]
  97.     end
  98.     local logHandle = fs.open(logFile, "a")
  99.     logHandle.writeLine("[Day"..os.day().."\\"..textutils.formatTime(os.time(), true).."] "..opStr)
  100.     logHandle.close()
  101.     if not (parameters["background"] or parameters["b"]) then
  102.         local x, y = term.getCursorPos()
  103.         term.clearLine()
  104.         oldPrint(opStr)
  105.         refreshUI()
  106.     end
  107. end
  108.  
  109. if parameters["v"] or parameters["verbose"] then
  110.     parameters["b"] = false
  111.     parameters["background"] = false
  112. end
  113.  
  114. if not (parameters["b"] or parameters["background"])then
  115.     print("Welcome to the Common Router Framework.")
  116.     print("Parameters selected:")
  117.     if parameters["v"] or parameters["verbose"] then
  118.         print("    Verbose")
  119.     end
  120.     if parameters["f"] or parameters["fake_rednet_events"] then
  121.         print("    Fake Rednet Events")
  122.     end
  123.     if parameters["s"] or parameters["hyperspeed"] then
  124.         print("    Hyperspeed Timer")
  125.     end
  126.     if parameters["p"] or parameters["show_pings"] then
  127.         print("    Show Ping Messages")
  128.     end
  129.     if parameters["t"] or parameters["show_timeout_check"] then
  130.         print("    Show Timeout Checks")
  131.     end
  132.     print("Using routing function: \""..args[1].."\".")
  133. else
  134.     print("Common Router Framework starting...")
  135. end
  136.  
  137. for i=1, 6 do
  138.     if peripheral.getType(rs.getSides()[i]) == "modem" or peripheral.getType(rs.getSides()[i]) == "cable" then
  139.         rednet.open(rs.getSides()[i])
  140.     end
  141. end
  142.  
  143. local function pruneDisconnectedNodes() -- run through every node's neighbors and check to see which nodes have zero neighbors (i.e they're unreachable)
  144.     if parameters["v"] or parameters["verbose"] then
  145.         print("Pruning disconnected nodes from routerNodeGraph...")
  146.     end
  147.     local references = {}
  148.     for id, neighbors in pairs(routerNodeGraph) do
  149.         if not references[id] then
  150.             references[id] = 0
  151.         end
  152.         for i, neighbor in ipairs(neighbors) do
  153.             if not references[neighbor] then
  154.                 references[neighbor] = 0
  155.             end
  156.             references[neighbor] = references[neighbor]+1
  157.         end
  158.     end
  159.     for id=1, #localRouterNodeGraph do
  160.         if not references[ localRouterNodeGraph[id] ] then
  161.             references[ localRouterNodeGraph[id] ] = 0
  162.         end
  163.         references[ localRouterNodeGraph[id] ] = references[ localRouterNodeGraph[id] ]+1
  164.     end
  165.     if parameters["v"] or parameters["verbose"] then
  166.         print("Node Reference Report:")
  167.     end
  168.     for id=0, table.maxn(routerNodeGraph) do
  169.         if references[id] then
  170.             if parameters["v"] or parameters["verbose"] then
  171.                 print("Node "..id..": "..references[id].." reference(s).")
  172.             end
  173.         end
  174.     end
  175.     for id=0, table.maxn(routerNodeGraph) do
  176.         if references[id] then
  177.             if references[id] == 0 then
  178.                 if id == os.computerID() then
  179.                     print("WARNING: This node has been cut off from a network!")
  180.                 elseif parameters["v"] or parameters["verbose"] then
  181.                     print("Pruning disconnected node: "..id)
  182.                 end
  183.                 routerNodeGraph[id] = nil
  184.             end
  185.         end
  186.     end
  187. end
  188.  
  189. local function sendNodeAdvertisement()
  190.     if parameters["v"] or parameters["verbose"] then
  191.         print("Sending node advertisements...")
  192.     end
  193.     for i=1, #localRouterNodeGraph do -- We'd probably be able to use rednet.broadcast here, but since we don't want to mess up OTHER applications...
  194.         -- if parameters["v"] or parameters["verbose"] then
  195.             -- print("Sending node advertisement to node "..localRouterNodeGraph[i])
  196.         -- end
  197.         oldRednetSend(localRouterNodeGraph[i], textutils.serialize( { "ROUTING", "NodeAdvertisement", os.computerID(), localRouterNodeGraph, {}, sequenceID } )) -- In order: Packet Identifier, Packet Type, Originating ID, Timestamp, Local Router Node Graph, recv list
  198.     end
  199.     sequenceID = sequenceID+1
  200. end
  201.  
  202. local function handleNodeAdvertisement(packet)
  203.     -- Okay, a few "ground rules" for NodeAdvertisements:
  204.     -- If we've already recieved this (aka packet[6][os.computerID()] == true), then drop it.
  205.     -- If we don't drop it, then simply set routerNodeGraph[packet[3]] = packet[5], packet[6][os.computerID()] = true, and pass it on.
  206.     if nodeStateAdvertList[packet[3]] then
  207.         if packet[6] == nodeStateAdvertList[packet[3]] then
  208.             return
  209.         end
  210.     end
  211.     if packet[5][os.computerID()] then
  212.         return
  213.     end
  214.     nodeStateAdvertList[packet[3]] = packet[6]
  215.     packet[5][os.computerID()] = true
  216.     if not (parameters["background"] or parameters["b"]) then
  217.         if parameters["verbose"] or parameters["v"] then
  218.             print("Got valid node advert from "..packet[3]..".")
  219.         end
  220.     end
  221.     routerNodeGraph[packet[3]] = packet[4]
  222.     routerAdvertTimestamps[packet[3]] = os.clock()
  223.     for i=1, #localRouterNodeGraph do
  224.         if localRouterNodeGraph[i] ~= packet[3] then
  225.             oldRednetSend(localRouterNodeGraph[i], textutils.serialize( packet ))
  226.         end
  227.     end
  228. end
  229.  
  230. local function addNodeToNodeList(node, link) -- Adds [link] to [node]'s routerNodeGraph entry, if not present already
  231.     if routerNodeGraph[node] == nil then
  232.         routerNodeGraph[node] = {}
  233.     end
  234.     for id=0, #routerNodeGraph[node] do
  235.         if routerNodeGraph[node][id] == link then
  236.             return
  237.         end
  238.     end
  239.     table.insert(routerNodeGraph[node], link)
  240.     routerNodeGraph[os.computerID()] = localRouterNodeGraph
  241. end
  242.  
  243. local function addToLocalGraph(node) -- Adds [node] to routerLocalGraph, if not present already
  244.     for id=1, #localRouterNodeGraph do
  245.         if localRouterNodeGraph[id] == node then
  246.             return
  247.         end
  248.     end
  249.     table.insert(localRouterNodeGraph,node)
  250.     routerNodeGraph[os.computerID()] = localRouterNodeGraph
  251. end
  252.  
  253. local function addNodeLinksFromRouteList(routeList)
  254.     for nodeInChain=1, #routeList, 2 do
  255.         addNodeToNodeList(routeList[nodeInChain], routeList[nodeInChain+1])
  256.         addNodeToNodeList(routeList[nodeInChain+1], routeList[nodeInChain])
  257.     end
  258. end
  259.  
  260. local function handleIncomingLoop()
  261.     while true do
  262.         routerNodeGraph[os.computerID()] = localRouterNodeGraph
  263.         local id, msg = oldRednetReceive()
  264.         msg = textutils.unserialize(msg)
  265.         if type(msg) == "table" then
  266.             if msg[1] == "ROUTING" then
  267.                 if msg[2] == "LocalPing" or msg[2] == "LocalPingAck" then
  268.                     addToLocalGraph(id)
  269.                     if msg[2] == "LocalPing" then
  270.                         if not (parameters["background"] or parameters["b"]) then
  271.                             if parameters["show_pings"] or parameters["p"] then
  272.                                 print("Got query ping from "..id..".")
  273.                             end
  274.                         end
  275.                         oldRednetSend(id, pingAckMsg)
  276.                     else
  277.                         if not (parameters["background"] or parameters["b"]) then
  278.                             if parameters["show_pings"] or parameters["p"] then
  279.                                 print("Got acknowledgement ping from "..id..".")
  280.                             end
  281.                         end
  282.                     end
  283.                 elseif msg[2] == "NodeAdvertisement" then
  284.                     handleNodeAdvertisement(msg)
  285.                 elseif msg[2] == "Data" then
  286.                     addNodeLinksFromRouteList(msg[5])
  287.                     if msg[5][#msg[5]] == os.computerID() then
  288.                         packets_in = packets_in+1
  289.                         if not (parameters["background"] or parameters["b"]) then
  290.                             print("Got packet from "..msg[5][1].." addressed to me.")
  291.                         end
  292.                         os.queueEvent("router_receive", msg[3], msg[4])
  293.                         if parameters["fake_rednet_events"] or parameters["f"] then
  294.                             os.queueEvent("rednet_receive", msg[3], msg[4], 999999999)
  295.                         end
  296.                     else
  297.                         packets_thru = packets_thru+1
  298.                         if not (parameters["background"] or parameters["b"]) then
  299.                             print("Got packet from "..msg[5][1].." addressed to "..msg[5][#msg[5]]..".")
  300.                         end
  301.                         for i=2, #msg[5]-1 do
  302.                             if msg[5][i] == os.computerID() then
  303.                                 oldRednetSend(msg[5][i+1], textutils.serialize(msg))
  304.                                 break
  305.                             end
  306.                         end
  307.                     end
  308.                 end
  309.             end
  310.         end
  311.     end
  312. end
  313.  
  314. local function TimedEventLoop()
  315.     while true do
  316.         sendNodeAdvertisement()
  317.         pruneDisconnectedNodes()
  318.         if parameters["v"] or parameters["verbose"] then
  319.             print("Starting timed event: Local Ping")
  320.         end
  321.         localRouterNodeGraph = {}
  322.         rednet.broadcast(pingMsg)
  323.         if parameters["s"] or parameters["hyperspeed"] then
  324.             os.sleep(5)
  325.         else
  326.             os.sleep(30)
  327.         end
  328.     end
  329. end
  330.  
  331. local function timestampCheckLoop()
  332.     while true do
  333.         if parameters["show_timeout_check"] or parameters["t"] then
  334.             print("Checking advertisement timestamps...")
  335.         end
  336.         for id=0, table.maxn(routerAdvertTimestamps) do
  337.             if routerAdvertTimestamps[id] then
  338.                 if (os.clock() - routerAdvertTimestamps[id]) > nodeTimeout then
  339.                     if parameters["verbose"] or parameters["v"] then
  340.                         print("Node "..id.." has timed out, pruning from graph...")
  341.                     end
  342.                     nodeStateAdvertList[id] = nil
  343.                     routerAdvertTimestamps[id] = nil
  344.                     routerNodeGraph[id] = nil
  345.                 end
  346.             end
  347.         end
  348.         os.sleep(1)
  349.     end
  350. end
  351.  
  352. local function createRestartWrapper(func, name)
  353.     return function()
  354.         while true do
  355.             local status, err = pcall(func)
  356.             if name then
  357.                 if not (parameters["background"] or parameters["bg"]) then
  358.                     print(name.." errored with message: ")
  359.                     print(err)
  360.                     print("Restarting function...")
  361.                 end
  362.             end
  363.         end
  364.     end
  365. end
  366.  
  367. local function handleRouting(id) -- Call the routeFunc with the necessary params
  368.     local route = routeFunc(id, routerNodeGraph, parameters)
  369.     -- Do some sanity tests:
  370.     if type(route) == "table" then -- is our route even a table?
  371.         if route[1] == os.computerID() and route[#route] == id then -- Does our route have valid start/end points?
  372.             return route
  373.         end
  374.     end
  375.     return
  376. end
  377.  
  378. local function sendPacket(id,data)
  379.     local route = handleRouting(id)
  380.     if not route then
  381.         return
  382.     end
  383.     packets_out = packets_out+1
  384.     return oldRednetSend(route[2], textutils.serialize({"ROUTING", "Data", os.computerID(), data, route}))
  385. end
  386.  
  387. -- Hijack rednet.send:
  388. rednet.send = function(id, msg)
  389.     if routerNodeGraph[id] then
  390.         if parameters["v"] or parameters["verbose"] then
  391.             print("Send target ("..id..") found in node graph, attempting packet send...")
  392.         end
  393.         return sendPacket(id, msg)
  394.     end
  395.     if parameters["v"] or parameters["verbose"] then
  396.         print("Send target ("..id..") not found in node graph, attempting old send...")
  397.     end
  398.     return oldRednetSend(id, msg)
  399. end
  400.  
  401. -- and rednet.receive:
  402. rednet.receive = function(timeout)
  403.     local timer = 0
  404.     if timeout then
  405.         timer = os.startTimer(timeout)
  406.     end
  407.     while true do
  408.         local event, id, msg, dist = os.pullEvent()
  409.         if timeout and (event == "timer" and id == timer) then
  410.             return
  411.         elseif event == "router_receive" then
  412.             return id, msg, 999999999
  413.         elseif event == "rednet_receive" then
  414.             local tMsg = textutils.unserialize(msg)
  415.             if type(tMsg) ~= "table" then -- don't worry about this one
  416.                 return id, msg, dist
  417.             end
  418.             if tMsg[1] ~= "ROUTING" then -- intercept routing messages
  419.                 return id, msg, dist
  420.             end
  421.         end
  422.     end
  423. end
  424.  
  425. -- add our own functions:
  426. rednet.localNodes = function()
  427.     return localRouterNodeGraph
  428. end
  429.  
  430. rednet.allNodes = function()
  431.     return routerNodeGraph
  432. end
  433.  
  434. rednet.forceNodeAdvert = function()
  435.     sendNodeAdvertisement()
  436. end
  437.  
  438. rednet.forceLocalNodeUpdate = function()
  439.     localRouterNodeGraph = {}
  440.     rednet.broadcast(pingMsg)
  441. end
  442.  
  443. rednet.forceNodePruning = function()
  444.     pruneDisconnectedNodes()
  445. end
  446.  
  447. rednet.forceTimeEventTick = function()
  448.     sendNodeAdvertisement()
  449.     pruneDisconnectedNodes()
  450.     localRouterNodeGraph = {}
  451.     rednet.broadcast(pingMsg)
  452. end
  453.  
  454. rednet.setParameter = function(parameter, state)
  455.     parameters[parameter] = state
  456.     return
  457. end
  458.  
  459. rednet.getParameter = function(parameter)
  460.     return parameters[parameter]
  461. end
  462.  
  463. rednet.packetTotals = function()
  464.     return packets_in, packets_thru, packets_out
  465. end
  466.  
  467. parallel.waitForAny(createRestartWrapper(handleIncomingLoop, "Incoming Handler Loop"), createRestartWrapper(TimedEventLoop, "30-Second Loop"), createRestartWrapper(timestampCheckLoop, "Timestamp Check Loop"), function() os.pullEventRaw("terminate") end)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement