Advertisement
Tatantyler

CRF v2

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