diff --git a/.gitignore b/.gitignore index af5c6a64..4400d04a 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ _minted-paper/ result _minted-report +.venv !report.pdf !references/*.pdf diff --git a/scripts/node-ranking/node_ranking.py b/scripts/node-ranking/node_ranking.py new file mode 100755 index 00000000..3e24434b --- /dev/null +++ b/scripts/node-ranking/node_ranking.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 + +from functools import reduce +from typing import Callable, Iterator +import sys + +import networkx as nx + +class Edge: + def __init__(self, source, destination): + self.source = source + self.destination = destination + + def __eq__(self, other): + return ( + isinstance(other, Edge) + and self.source == other.source + and self.destination == other.destination + ) + + def __hash__(self): + return hash((self.source, self.destination)) + + def __repr__(self): + return f'Edge({self.source}, {self.destination})' + +class Node: + def __init__(self, node, rank: float): + self.node = node + self.rank = rank + + def with_rank(self, new_rank: float): + return Node(self.node, new_rank) + + def __eq__(self, other): + """ + Equality only compares the `Node`, not the rank value + """ + return ( + isinstance(other, Node) + and self.node == other.node + ) + + def __hash__(self): + """ + Equality only hashes the `Node`, not the rank value + """ + return hash((self.node)) + + def __repr__(self): + return f'Node({self.node}, {self.rank})' + +def build_graph(edges: Iterator[Edge], initial_rank: float = 0.25) -> nx.DiGraph: + """ + Create a graph from an iterator over `Edge`s + """ + graph = nx.DiGraph() + for edge in edges: + source = Node(edge.source, initial_rank) + destination = Node(edge.destination, initial_rank) + graph.add_node(source) + graph.add_node(destination) + graph.add_edge(source, destination) + + return graph + +def parse_edge(line: str) -> Edge: + """ + Parse a line into an edge. The line is expected to be of the format source_ip:source_port->destination_ip:destination_port, + e.g. `1.1.1.1:1337->2.2.2.2:1337` + + :param line: (str) the line + :return: the parsed edge + """ + # remove spaces and potential leading semicolon. then split at -> + split = line.strip().strip(';').split("->") + return Edge(split[0].strip(), split[1].strip()) + +def file_loader(path: str) -> Iterator[Edge]: + """ + Load a list of edges from a file. + + :param path: (str) filepath + :return: iterator over the edges + """ + with open(path, "r") as file: + return map( + parse_edge, + # filter only edges + filter( + lambda line: '->' in line, + file.readlines() + ) + ) + +def page_ranker( + graph: nx.DiGraph, node: Node, dampingFactor: float = 1.0 +) -> float: + return ( + reduce( + lambda x, y: x + y, + map( + lambda pred: pred.rank / len(list(graph.successors(pred))), + graph.predecessors(node), + ), + 0.0, + ) + * dampingFactor + + (1 - dampingFactor) / graph.number_of_nodes() + ) + +def page_rank(graph: nx.DiGraph, dampingFactor: float = 1.0) -> nx.DiGraph: + return rank(graph, lambda g, node: page_ranker(g, node, dampingFactor)) + +def sensor_rank(graph: nx.DiGraph) -> nx.DiGraph: + number_of_nodes = graph.number_of_nodes() + return rank( + graph, + lambda g, node: (node.rank / len(list(g.successors(node)))) * (len(list(g.predecessors(node))) / number_of_nodes), + ) + + +def rank( + graph: nx.DiGraph, rank_fn: Callable[[nx.DiGraph, Node], float] +) -> nx.DiGraph: + ranked_graph = nx.DiGraph() + # number_of_nodes = graph.number_of_nodes() + for node in graph.nodes: + rank = rank_fn(graph, node) + + ranked_graph.add_node(node.with_rank(rank)) + + for u, v in graph.edges: + ranked_graph.add_edge(u, v) + + return ranked_graph + +def main(): + arg = sys.argv + if len(arg) != 2: + print(f'Usage: {arg[0]} graph.dot') + sys.exit(1) + + edges = file_loader(arg[1]) + graph = build_graph(edges) + page_ranked = page_rank(graph) + print("PageRank: ") + for node in page_ranked: + print(node) + + print() + sensor_ranked = sensor_rank(page_ranked) + print("SensorRank: ") + for node in sensor_ranked: + print(node) + + +if __name__ == '__main__': + main() diff --git a/scripts/node-ranking/requirements.txt b/scripts/node-ranking/requirements.txt new file mode 100644 index 00000000..b9b74ea3 --- /dev/null +++ b/scripts/node-ranking/requirements.txt @@ -0,0 +1 @@ +networkx==2.5