diff --git a/.gitignore b/.gitignore
index 2c7a47d..b0bf8cd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,9 @@ lib/python*
\.pydevproject
\.project
+.idea/workspace.xml
+.idea/vcs.xml
+.idea/modules.xml
+.idea/misc.xml
+.idea/encodings.xml
+.idea/CSCI_466_Programming_Assignments.iml
diff --git a/.project b/.project
new file mode 100644
index 0000000..01a42ac
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+
+
+ CSCI_466_PAs
+
+
+
+
+
+ org.python.pydev.PyDevBuilder
+
+
+
+
+
+ org.python.pydev.pythonNature
+
+
diff --git a/.pydevproject b/.pydevproject
new file mode 100644
index 0000000..2899bac
--- /dev/null
+++ b/.pydevproject
@@ -0,0 +1,8 @@
+
+
+
+/${PROJECT_DIR_NAME}
+
+python 3.6
+python37
+
diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..179bf68
--- /dev/null
+++ b/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+eclipse.preferences.version=1
+encoding/network.py=UTF-8
+encoding/network_1.py=UTF-8
diff --git a/images/Drawing2.vsdx b/images/Drawing2.vsdx
new file mode 100644
index 0000000..ada3628
Binary files /dev/null and b/images/Drawing2.vsdx differ
diff --git a/images/network.png b/images/network.png
new file mode 100644
index 0000000..43e9cbc
Binary files /dev/null and b/images/network.png differ
diff --git a/link.py b/link_1.py
similarity index 100%
rename from link.py
rename to link_1.py
diff --git a/link_2.py b/link_2.py
new file mode 100644
index 0000000..e488016
--- /dev/null
+++ b/link_2.py
@@ -0,0 +1,75 @@
+import queue
+import threading
+
+## An abstraction of a link between router interfaces
+class Link:
+
+ ## creates a link between two objects by looking up and linking node interfaces.
+ # @param node_1: node from which data will be transfered
+ # @param node_1_intf: number of the interface on that node
+ # @param node_2: node to which data will be transfered
+ # @param node_2_intf: number of the interface on that node
+ def __init__(self, node_1, node_1_intf, node_2, node_2_intf):
+ self.node_1 = node_1
+ self.node_1_intf = node_1_intf
+ self.node_2 = node_2
+ self.node_2_intf = node_2_intf
+ print('Created link %s' % self.__str__())
+
+ ## called when printing the object
+ def __str__(self):
+ return 'Link %s-%d - %s-%d' % (self.node_1, self.node_1_intf, self.node_2, self.node_2_intf)
+
+ ##transmit a packet between interfaces in each direction
+ def tx_pkt(self):
+ for (node_a, node_a_intf, node_b, node_b_intf) in \
+ [(self.node_1, self.node_1_intf, self.node_2, self.node_2_intf),
+ (self.node_2, self.node_2_intf, self.node_1, self.node_1_intf)]:
+ intf_a = node_a.intf_L[node_a_intf]
+ intf_b = node_b.intf_L[node_b_intf]
+ pkt_S = intf_a.get('out')
+ if pkt_S is None:
+ continue #continue if no packet to transfer
+ #otherwise transmit the packet
+ try:
+ intf_b.put(pkt_S, 'in')
+ print('%s: direction %s-%s -> %s-%s: transmitting packet "%s"' % \
+ (self, node_a, node_a_intf, node_b, node_b_intf, pkt_S))
+ except queue.Full:
+ print('%s: direction %s-%s -> %s-%s: packet lost' % \
+ (self, node_a, node_a_intf, node_b, node_b_intf))
+ pass
+
+
+## An abstraction of the link layer
+class LinkLayer:
+
+ def __init__(self):
+ ## list of links in the network
+ self.link_L = []
+ self.stop = False #for thread termination
+
+ ## called when printing the object
+ def __str__(self):
+ return 'Network'
+
+ ##add a Link to the network
+ def add_link(self, link):
+ self.link_L.append(link)
+
+ ##transfer a packet across all links
+ def transfer(self):
+ for link in self.link_L:
+ link.tx_pkt()
+
+ ## thread target for the network to keep transmitting data across links
+ def run(self):
+ print (threading.currentThread().getName() + ': Starting')
+ while True:
+ #transfer one packet on all the links
+ self.transfer()
+ #terminate
+ if self.stop:
+ print (threading.currentThread().getName() + ': Ending')
+ return
+
\ No newline at end of file
diff --git a/link_3.py b/link_3.py
new file mode 100644
index 0000000..e488016
--- /dev/null
+++ b/link_3.py
@@ -0,0 +1,75 @@
+import queue
+import threading
+
+## An abstraction of a link between router interfaces
+class Link:
+
+ ## creates a link between two objects by looking up and linking node interfaces.
+ # @param node_1: node from which data will be transfered
+ # @param node_1_intf: number of the interface on that node
+ # @param node_2: node to which data will be transfered
+ # @param node_2_intf: number of the interface on that node
+ def __init__(self, node_1, node_1_intf, node_2, node_2_intf):
+ self.node_1 = node_1
+ self.node_1_intf = node_1_intf
+ self.node_2 = node_2
+ self.node_2_intf = node_2_intf
+ print('Created link %s' % self.__str__())
+
+ ## called when printing the object
+ def __str__(self):
+ return 'Link %s-%d - %s-%d' % (self.node_1, self.node_1_intf, self.node_2, self.node_2_intf)
+
+ ##transmit a packet between interfaces in each direction
+ def tx_pkt(self):
+ for (node_a, node_a_intf, node_b, node_b_intf) in \
+ [(self.node_1, self.node_1_intf, self.node_2, self.node_2_intf),
+ (self.node_2, self.node_2_intf, self.node_1, self.node_1_intf)]:
+ intf_a = node_a.intf_L[node_a_intf]
+ intf_b = node_b.intf_L[node_b_intf]
+ pkt_S = intf_a.get('out')
+ if pkt_S is None:
+ continue #continue if no packet to transfer
+ #otherwise transmit the packet
+ try:
+ intf_b.put(pkt_S, 'in')
+ print('%s: direction %s-%s -> %s-%s: transmitting packet "%s"' % \
+ (self, node_a, node_a_intf, node_b, node_b_intf, pkt_S))
+ except queue.Full:
+ print('%s: direction %s-%s -> %s-%s: packet lost' % \
+ (self, node_a, node_a_intf, node_b, node_b_intf))
+ pass
+
+
+## An abstraction of the link layer
+class LinkLayer:
+
+ def __init__(self):
+ ## list of links in the network
+ self.link_L = []
+ self.stop = False #for thread termination
+
+ ## called when printing the object
+ def __str__(self):
+ return 'Network'
+
+ ##add a Link to the network
+ def add_link(self, link):
+ self.link_L.append(link)
+
+ ##transfer a packet across all links
+ def transfer(self):
+ for link in self.link_L:
+ link.tx_pkt()
+
+ ## thread target for the network to keep transmitting data across links
+ def run(self):
+ print (threading.currentThread().getName() + ': Starting')
+ while True:
+ #transfer one packet on all the links
+ self.transfer()
+ #terminate
+ if self.stop:
+ print (threading.currentThread().getName() + ': Ending')
+ return
+
\ No newline at end of file
diff --git a/network.py b/network.py
deleted file mode 100644
index 0bb95f0..0000000
--- a/network.py
+++ /dev/null
@@ -1,223 +0,0 @@
-import queue
-import threading
-
-
-## wrapper class for a queue of packets
-class Interface:
- ## @param maxsize - the maximum size of the queue storing packets
- def __init__(self, maxsize=0):
- self.in_queue = queue.Queue(maxsize)
- self.out_queue = queue.Queue(maxsize)
-
- ##get packet from the queue interface
- # @param in_or_out - use 'in' or 'out' interface
- def get(self, in_or_out):
- try:
- if in_or_out == 'in':
- pkt_S = self.in_queue.get(False)
- # if pkt_S is not None:
- # print('getting packet from the IN queue')
- return pkt_S
- else:
- pkt_S = self.out_queue.get(False)
- # if pkt_S is not None:
- # print('getting packet from the OUT queue')
- return pkt_S
- except queue.Empty:
- return None
-
- ##put the packet into the interface queue
- # @param pkt - Packet to be inserted into the queue
- # @param in_or_out - use 'in' or 'out' interface
- # @param block - if True, block until room in queue, if False may throw queue.Full exception
- def put(self, pkt, in_or_out, block=False):
- if in_or_out == 'out':
- # print('putting packet in the OUT queue')
- self.out_queue.put(pkt, block)
- else:
- # print('putting packet in the IN queue')
- self.in_queue.put(pkt, block)
-
-
-## Implements a network layer packet.
-class NetworkPacket:
- ## packet encoding lengths
- dst_S_length = 5
- prot_S_length = 1
-
- ##@param dst: address of the destination host
- # @param data_S: packet payload
- # @param prot_S: upper layer protocol for the packet (data, or control)
- def __init__(self, dst, prot_S, data_S):
- self.dst = dst
- self.data_S = data_S
- self.prot_S = prot_S
-
- ## called when printing the object
- def __str__(self):
- return self.to_byte_S()
-
- ## convert packet to a byte string for transmission over links
- def to_byte_S(self):
- byte_S = str(self.dst).zfill(self.dst_S_length)
- if self.prot_S == 'data':
- byte_S += '1'
- elif self.prot_S == 'control':
- byte_S += '2'
- else:
- raise('%s: unknown prot_S option: %s' %(self, self.prot_S))
- byte_S += self.data_S
- return byte_S
-
- ## extract a packet object from a byte string
- # @param byte_S: byte string representation of the packet
- @classmethod
- def from_byte_S(self, byte_S):
- dst = byte_S[0 : NetworkPacket.dst_S_length].strip('0')
- prot_S = byte_S[NetworkPacket.dst_S_length : NetworkPacket.dst_S_length + NetworkPacket.prot_S_length]
- if prot_S == '1':
- prot_S = 'data'
- elif prot_S == '2':
- prot_S = 'control'
- else:
- raise('%s: unknown prot_S field: %s' %(self, prot_S))
- data_S = byte_S[NetworkPacket.dst_S_length + NetworkPacket.prot_S_length : ]
- return self(dst, prot_S, data_S)
-
-
-
-
-## Implements a network host for receiving and transmitting data
-class Host:
-
- ##@param addr: address of this node represented as an integer
- def __init__(self, addr):
- self.addr = addr
- self.intf_L = [Interface()]
- self.stop = False #for thread termination
-
- ## called when printing the object
- def __str__(self):
- return self.addr
-
- ## create a packet and enqueue for transmission
- # @param dst: destination address for the packet
- # @param data_S: data being transmitted to the network layer
- def udt_send(self, dst, data_S):
- p = NetworkPacket(dst, 'data', data_S)
- print('%s: sending packet "%s"' % (self, p))
- self.intf_L[0].put(p.to_byte_S(), 'out') #send packets always enqueued successfully
-
- ## receive packet from the network layer
- def udt_receive(self):
- pkt_S = self.intf_L[0].get('in')
- if pkt_S is not None:
- print('%s: received packet "%s"' % (self, pkt_S))
-
- ## thread target for the host to keep receiving data
- def run(self):
- print (threading.currentThread().getName() + ': Starting')
- while True:
- #receive data arriving to the in interface
- self.udt_receive()
- #terminate
- if(self.stop):
- print (threading.currentThread().getName() + ': Ending')
- return
-
-
-
-## Implements a multi-interface router
-class Router:
-
- ##@param name: friendly router name for debugging
- # @param cost_D: cost table to neighbors {neighbor: {interface: cost}}
- # @param max_queue_size: max queue length (passed to Interface)
- def __init__(self, name, cost_D, max_queue_size):
- self.stop = False #for thread termination
- self.name = name
- #create a list of interfaces
- self.intf_L = [Interface(max_queue_size) for _ in range(len(cost_D))]
- #save neighbors and interfeces on which we connect to them
- self.cost_D = cost_D # {neighbor: {interface: cost}}
- #TODO: set up the routing table for connected hosts
- self.rt_tbl_D = {} # {destination: {router: cost}}
- print('%s: Initialized routing table' % self)
- self.print_routes()
-
-
- ## Print routing table
- def print_routes(self):
- #TODO: print the routes as a two dimensional table
- print(self.rt_tbl_D)
-
-
- ## called when printing the object
- def __str__(self):
- return self.name
-
-
- ## look through the content of incoming interfaces and
- # process data and control packets
- def process_queues(self):
- for i in range(len(self.intf_L)):
- pkt_S = None
- #get packet from interface i
- pkt_S = self.intf_L[i].get('in')
- #if packet exists make a forwarding decision
- if pkt_S is not None:
- p = NetworkPacket.from_byte_S(pkt_S) #parse a packet out
- if p.prot_S == 'data':
- self.forward_packet(p,i)
- elif p.prot_S == 'control':
- self.update_routes(p, i)
- else:
- raise Exception('%s: Unknown packet type in packet %s' % (self, p))
-
-
- ## forward the packet according to the routing table
- # @param p Packet to forward
- # @param i Incoming interface number for packet p
- def forward_packet(self, p, i):
- try:
- # TODO: Here you will need to implement a lookup into the
- # forwarding table to find the appropriate outgoing interface
- # for now we assume the outgoing interface is 1
- self.intf_L[1].put(p.to_byte_S(), 'out', True)
- print('%s: forwarding packet "%s" from interface %d to %d' % \
- (self, p, i, 1))
- except queue.Full:
- print('%s: packet "%s" lost on interface %d' % (self, p, i))
- pass
-
-
- ## send out route update
- # @param i Interface number on which to send out a routing update
- def send_routes(self, i):
- # TODO: Send out a routing table update
- #create a routing table update packet
- p = NetworkPacket(0, 'control', 'DUMMY_ROUTING_TABLE')
- try:
- print('%s: sending routing update "%s" from interface %d' % (self, p, i))
- self.intf_L[i].put(p.to_byte_S(), 'out', True)
- except queue.Full:
- print('%s: packet "%s" lost on interface %d' % (self, p, i))
- pass
-
-
- ## forward the packet according to the routing table
- # @param p Packet containing routing information
- def update_routes(self, p, i):
- #TODO: add logic to update the routing tables and
- # possibly send out routing updates
- print('%s: Received routing update %s from interface %d' % (self, p, i))
-
-
- ## thread target for the host to keep forwarding data
- def run(self):
- print (threading.currentThread().getName() + ': Starting')
- while True:
- self.process_queues()
- if self.stop:
- print (threading.currentThread().getName() + ': Ending')
- return
\ No newline at end of file
diff --git a/network.pyc b/network.pyc
new file mode 100644
index 0000000..9681158
Binary files /dev/null and b/network.pyc differ
diff --git a/network_1.py b/network_1.py
new file mode 100644
index 0000000..5c5f9b7
--- /dev/null
+++ b/network_1.py
@@ -0,0 +1,374 @@
+import queue
+import threading
+from collections import defaultdict
+import sys
+import math
+
+
+# wrapper class for a queue of packets
+class Interface:
+ # @param maxsize - the maximum size of the queue storing packets
+ def __init__(self, maxsize=0):
+ self.in_queue = queue.Queue(maxsize)
+ self.out_queue = queue.Queue(maxsize)
+
+ # get packet from the queue interface
+ # @param in_or_out - use 'in' or 'out' interface
+ def get(self, in_or_out):
+ try:
+ if in_or_out == 'in':
+ pkt_S = self.in_queue.get(False)
+ # if pkt_S is not None:
+ # print('getting packet from the IN queue')
+ return pkt_S
+ else:
+ pkt_S = self.out_queue.get(False)
+ # if pkt_S is not None:
+ # print('getting packet from the OUT queue')
+ return pkt_S
+ except queue.Empty:
+ return None
+
+ # put the packet into the interface queue
+ # @param pkt - Packet to be inserted into the queue
+ # @param in_or_out - use 'in' or 'out' interface
+ # @param block - if True, block until room in queue, if False may throw queue.Full exception
+ def put(self, pkt, in_or_out, block=False):
+ if in_or_out == 'out':
+ # print('putting packet in the OUT queue')
+ self.out_queue.put(pkt, block)
+ else:
+ # print('putting packet in the IN queue')
+ self.in_queue.put(pkt, block)
+
+
+# Implements a network layer packet.
+class NetworkPacket:
+ # packet encoding lengths
+ dst_S_length = 5
+ prot_S_length = 1
+
+ ##@param dst: address of the destination host
+ # @param data_S: packet payload
+ # @param prot_S: upper layer protocol for the packet (data, or control)
+ def __init__(self, dst, prot_S, data_S):
+ self.dst = dst
+ self.data_S = data_S
+ self.prot_S = prot_S
+
+ # called when printing the object
+ def __str__(self):
+ return self.to_byte_S()
+
+ # convert packet to a byte string for transmission over links
+ def to_byte_S(self):
+ byte_S = str(self.dst).zfill(self.dst_S_length)
+ if self.prot_S == 'data':
+ byte_S += '1'
+ elif self.prot_S == 'control':
+ byte_S += '2'
+ else:
+ raise ('%s: unknown prot_S option: %s' % (self, self.prot_S))
+ byte_S += self.data_S
+ return byte_S
+
+ ## extract a packet object from a byte string
+ # @param byte_S: byte string representation of the packet
+ @classmethod
+ def from_byte_S(self, byte_S):
+ dst = byte_S[0: NetworkPacket.dst_S_length].strip('0')
+ prot_S = byte_S[NetworkPacket.dst_S_length: NetworkPacket.dst_S_length + NetworkPacket.prot_S_length]
+ if prot_S == '1':
+ prot_S = 'data'
+ elif prot_S == '2':
+ prot_S = 'control'
+ else:
+ raise ('%s: unknown prot_S field: %s' % (self, prot_S))
+ data_S = byte_S[NetworkPacket.dst_S_length + NetworkPacket.prot_S_length:]
+ return self(dst, prot_S, data_S)
+
+
+## Implements a network host for receiving and transmitting data
+class Host:
+
+ ##@param addr: address of this node represented as an integer
+ def __init__(self, addr):
+ self.addr = addr
+ self.intf_L = [Interface()]
+ self.stop = False # for thread termination
+
+ ## called when printing the object
+ def __str__(self):
+ return self.addr
+
+ ## create a packet and enqueue for transmission
+ # @param dst: destination address for the packet
+ # @param data_S: data being transmitted to the network layer
+ def udt_send(self, dst, data_S):
+ p = NetworkPacket(dst, 'data', data_S)
+ print('%s: sending packet "%s"' % (self, p))
+ self.intf_L[0].put(p.to_byte_S(), 'out') # send packets always enqueued successfully
+
+ ## receive packet from the network layer
+ def udt_receive(self):
+ pkt_S = self.intf_L[0].get('in')
+ if pkt_S is not None:
+ print('%s: received packet "%s"' % (self, pkt_S))
+
+ ## thread target for the host to keep receiving data
+ def run(self):
+ print(threading.currentThread().getName() + ': Starting')
+ while True:
+ # receive data arriving to the in interface
+ self.udt_receive()
+ # terminate
+ if (self.stop):
+ print(threading.currentThread().getName() + ': Ending')
+ return
+
+
+## Implements a multi-interface router
+class Router:
+
+ ##@param name: friendly router name for debugging
+ # @param cost_D: cost table to neighbors {neighbor: {interface: cost}}
+ # @param max_queue_size: max queue length (passed to Interface)
+ def __init__(self, name, cost_D, max_queue_size):
+ self.stop = False # for thread termination
+ self.name = name
+ # create a list of interfaces
+ self.intf_L = [Interface(max_queue_size) for _ in range(len(cost_D))]
+ # save neighbors and interfeces on which we connect to them
+ self.cost_D = cost_D # cost_D {neighbor: {interface: cost}}
+ # TODO: set up the routing table for connected hosts
+ # {destination: {router: cost}} ##Initial setup
+ self.rt_tbl_D = {name: {name: 0}}
+ keys = list(cost_D.keys())
+ values = list(cost_D.values())
+ for i in range(len(keys)):
+ self.rt_tbl_D[keys[i]] = {name: list(values[i].values())[0]}
+ self.print_routes()
+ print('%s: Initialized routing table' % self)
+
+ def getCurrentRoutingTable(self):
+ routingTableString = self.name + "-"
+ values = list(self.rt_tbl_D.values())
+ keys = list(self.rt_tbl_D.keys())
+ first = True
+ for i in range(len(keys)):
+ if first:
+ first = False
+ routingTableString += keys[i] + "," + str(list(values[i])[0]) + "," + str(list(values[i].values())[0])
+ else:
+ routingTableString += ":" + keys[i] + "," + str(list(values[i])[0]) + "," + str(
+ list(values[i].values())[0])
+ print(routingTableString)
+ return routingTableString
+
+ ## Print routing table
+ def updateUniqueRouters(self):
+ self.uniqueRouters = []
+ values = self.rt_tbl_D.values()
+ routers = {}
+ for i in range(len(values)):
+ if list(list(values)[i].keys())[0] not in routers:
+ routers[list(list(values)[i].keys())[0]] = ""
+ for item in routers:
+ self.uniqueRouters.append(item)
+
+ def print_routes(self):
+ keys = self.rt_tbl_D.keys()
+ values = self.rt_tbl_D.values()
+ columns = len(keys) + 1
+ keyString = ""
+ topTableString = "╒"
+ headerBottomTableString = "╞"
+ tableRowSeperator = "├"
+ tableBottom = "╘"
+ # //Setting up table
+ for i in range(columns):
+ if i + 1 != columns:
+ topTableString += "══════╤"
+ headerBottomTableString += "══════╪"
+ tableRowSeperator += "──────┼"
+ tableBottom += "══════╧"
+ else:
+ topTableString += "══════╕\n"
+ headerBottomTableString += "══════╡\n"
+ tableRowSeperator += "──────┤\n"
+ tableBottom += "══════╛\n"
+ itemSpace = " "
+ for item in keys:
+ keyString += " " + item + " │"
+ costRows = []
+ changed = []
+ self.updateUniqueRouters()
+ for item in self.uniqueRouters:
+ costRows.append("│ " + item + " │")
+ for i in range(len(values)):
+ changedFlag = False
+ for j in range(len(costRows)):
+ for k in range(len(list(values)[i].keys())):
+ if list(list(values)[i].keys())[k] == self.uniqueRouters[j]:
+ formattedVal = itemSpace[0:len(itemSpace) - len(str(list(list(values)[i].values())[k]))] + str(
+ list(list(values)[i].values())[k])
+ costRows[j] += formattedVal + "│"
+ changed.append(j)
+ changedFlag = True
+ if changedFlag:
+ changedFlag = False
+ for l in range(len(costRows)):
+ if (l in changed):
+ continue
+ else:
+ costRows[l] += " │"
+ changed = []
+
+ sys.stdout.write(topTableString + "│ " + self.name + " │" + keyString + "\n" + headerBottomTableString)
+ for i in range(len(costRows)):
+ if i + 1 != len(costRows):
+ sys.stdout.write(costRows[i] + "\n" + tableRowSeperator)
+ else:
+ sys.stdout.write(costRows[i] + "\n")
+ sys.stdout.write(tableBottom)
+
+ ## called when printing the object
+ def __str__(self):
+ return self.name
+
+ ## look through the content of incoming interfaces and
+ # process data and control packets
+ def process_queues(self):
+ for i in range(len(self.intf_L)):
+ pkt_S = None
+ # get packet from interface i
+ pkt_S = self.intf_L[i].get('in')
+ # if packet exists make a forwarding decision
+ if pkt_S is not None:
+ p = NetworkPacket.from_byte_S(pkt_S) # parse a packet out
+ if p.prot_S == 'data':
+ self.forward_packet(p, i)
+ elif p.prot_S == 'control':
+ self.update_routes(p, i)
+ else:
+ raise Exception('%s: Unknown packet type in packet %s' % (self, p))
+
+ ## forward the packet according to the routing table
+ # @param p Packet to forward
+ # @param i Incoming interface number for packet p
+ def forward_packet(self, p, i):
+ try:
+ # TODO: Here you will need to implement a lookup into the
+ # forwarding table to find the appropriate outgoing interface
+ # for now we assume the outgoing interface is 1
+ self.intf_L[1].put(p.to_byte_S(), 'out', True) # send out
+ print("Index out of range, {}".format(1))
+ print('%s: forwarding packet "%s" from interface %d to %d' % \
+ (self, p, i, 1))
+ except queue.Full:
+ print('%s: packet "%s" lost on interface %d' % (self, p, i))
+ pass
+
+ ## send out route update
+ # @param i Interface number on which to send out a routing update
+ def send_routes(self, i):
+ # TODO: Send out a routing table update
+ # create a routing table update packet
+ p = NetworkPacket(0, 'control', self.getCurrentRoutingTable())
+ try:
+ print('%s: sending routing update "%s" from interface %d' % (self, p, i))
+ self.intf_L[i].put(p.to_byte_S(), 'out', True)
+ except queue.Full:
+ print('%s: packet "%s" lost on interface %d' % (self, p, i))
+ pass
+
+ ## forward the packet according to the routing table
+ # @param p Packet containing routing information
+ def update_routes(self, p, i):
+ # TODO: add logic to update the routing tables and
+ # possibly send out routing updates
+ updates = p.to_byte_S()[6:].split('-')
+ name = updates[0]
+ update = updates[1].split(":")
+ # Raw updating
+ for j in update: # for each update
+ items = j.split(",") # items: 0=dest 1 1=dest 2 2=cost between dest 1 and dest 2
+ if items[0] in self.rt_tbl_D: # if dest 1 is in table headers
+ values = list(self.rt_tbl_D.values()) # values is a list of dicts of form {router: cost}
+ exists = False # assume that it doesn't exist
+ # already in table
+ for i in range(len(values)): # for as many values(which are mappings of dests to routers)
+ vks = list(values[i].keys()) # vks = list of routers in
+ for vk in vks: # for each router in the router list,
+ if vk == items[1]: # if the router is dest 2
+ self.rt_tbl_D[items[0]][items[1]] = items[2] # set the cost of dest 1 to dest 2 in the table to the cost in items
+ self.rt_tbl_D[items[1]][items[0]] = items[2] # set the cost of dest 1 to dest 2 in the table to the cost in items
+ # do stuff/compare
+ exists = True
+ if not exists: # will always default to this
+ self.rt_tbl_D[items[0]][items[1]] = items[
+ 2] # set the cost of dest 1 to dest 2 in the table to the cost in items
+ self.rt_tbl_D[items[1]][items[0]] = items[
+ 2] # set the cost of dest 1 to dest 2 in the table to the cost in items
+ else:
+ self.rt_tbl_D[items[0]] = {items[1]: items[2]}
+
+ '''for header in self.rt_tbl_D: #see if header is missing routers in its dict
+ for router in self.uniqueRouters: #for each router,
+ if router not in self.rt_tbl_D[header]: #if the router is NOT in the dict of the header
+ #put it in the header's dict, set cost to inf
+ self.rt_tbl_D[header][router] = 999 #basically infinity, right?
+ self.rt_tbl_D[router][header] = 999'''
+
+ self.updateUniqueRouters()
+ # run the algorithm on each router in the table
+ router_count = len(self.uniqueRouters)
+ print(router_count)
+ for j in range(router_count): # for every router (row) in the network,
+ # step 1: set all unknowns to infinity
+ for header in self.rt_tbl_D:
+ # print("Detecting gaps for {} to {}".format(header,self.uniqueRouters[j]))
+ if self.uniqueRouters[j] not in self.rt_tbl_D[header]: # if the router is NOT in the dict of the header
+ # print("Gap filled {} to {}".format(header, self.uniqueRouters[j]))
+ # put it in the header's dict, set cost to inf
+ self.rt_tbl_D[header][self.uniqueRouters[j]] = 999 # basically infinity, right?
+ # {header: {router: cost}}
+ # bellman ford starts here
+ i = 1
+ # http://courses.csail.mit.edu/6.006/spring11/lectures/lec15.pdf
+ # rt_tbl is a list of edges.
+ updated = False
+ # step 2: relax edges |V|-1 times
+ for i in range(len(self.rt_tbl_D)):
+ # for V-1 (the number of verticies minus one
+ for u in self.rt_tbl_D:
+ # relax edge, represented as a call with the header
+ # for each vertex's neighbor,
+ for v in self.rt_tbl_D[u]: # iterate through each outgoing edge
+ edge_distance = int(self.rt_tbl_D[u][v])
+ u_dist = int(self.rt_tbl_D[u][self.uniqueRouters[j]]) # distance to u vertex
+ v_dist = int(self.rt_tbl_D[v][self.uniqueRouters[j]]) # distance to v vertex
+ try:
+ if (u_dist > (v_dist + edge_distance)):
+ # if the edge plus the distance to vertex v is greater than the distance to u
+ self.rt_tbl_D[u][
+ self.uniqueRouters[j]] = v_dist + edge_distance # update the distance to u
+ updated = True
+ self.updateUniqueRouters()
+ except KeyError:
+ print("Key error exception occurred")
+ if updated:
+ # cost_D {neighbor: {interface: cost}}
+ for i in range(len(self.cost_D.values())): # for all values
+ for x in range(len(list(self.cost_D.values())[i].keys())):
+ interface = list(list(self.cost_D.values())[i].keys())[x]
+ self.send_routes(interface)
+
+ ## thread target for the host to keep forwarding data
+ def run(self):
+ print(threading.currentThread().getName() + ': Starting')
+ while True:
+ self.process_queues()
+ if self.stop:
+ print(threading.currentThread().getName() + ': Ending')
+ return
diff --git a/network_2.py b/network_2.py
new file mode 100644
index 0000000..fc683ae
--- /dev/null
+++ b/network_2.py
@@ -0,0 +1,420 @@
+import queue
+import threading
+from collections import defaultdict
+import sys
+import math
+
+
+## wrapper class for a queue of packets
+class Interface:
+ ## @param maxsize - the maximum size of the queue storing packets
+ def __init__(self, maxsize=0):
+ self.in_queue = queue.Queue(maxsize)
+ self.out_queue = queue.Queue(maxsize)
+
+ ##get packet from the queue interface
+ # @param in_or_out - use 'in' or 'out' interface
+ def get(self, in_or_out):
+ try:
+ if in_or_out == 'in':
+ pkt_S = self.in_queue.get(False)
+ # if pkt_S is not None:
+ # print('getting packet from the IN queue')
+ return pkt_S
+ else:
+ pkt_S = self.out_queue.get(False)
+ # if pkt_S is not None:
+ # print('getting packet from the OUT queue')
+ return pkt_S
+ except queue.Empty:
+ return None
+
+ ##put the packet into the interface queue
+ # @param pkt - Packet to be inserted into the queue
+ # @param in_or_out - use 'in' or 'out' interface
+ # @param block - if True, block until room in queue, if False may throw queue.Full exception
+ def put(self, pkt, in_or_out, block=False):
+ if in_or_out == 'out':
+ # print('putting packet in the OUT queue')
+ self.out_queue.put(pkt, block)
+ else:
+ # print('putting packet in the IN queue')
+ self.in_queue.put(pkt, block)
+
+
+## Implements a network layer packet.
+class NetworkPacket:
+ ## packet encoding lengths
+ dst_S_length = 5
+ prot_S_length = 1
+
+ ##@param dst: address of the destination host
+ # @param data_S: packet payload
+ # @param prot_S: upper layer protocol for the packet (data, or control)
+ def __init__(self, dst, prot_S, data_S):
+ self.dst = dst
+ self.data_S = data_S
+ self.prot_S = prot_S
+
+ ## called when printing the object
+ def __str__(self):
+ return self.to_byte_S()
+
+ ## convert packet to a byte string for transmission over links
+ def to_byte_S(self):
+ byte_S = str(self.dst).zfill(self.dst_S_length)
+ if self.prot_S == 'data':
+ byte_S += '1'
+ elif self.prot_S == 'control':
+ byte_S += '2'
+ else:
+ raise ('%s: unknown prot_S option: %s' % (self, self.prot_S))
+ byte_S += self.data_S
+ return byte_S
+
+ ## extract a packet object from a byte string
+ # @param byte_S: byte string representation of the packet
+ @classmethod
+ def from_byte_S(self, byte_S):
+ dst = byte_S[0: NetworkPacket.dst_S_length].strip('0')
+ prot_S = byte_S[NetworkPacket.dst_S_length: NetworkPacket.dst_S_length + NetworkPacket.prot_S_length]
+ if prot_S == '1':
+ prot_S = 'data'
+ elif prot_S == '2':
+ prot_S = 'control'
+ else:
+ raise ('%s: unknown prot_S field: %s' % (self, prot_S))
+ data_S = byte_S[NetworkPacket.dst_S_length + NetworkPacket.prot_S_length:]
+ return self(dst, prot_S, data_S)
+
+
+## Implements a network host for receiving and transmitting data
+class Host:
+
+ ##@param addr: address of this node represented as an integer
+ def __init__(self, addr):
+ self.addr = addr
+ self.intf_L = [Interface()]
+ self.stop = False # for thread termination
+
+ ## called when printing the object
+ def __str__(self):
+ return self.addr
+
+ ## create a packet and enqueue for transmission
+ # @param dst: destination address for the packet
+ # @param data_S: data being transmitted to the network layer
+ def udt_send(self, dst, data_S):
+ p = NetworkPacket(dst, 'data', data_S)
+ print('%s: sending packet "%s"' % (self, p))
+ self.intf_L[0].put(p.to_byte_S(), 'out') # send packets always enqueued successfully
+
+ ## receive packet from the network layer
+ def udt_receive(self):
+ pkt_S = self.intf_L[0].get('in')
+ if pkt_S is not None:
+ print('%s: received packet "%s"' % (self, pkt_S))
+
+ ## thread target for the host to keep receiving data
+ def run(self):
+ print(threading.currentThread().getName() + ': Starting')
+ while True:
+ # receive data arriving to the in interface
+ self.udt_receive()
+ # terminate
+ if (self.stop):
+ print(threading.currentThread().getName() + ': Ending')
+ return
+
+
+## Implements a multi-interface router
+class Router:
+
+ ##@param name: friendly router name for debugging
+ # @param cost_D: cost table to neighbors {neighbor: {interface: cost}}
+ # @param max_queue_size: max queue length (passed to Interface)
+ def __init__(self, name, cost_D, max_queue_size):
+ self.stop = False # for thread termination
+ self.name = name
+ # create a list of interfaces
+ self.intf_L = [Interface(max_queue_size) for _ in range(len(cost_D))]
+ # save neighbors and interfeces on which we connect to them
+ self.cost_D = cost_D # cost_D {neighbor: {interface: cost}}
+ # TODO: set up the routing table for connected hosts
+
+ # {destination: {router: cost}} ##Initial setup
+ self.rt_tbl_D = {name: {name: 0}}
+ keys = list(cost_D.keys())
+ values = list(cost_D.values())
+ for i in range(len(keys)):
+ self.rt_tbl_D[keys[i]] = {name: list(values[i].values())[0]}
+ self.print_routes()
+ print('%s: Initialized routing table' % self)
+
+ def getCurrentRoutingTable(self):
+ routingTableString = self.name + "-"
+ values = list(self.rt_tbl_D.values())
+ keys = list(self.rt_tbl_D.keys())
+ first = True
+ for i in range(len(keys)):
+ if first:
+ first = False
+ routingTableString += keys[i] + "," + str(list(values[i])[0]) + "," + str(list(values[i].values())[0])
+ else:
+ routingTableString += ":" + keys[i] + "," + str(list(values[i])[0]) + "," + str(
+ list(values[i].values())[0])
+ print(routingTableString)
+ return routingTableString
+
+ ## Print routing table
+ def updateUniqueRouters(self):
+ self.uniqueRouters = []
+ values = self.rt_tbl_D.values()
+ routers = {}
+ for i in range(len(values)):
+ if list(list(values)[i].keys())[0] not in routers:
+ routers[list(list(values)[i].keys())[0]] = ""
+ for item in routers:
+ self.uniqueRouters.append(item)
+
+ def print_routes(self):
+ keys = self.rt_tbl_D.keys()
+ values = self.rt_tbl_D.values()
+ columns = len(keys) + 1
+ keyString = ""
+ topTableString = "╒"
+ headerBottomTableString = "╞"
+ tableRowSeperator = "├"
+ tableBottom = "╘"
+ # //Setting up table
+ for i in range(columns):
+ if (i + 1 != columns):
+ topTableString += "══════╤"
+ headerBottomTableString += "══════╪"
+ tableRowSeperator += "──────┼"
+ tableBottom += "══════╧"
+ else:
+ topTableString += "══════╕\n"
+ headerBottomTableString += "══════╡\n"
+ tableRowSeperator += "──────┤\n"
+ tableBottom += "══════╛\n"
+ itemSpace = " "
+ for item in keys:
+ keyString += " " + item + " │"
+ costRows = []
+ changed = []
+ self.updateUniqueRouters()
+ for item in self.uniqueRouters:
+ costRows.append("│ " + item + " │")
+ for i in range(len(values)):
+ changedFlag = False
+ for j in range(len(costRows)):
+ for k in range(len(list(values)[i].keys())):
+ if list(list(values)[i].keys())[k] == self.uniqueRouters[j]:
+ formattedVal = itemSpace[0:len(itemSpace) - len(str(list(list(values)[i].values())[k]))] + str(
+ list(list(values)[i].values())[k])
+ costRows[j] += formattedVal + "│"
+ changed.append(j)
+ changedFlag = True
+ if changedFlag:
+ changedFlag = False
+ for l in range(len(costRows)):
+ if (l in changed):
+ continue
+ else:
+ costRows[l] += " │"
+ changed = []
+
+ sys.stdout.write(topTableString + "│ " + self.name + " │" + keyString + "\n" + headerBottomTableString)
+ for i in range(len(costRows)):
+ if i + 1 != len(costRows):
+ sys.stdout.write(costRows[i] + "\n" + tableRowSeperator)
+ else:
+ sys.stdout.write(costRows[i] + "\n")
+ sys.stdout.write(tableBottom)
+
+ ## called when printing the object
+ def __str__(self):
+ return self.name
+
+ ## look through the content of incoming interfaces and
+ # process data and control packets
+ def process_queues(self):
+ for i in range(len(self.intf_L)):
+ pkt_S = None
+ # get packet from interface i
+ pkt_S = self.intf_L[i].get('in')
+ # if packet exists make a forwarding decision
+ if pkt_S is not None:
+ p = NetworkPacket.from_byte_S(pkt_S) # parse a packet out
+ if p.prot_S == 'data':
+ self.forward_packet(p, i)
+ elif p.prot_S == 'control':
+ self.update_routes(p, i)
+ else:
+ raise Exception('%s: Unknown packet type in packet %s' % (self, p))
+
+ ## forward the packet according to the routing table
+ # @param p Packet to forward
+ # @param i Incoming interface number for packet p
+ def forward_packet(self, p, i):
+ try:
+ # TODO: Here you will need to implement a lookup into the
+ # forwarding table to find the appropriate outgoing interface
+ # for now we assume the outgoing interface is 1
+
+ # we know the length of the shortest path
+ # we know how many edges and verticies there are
+ # we don't know what the shortest path is... like how is the program going to trace the path??
+ # simple: we use the bellman ford equation as a verification instead of an algorithm
+
+ # first, let's make it easy.
+ dest = p.dst
+
+ # then we'll set aside some variable for the node to forward to, let's call it v
+ v_d = 999 # distance to v
+ v = dest
+
+ # cost_D {neighbor: {interface: cost}}
+ # okay, so now we know where we're going.
+ for header in self.rt_tbl_D:
+ # for every node in the routing table,
+ if header in self.cost_D: # narrow it down to only neighbors
+ # header is in routing table and is reachable by the node
+ dest_d = int(self.rt_tbl_D[dest][self.name]) # distance to the destination
+ node_d = int(self.rt_tbl_D[header][self.name]) # distance to potential outgoing node
+ try:
+ node_dest_d = int(self.rt_tbl_D[header][dest]) # distance from the potential outgoing node to the destination
+ if v_d > (node_d + node_dest_d): # find the minimum
+ # new minimum
+ v_d = node_d
+ v = header
+ except KeyError:
+ continue;
+ #print("Key Error: Neighbor is likely host")
+ # new addition
+ chosenVal = 999
+ chosenRoute = ""
+ if v not in self.cost_D: # if v is NOT a neighbor
+ for value in self.rt_tbl_D[v]: # iterate through values
+ cost = self.rt_tbl_D[v][value] # get cost in routing table
+ if int(cost) < int(chosenVal): # find lowest cost router
+ chosenRoute = value
+ chosenVal = cost
+ for key in self.cost_D[chosenRoute]: # set the chosenRoutes interface
+ out_intf = key # set the outgoing interface to the result.
+ else: # is a neighbor
+ # @param cost_D: cost table to neighbors {neighbor: {interface: cost}}
+ for key in self.cost_D[v]: # iterate through values
+ out_intf = key # set the outgoing interface to the result.
+ try:
+ self.intf_L[out_intf].put(p.to_byte_S(), 'out', True) # send out
+ except IndexError:
+ print("Index out of range, %i" % out_intf)
+ print('%s: forwarding packet "%s" from interface %d to %d' % \
+ (self, p, i, 1))
+ except queue.Full:
+ print('%s: packet "%s" lost on interface %d' % (self, p, i))
+ pass
+
+ ## send out route update
+ # @param i Interface number on which to send out a routing update
+ def send_routes(self, i):
+ # TODO: Send out a routing table update
+ # create a routing table update packet
+ p = NetworkPacket(0, 'control', self.getCurrentRoutingTable())
+ try:
+ print('%s: sending routing update "%s" from interface %d' % (self, p, i))
+ self.intf_L[i].put(p.to_byte_S(), 'out', True)
+ except queue.Full:
+ print('%s: packet "%s" lost on interface %d' % (self, p, i))
+ pass
+
+ ## forward the packet according to the routing table
+ # @param p Packet containing routing information
+ def update_routes(self, p, i):
+ # TODO: add logic to update the routing tables and
+ # possibly send out routing updates
+ updates = p.to_byte_S()[6:].split('-')
+ name = updates[0]
+ update = updates[1].split(":")
+ # Raw updating
+ for j in update: # for each update
+ items = j.split(",") # items: 0=dest 1 1=dest 2 2=cost between dest 1 and dest 2
+ if items[0] in self.rt_tbl_D: # if dest 1 is in table headers
+ values = list(self.rt_tbl_D.values()) # values is a list of dicts of form {router: cost}
+ exists = False # assume that it doesn't exist
+ # already in table
+ for i in range(len(values)): # for as many values(which are mappings of dests to routers)
+ vks = list(values[i].keys()) # vks = list of routers in
+ for vk in vks: # for each router in the router list,
+ if vk == items[1]: # if the router is dest 2
+ self.rt_tbl_D[items[0]][items[1]] = items[
+ 2] # set the cost of dest 1 to dest 2 in the table to the cost in items
+ # do stuff/compare
+ exists = True
+ if not exists: # will always default to this
+ self.rt_tbl_D[items[0]][items[1]] = items[
+ 2] # set the cost of dest 1 to dest 2 in the table to the cost in items
+ else:
+ self.rt_tbl_D[items[0]] = {items[1]: items[2]}
+
+ '''for header in self.rt_tbl_D: #see if header is missing routers in its dict
+ for router in self.uniqueRouters: #for each router,
+ if router not in self.rt_tbl_D[header]: #if the router is NOT in the dict of the header
+ #put it in the header's dict, set cost to inf
+ self.rt_tbl_D[header][router] = 999 #basically infinity, right?
+ self.rt_tbl_D[router][header] = 999'''
+
+ self.updateUniqueRouters()
+ # run the algorithm on each router in the table
+ router_count = len(self.uniqueRouters)
+ for j in range(router_count): # for every router (row) in the network,
+ # step 1: set all unknowns to infinity
+ for header in self.rt_tbl_D:
+ # print("Detecting gaps for {} to {}".format(header,self.uniqueRouters[j]))
+ if self.uniqueRouters[j] not in self.rt_tbl_D[header]: # if the router is NOT in the dict of the header
+ # print("Gap filled {} to {}".format(header, self.uniqueRouters[j]))
+ # put it in the header's dict, set cost to inf
+ self.rt_tbl_D[header][self.uniqueRouters[j]] = 999 # basically infinity, right?
+ # {header: {router: cost}}
+ # bellman ford starts here
+ i = 1
+ # http://courses.csail.mit.edu/6.006/spring11/lectures/lec15.pdf
+ # rt_tbl is a list of edges.
+ updated = False
+ # step 2: relax edges |V|-1 times
+ for i in range(len(self.rt_tbl_D)):
+ # for V-1 (the number of verticies minus one
+ for u in self.rt_tbl_D:
+ # relax edge, represented as a call with the header
+ # for each vertex's neighbor,
+ for v in self.rt_tbl_D[u]: # iterate through each outgoing edge
+ edge_distance = int(self.rt_tbl_D[u][v])
+ u_dist = int(self.rt_tbl_D[u][self.uniqueRouters[j]]) # distance to u vertex
+ v_dist = int(self.rt_tbl_D[v][self.uniqueRouters[j]]) # distance to v vertex
+ try:
+ if (u_dist > (v_dist + edge_distance)):
+ # if the edge plus the distance to vertex v is greater than the distance to u
+ self.rt_tbl_D[u][
+ self.uniqueRouters[j]] = v_dist + edge_distance # update the distance to u
+ updated = True
+ self.updateUniqueRouters()
+ except KeyError:
+ continue
+ #print("Key error exception occurred")
+ if (updated):
+ # cost_D {neighbor: {interface: cost}}
+ for i in range(len(self.cost_D.values())): # for all values
+ for x in range(len(list(self.cost_D.values())[i].keys())):
+ interface = list(list(self.cost_D.values())[i].keys())[x]
+ self.send_routes(interface)
+
+ ## thread target for the host to keep forwarding data
+ def run(self):
+ print(threading.currentThread().getName() + ': Starting')
+ while True:
+ self.process_queues()
+ if self.stop:
+ print(threading.currentThread().getName() + ': Ending')
+ return
diff --git a/network_3.py b/network_3.py
new file mode 100644
index 0000000..1c3468a
--- /dev/null
+++ b/network_3.py
@@ -0,0 +1,419 @@
+import queue
+import threading
+from collections import defaultdict
+import sys
+import math
+
+
+## wrapper class for a queue of packets
+class Interface:
+ ## @param maxsize - the maximum size of the queue storing packets
+ def __init__(self, maxsize=0):
+ self.in_queue = queue.Queue(maxsize)
+ self.out_queue = queue.Queue(maxsize)
+
+ ##get packet from the queue interface
+ # @param in_or_out - use 'in' or 'out' interface
+ def get(self, in_or_out):
+ try:
+ if in_or_out == 'in':
+ pkt_S = self.in_queue.get(False)
+ # if pkt_S is not None:
+ # print('getting packet from the IN queue')
+ return pkt_S
+ else:
+ pkt_S = self.out_queue.get(False)
+ # if pkt_S is not None:
+ # print('getting packet from the OUT queue')
+ return pkt_S
+ except queue.Empty:
+ return None
+
+ ##put the packet into the interface queue
+ # @param pkt - Packet to be inserted into the queue
+ # @param in_or_out - use 'in' or 'out' interface
+ # @param block - if True, block until room in queue, if False may throw queue.Full exception
+ def put(self, pkt, in_or_out, block=False):
+ if in_or_out == 'out':
+ # print('putting packet in the OUT queue')
+ self.out_queue.put(pkt, block)
+ else:
+ # print('putting packet in the IN queue')
+ self.in_queue.put(pkt, block)
+
+
+## Implements a network layer packet.
+class NetworkPacket:
+ ## packet encoding lengths
+ dst_S_length = 5
+ prot_S_length = 1
+
+ ##@param dst: address of the destination host
+ # @param data_S: packet payload
+ # @param prot_S: upper layer protocol for the packet (data, or control)
+ def __init__(self, dst, prot_S, data_S):
+ self.dst = dst
+ self.data_S = data_S
+ self.prot_S = prot_S
+
+ ## called when printing the object
+ def __str__(self):
+ return self.to_byte_S()
+
+ ## convert packet to a byte string for transmission over links
+ def to_byte_S(self):
+ byte_S = str(self.dst).zfill(self.dst_S_length)
+ if self.prot_S == 'data':
+ byte_S += '1'
+ elif self.prot_S == 'control':
+ byte_S += '2'
+ else:
+ raise ('%s: unknown prot_S option: %s' % (self, self.prot_S))
+ byte_S += self.data_S
+ return byte_S
+
+ ## extract a packet object from a byte string
+ # @param byte_S: byte string representation of the packet
+ @classmethod
+ def from_byte_S(self, byte_S):
+ dst = byte_S[0: NetworkPacket.dst_S_length].strip('0')
+ prot_S = byte_S[NetworkPacket.dst_S_length: NetworkPacket.dst_S_length + NetworkPacket.prot_S_length]
+ if prot_S == '1':
+ prot_S = 'data'
+ elif prot_S == '2':
+ prot_S = 'control'
+ else:
+ raise ('%s: unknown prot_S field: %s' % (self, prot_S))
+ data_S = byte_S[NetworkPacket.dst_S_length + NetworkPacket.prot_S_length:]
+ return self(dst, prot_S, data_S)
+
+
+## Implements a network host for receiving and transmitting data
+class Host:
+
+ ##@param addr: address of this node represented as an integer
+ def __init__(self, addr):
+ self.addr = addr
+ self.intf_L = [Interface()]
+ self.stop = False # for thread termination
+
+ ## called when printing the object
+ def __str__(self):
+ return self.addr
+
+ ## create a packet and enqueue for transmission
+ # @param dst: destination address for the packet
+ # @param data_S: data being transmitted to the network layer
+ def udt_send(self, dst, data_S):
+ p = NetworkPacket(dst, 'data', data_S)
+ print('%s: sending packet "%s"' % (self, p))
+ self.intf_L[0].put(p.to_byte_S(), 'out') # send packets always enqueued successfully
+
+ ## receive packet from the network layer
+ def udt_receive(self):
+ pkt_S = self.intf_L[0].get('in')
+ if pkt_S is not None:
+ print('%s: received packet "%s"' % (self, pkt_S))
+
+ ## thread target for the host to keep receiving data
+ def run(self):
+ print(threading.currentThread().getName() + ': Starting')
+ while True:
+ # receive data arriving to the in interface
+ self.udt_receive()
+ # terminate
+ if (self.stop):
+ print(threading.currentThread().getName() + ': Ending')
+ return
+
+
+## Implements a multi-interface router
+class Router:
+
+ ##@param name: friendly router name for debugging
+ # @param cost_D: cost table to neighbors {neighbor: {interface: cost}}
+ # @param max_queue_size: max queue length (passed to Interface)
+ def __init__(self, name, cost_D, max_queue_size):
+ self.stop = False # for thread termination
+ self.name = name
+ # create a list of interfaces
+ self.intf_L = [Interface(max_queue_size) for _ in range(len(cost_D))]
+ # save neighbors and interfeces on which we connect to them
+ self.cost_D = cost_D # cost_D {neighbor: {interface: cost}}
+ # TODO: set up the routing table for connected hosts
+
+ # {destination: {router: cost}} ##Initial setup
+ self.rt_tbl_D = {name: {name: 0}}
+ keys = list(cost_D.keys())
+ values = list(cost_D.values())
+ for i in range(len(keys)):
+ self.rt_tbl_D[keys[i]] = {name: list(values[i].values())[0]}
+ self.print_routes()
+ print('%s: Initialized routing table' % self)
+
+ def getCurrentRoutingTable(self):
+ routingTableString = self.name + "-"
+ values = list(self.rt_tbl_D.values())
+ keys = list(self.rt_tbl_D.keys())
+ first = True
+ for i in range(len(keys)):
+ if first:
+ first = False
+ routingTableString += keys[i] + "," + str(list(values[i])[0]) + "," + str(list(values[i].values())[0])
+ else:
+ routingTableString += ":" + keys[i] + "," + str(list(values[i])[0]) + "," + str(
+ list(values[i].values())[0])
+ print(routingTableString)
+ return routingTableString
+
+ ## Print routing table
+ def updateUniqueRouters(self):
+ self.uniqueRouters = []
+ values = self.rt_tbl_D.values()
+ routers = {}
+ for i in range(len(values)):
+ if list(list(values)[i].keys())[0] not in routers:
+ routers[list(list(values)[i].keys())[0]] = ""
+ for item in routers:
+ self.uniqueRouters.append(item)
+
+ def print_routes(self):
+ keys = self.rt_tbl_D.keys()
+ values = self.rt_tbl_D.values()
+ columns = len(keys) + 1
+ keyString = ""
+ topTableString = "╒"
+ headerBottomTableString = "╞"
+ tableRowSeperator = "├"
+ tableBottom = "╘"
+ # //Setting up table
+ for i in range(columns):
+ if (i + 1 != columns):
+ topTableString += "══════╤"
+ headerBottomTableString += "══════╪"
+ tableRowSeperator += "──────┼"
+ tableBottom += "══════╧"
+ else:
+ topTableString += "══════╕\n"
+ headerBottomTableString += "══════╡\n"
+ tableRowSeperator += "──────┤\n"
+ tableBottom += "══════╛\n"
+ itemSpace = " "
+ for item in keys:
+ keyString += " " + item + " │"
+ costRows = []
+ changed = []
+ self.updateUniqueRouters()
+ for item in self.uniqueRouters:
+ costRows.append("│ " + item + " │")
+ for i in range(len(values)):
+ changedFlag = False
+ for j in range(len(costRows)):
+ for k in range(len(list(values)[i].keys())):
+ if list(list(values)[i].keys())[k] == self.uniqueRouters[j]:
+ formattedVal = itemSpace[0:len(itemSpace) - len(str(list(list(values)[i].values())[k]))] + str(
+ list(list(values)[i].values())[k])
+ costRows[j] += formattedVal + "│"
+ changed.append(j)
+ changedFlag = True
+ if changedFlag:
+ changedFlag = False
+ for l in range(len(costRows)):
+ if (l in changed):
+ continue
+ else:
+ costRows[l] += " │"
+ changed = []
+
+ sys.stdout.write(topTableString + "│ " + self.name + " │" + keyString + "\n" + headerBottomTableString)
+ for i in range(len(costRows)):
+ if i + 1 != len(costRows):
+ sys.stdout.write(costRows[i] + "\n" + tableRowSeperator)
+ else:
+ sys.stdout.write(costRows[i] + "\n")
+ sys.stdout.write(tableBottom)
+
+ ## called when printing the object
+ def __str__(self):
+ return self.name
+
+ ## look through the content of incoming interfaces and
+ # process data and control packets
+ def process_queues(self):
+ for i in range(len(self.intf_L)):
+ pkt_S = None
+ # get packet from interface i
+ pkt_S = self.intf_L[i].get('in')
+ # if packet exists make a forwarding decision
+ if pkt_S is not None:
+ p = NetworkPacket.from_byte_S(pkt_S) # parse a packet out
+ if p.prot_S == 'data':
+ self.forward_packet(p, i)
+ elif p.prot_S == 'control':
+ self.update_routes(p, i)
+ else:
+ raise Exception('%s: Unknown packet type in packet %s' % (self, p))
+
+ ## forward the packet according to the routing table
+ # @param p Packet to forward
+ # @param i Incoming interface number for packet p
+ def forward_packet(self, p, i):
+ try:
+ # TODO: Here you will need to implement a lookup into the
+ # forwarding table to find the appropriate outgoing interface
+ # for now we assume the outgoing interface is 1
+
+ # we know the length of the shortest path
+ # we know how many edges and verticies there are
+ # we don't know what the shortest path is... like how is the program going to trace the path??
+ # simple: we use the bellman ford equation as a verification instead of an algorithm
+
+ # first, let's make it easy.
+ dest = p.dst
+
+ # then we'll set aside some variable for the node to forward to, let's call it v
+ v_d = 999 # distance to v
+ v = dest
+ # cost_D {neighbor: {interface: cost}}
+ # okay, so now we know where we're going.
+ for header in self.rt_tbl_D:
+ # for every node in the routing table,
+ if header in self.cost_D: # narrow it down to only neighbors
+ # header is in routing table and is reachable by the node
+ dest_d = int(self.rt_tbl_D[dest][self.name]) # distance to the destination
+ node_d = int(self.rt_tbl_D[header][self.name]) # distance to potential outgoing node
+ try:
+ if v_d > (dest_d + node_d): # find the minimum
+ # new minimum
+ v_d = dest_d
+ v = header
+ except KeyError:
+ print("Key Error: Neighbor is likely host")
+ # new addition
+ chosenVal = 999
+ chosenRoute = ""
+ if v not in self.cost_D: # if v is NOT a neighbor
+ for value in self.rt_tbl_D[v]: # iterate through values
+ cost = self.rt_tbl_D[v][value] # get cost in routing table
+ if int(cost) < int(chosenVal): # find lowest cost router
+ chosenRoute = value
+ chosenVal = cost
+ for key in self.cost_D[chosenRoute]: # set the chosenRoutes interface
+ out_intf = key # set the outgoing interface to the result.
+ else: # is a neighbor
+ # @param cost_D: cost table to neighbors {neighbor: {interface: cost}}
+ for key in self.cost_D[v]: # iterate through values
+ out_intf = key # set the outgoing interface to the result.
+ try:
+ self.intf_L[out_intf].put(p.to_byte_S(), 'out', True) # send out
+ except IndexError:
+ print("Index out of range, %i" % out_intf)
+ print('%s: forwarding packet "%s" from interface %d to %d' % \
+ (self, p, i, 1))
+ except queue.Full:
+ print('%s: packet "%s" lost on interface %d' % (self, p, i))
+ pass
+
+ ## send out route update
+ # @param i Interface number on which to send out a routing update
+ def send_routes(self, i):
+ # TODO: Send out a routing table update
+ # create a routing table update packet
+ p = NetworkPacket(0, 'control', self.getCurrentRoutingTable())
+ try:
+ print('%s: sending routing update "%s" from interface %d' % (self, p, i))
+ self.intf_L[i].put(p.to_byte_S(), 'out', True)
+ except queue.Full:
+ print('%s: packet "%s" lost on interface %d' % (self, p, i))
+ pass
+
+ ## forward the packet according to the routing table
+ # @param p Packet containing routing information
+ def update_routes(self, p, i):
+ # TODO: add logic to update the routing tables and
+ # possibly send out routing updates
+ updates = p.to_byte_S()[6:].split('-')
+ name = updates[0]
+ update = updates[1].split(":")
+ updated = False
+ # Raw updating
+ for j in update: # for each update
+ items = j.split(",") # items: 0=dest 1 1=dest 2 2=cost between dest 1 and dest 2
+ if items[0] in self.rt_tbl_D: # if dest 1 is in table headers
+ values = list(self.rt_tbl_D.values()) # values is a list of dicts of form {router: cost}
+ exists = False # assume that it doesn't exist
+ # already in table
+ for i in range(len(values)): # for as many values(which are mappings of dests to routers)
+ vks = list(values[i].keys()) # vks = list of routers in
+ for vk in vks: # for each router in the router list,
+ if vk == items[1]: # if the router is dest 2
+ self.rt_tbl_D[items[0]][items[1]] = items[2] # set the cost of dest 1 to dest 2 in the table to the cost in items
+ # do stuff/compare
+ exists = True
+
+ if not exists: # will always default to this
+ self.rt_tbl_D[items[0]][items[1]] = items[2] # set the cost of dest 1 to dest 2 in the table to the cost in items
+ updated = True
+ else:
+ self.rt_tbl_D[items[0]] = {items[1]: items[2]}
+ updated = True
+
+ '''for header in self.rt_tbl_D: #see if header is missing routers in its dict
+ for router in self.uniqueRouters: #for each router,
+ if router not in self.rt_tbl_D[header]: #if the router is NOT in the dict of the header
+ #put it in the header's dict, set cost to inf
+ self.rt_tbl_D[header][router] = 999 #basically infinity, right?
+ self.rt_tbl_D[router][header] = 999'''
+
+ self.updateUniqueRouters()
+ # run the algorithm on each router in the table
+ router_count = len(self.uniqueRouters)
+ print(router_count)
+ for j in range(router_count): # for every router (row) in the network,
+ # step 1: set all unknowns to infinity
+ for header in self.rt_tbl_D:
+ # print("Detecting gaps for {} to {}".format(header,self.uniqueRouters[j]))
+ if self.uniqueRouters[j] not in self.rt_tbl_D[header]: # if the router is NOT in the dict of the header
+ # print("Gap filled {} to {}".format(header, self.uniqueRouters[j]))
+ # put it in the header's dict, set cost to inf
+ self.rt_tbl_D[header][self.uniqueRouters[j]] = 999 # basically infinity, right?
+ # {header: {router: cost}}
+ # bellman ford starts here
+ # http://courses.csail.mit.edu/6.006/spring11/lectures/lec15.pdf
+ # rt_tbl is a list of edges.
+ self.updateUniqueRouters()
+ # step 2: relax edges |V|-1 times
+ for i in range(len(self.rt_tbl_D)-1):
+ # for V-1 (the number of verticies minus one
+ for u in self.rt_tbl_D:
+ # relax edge, represented as a call with the header
+ # for each vertex's neighbor,
+ for v in self.rt_tbl_D[u]: # iterate through each outgoing edge
+ edge_distance = int(self.rt_tbl_D[u][v])
+ u_dist = int(self.rt_tbl_D[u][self.uniqueRouters[j]]) # distance to u vertex
+ v_dist = int(self.rt_tbl_D[v][self.uniqueRouters[j]]) # distance to v vertex
+ try:
+ if (u_dist > (v_dist + edge_distance)):
+ # if the edge plus the distance to vertex v is greater than the distance to u
+ self.rt_tbl_D[u][
+ self.uniqueRouters[j]] = v_dist + edge_distance # update the distance to u
+ updated = True
+ self.updateUniqueRouters()
+ except KeyError:
+ print("Key error exception occurred")
+ if (updated):
+ # cost_D {neighbor: {interface: cost}}
+ for i in range(len(self.cost_D.values())): # for all values
+ for x in range(len(list(self.cost_D.values())[i].keys())):
+ interface = list(list(self.cost_D.values())[i].keys())[x]
+ self.send_routes(interface)
+ self.updateUniqueRouters()
+
+ ## thread target for the host to keep forwarding data
+ def run(self):
+ print(threading.currentThread().getName() + ': Starting')
+ while True:
+ self.process_queues()
+ if self.stop:
+ print(threading.currentThread().getName() + ': Ending')
+ return
diff --git a/partners.txt b/partners.txt
new file mode 100644
index 0000000..632a8a7
--- /dev/null
+++ b/partners.txt
@@ -0,0 +1,2 @@
+Kendal Black
+Keefer Sands
\ No newline at end of file
diff --git a/simulation.py b/simulation.py
deleted file mode 100644
index b9c412b..0000000
--- a/simulation.py
+++ /dev/null
@@ -1,71 +0,0 @@
-import network
-import link
-import threading
-from time import sleep
-import sys
-
-##configuration parameters
-router_queue_size = 0 #0 means unlimited
-simulation_time = 1 #give the network sufficient time to execute transfers
-
-if __name__ == '__main__':
- object_L = [] #keeps track of objects, so we can kill their threads at the end
-
- #create network hosts
- host_1 = network.Host('H1')
- object_L.append(host_1)
- host_2 = network.Host('H2')
- object_L.append(host_2)
-
- #create routers and cost tables for reaching neighbors
- cost_D = {'H1': {0: 1}, 'RB': {1: 1}} # {neighbor: {interface: cost}}
- router_a = network.Router(name='RA',
- cost_D = cost_D,
- max_queue_size=router_queue_size)
- object_L.append(router_a)
-
- cost_D = {'H2': {1: 3}, 'RA': {0: 1}} # {neighbor: {interface: cost}}
- router_b = network.Router(name='RB',
- cost_D = cost_D,
- max_queue_size=router_queue_size)
- object_L.append(router_b)
-
- #create a Link Layer to keep track of links between network nodes
- link_layer = link.LinkLayer()
- object_L.append(link_layer)
-
- #add all the links - need to reflect the connectivity in cost_D tables above
- link_layer.add_link(link.Link(host_1, 0, router_a, 0))
- link_layer.add_link(link.Link(router_a, 1, router_b, 0))
- link_layer.add_link(link.Link(router_b, 1, host_2, 0))
-
-
- #start all the objects
- thread_L = []
- for obj in object_L:
- thread_L.append(threading.Thread(name=obj.__str__(), target=obj.run))
-
- for t in thread_L:
- t.start()
-
- ## compute routing tables
- router_a.send_routes(1) #one update starts the routing process
- sleep(simulation_time) #let the tables converge
- print("Converged routing tables")
- for obj in object_L:
- if str(type(obj)) == "":
- obj.print_routes()
-
- #send packet from host 1 to host 2
- host_1.udt_send('H2', 'MESSAGE_FROM_H1')
- sleep(simulation_time)
-
-
- #join all threads
- for o in object_L:
- o.stop = True
- for t in thread_L:
- t.join()
-
- print("All simulation threads joined")
-
diff --git a/simulation_1.py b/simulation_1.py
new file mode 100644
index 0000000..a73d9dc
--- /dev/null
+++ b/simulation_1.py
@@ -0,0 +1,64 @@
+import network_1
+import link_1
+import threading
+from time import sleep
+import sys
+
+# configuration parameters
+router_queue_size = 0 # 0 means unlimited
+simulation_time = 7 # give the network_1 sufficient time to execute transfers
+if __name__ == '__main__':
+ object_L = [] # keeps track of objects, so we can kill their threads at the end
+ # create network_1 hosts
+ host_1 = network_1.Host('H1')
+ object_L.append(host_1)
+ host_2 = network_1.Host('H2')
+ object_L.append(host_2)
+ # create routers and cost tables for reaching neighbors
+ cost_D = {'H1': {0: 1}, 'RB': {1: 1}} # {neighbor: {interface: cost}}
+ router_a = network_1.Router(name='RA',
+ cost_D=cost_D,
+ max_queue_size=router_queue_size)
+ object_L.append(router_a)
+ cost_D = {'H2': {1: 3}, 'RA': {0: 1}} # {neighbor: {interface: cost}}
+ router_b = network_1.Router(name='RB',
+ cost_D=cost_D,
+ max_queue_size=router_queue_size)
+ object_L.append(router_b)
+ # create a Link Layer to keep track of links between network_1 nodes
+ link_layer = link_1.LinkLayer()
+ object_L.append(link_layer)
+
+ # add all the links - need to reflect the connectivity in cost_D tables above
+ link_layer.add_link(link_1.Link(host_1, 0, router_a, 0))
+ link_layer.add_link(link_1.Link(router_a, 1, router_b, 0))
+ link_layer.add_link(link_1.Link(router_b, 1, host_2, 0))
+
+ # start all the objects
+ thread_L = []
+ for obj in object_L:
+ thread_L.append(threading.Thread(name=obj.__str__(), target=obj.run))
+ sys.stdout.write("\n")
+ for t in thread_L:
+ t.start()
+
+ # compute routing tables
+ router_a.send_routes(1) # one update starts the routing process
+ sleep(simulation_time) # let the tables converge
+ print("Converged routing tables")
+ # Table Header Bottom
+ for i in range(len(object_L)):
+ if str(type(object_L[i])) == "":
+ object_L[i].print_routes()
+
+ # send packet from host 1 to host 2
+ host_1.udt_send('H2', 'MESSAGE_FROM_H1')
+ sleep(simulation_time)
+
+ # join all threads
+ for o in object_L:
+ o.stop = True
+ for t in thread_L:
+ t.join()
+
+ print("All simulation threads joined")
diff --git a/simulation_2.py b/simulation_2.py
new file mode 100644
index 0000000..4e90d46
--- /dev/null
+++ b/simulation_2.py
@@ -0,0 +1,67 @@
+import network_2
+import link_2
+import threading
+from time import sleep
+import sys
+
+# configuration parameters
+router_queue_size = 0 # 0 means unlimited
+simulation_time = 5 # give the network_2 sufficient time to execute transfers
+if __name__ == '__main__':
+ object_L = [] # keeps track of objects, so we can kill their threads at the end
+ # create network_2 hosts
+ host_1 = network_2.Host('H1')
+ object_L.append(host_1)
+ host_2 = network_2.Host('H2')
+ object_L.append(host_2)
+ # create routers and cost tables for reaching neighbors
+ cost_D = {'H1': {0: 1}, 'RB': {1: 1}} # {neighbor: {interface: cost}}
+ router_a = network_2.Router(name='RA',
+ cost_D=cost_D,
+ max_queue_size=router_queue_size)
+ object_L.append(router_a)
+ cost_D = {'H2': {1: 3}, 'RA': {0: 1}} # {neighbor: {interface: cost}}
+ router_b = network_2.Router(name='RB',
+ cost_D=cost_D,
+ max_queue_size=router_queue_size)
+ object_L.append(router_b)
+ # create a link_2 Layer to keep track of link_2s between network_2 nodes
+ link_layer = link_2.LinkLayer()
+ object_L.append(link_layer)
+
+ # add all the links - need to reflect the connectivity in cost_D tables above
+ link_layer.add_link(link_2.Link(host_1, 0, router_a, 0))
+ link_layer.add_link(link_2.Link(router_a, 0, host_1, 0))
+ link_layer.add_link(link_2.Link(router_a, 1, router_b, 0))
+ link_layer.add_link(link_2.Link(router_b, 1, host_2, 0))
+ link_layer.add_link(link_2.Link(router_b, 0, router_a, 1))
+ link_layer.add_link(link_2.Link(host_2, 0, router_b, 1))
+ # start all the objects
+ thread_L = []
+ for obj in object_L:
+ thread_L.append(threading.Thread(name=obj.__str__(), target=obj.run))
+ sys.stdout.write("\n")
+ for t in thread_L:
+ t.start()
+
+ # compute routing tables
+ router_a.send_routes(1) # one update starts the routing process
+ sleep(simulation_time) # let the tables converge
+ print("Converged routing tables")
+ # Table Header Bottom
+ for i in range(len(object_L)):
+ if str(type(object_L[i])) == "":
+ object_L[i].print_routes()
+
+ # send packet from host 1 to host 2
+ host_1.udt_send('H2', 'MESSAGE_FROM_H1')
+ host_2.udt_send('H1', 'MESSAGE FROM H2')
+ sleep(simulation_time)
+
+ # join all threads
+ for o in object_L:
+ o.stop = True
+ for t in thread_L:
+ t.join()
+
+ print("All simulation threads joined")
diff --git a/simulation_3.py b/simulation_3.py
new file mode 100644
index 0000000..70f01d0
--- /dev/null
+++ b/simulation_3.py
@@ -0,0 +1,72 @@
+import network_3
+import link_3
+import threading
+from time import sleep
+import sys
+
+# configuration parameters
+router_queue_size = 0 # 0 means unlimited
+simulation_time = 8 # give the network_3 sufficient time to execute transfers
+if __name__ == '__main__':
+ object_L = [] # keeps track of objects, so we can kill their threads at the end
+ # create network_3 hosts
+ host_1 = network_3.Host('H1')
+ host_2 = network_3.Host('H2')
+ # {neighbor: {interface: cost}}
+ cost_d_a = {'H1': {0: 5}, 'RB': {1: 4}, 'RC': {2: 1}}
+ cost_d_b = {'RA': {0: 3}, 'RD': {1: 1}}
+ cost_d_c = {'RA': {0: 1}, 'RD': {1: 3}}
+ cost_d_d = {'RB': {0: 1}, 'RC': {1: 1}, 'H2': {2: 5}}
+ router_a = network_3.Router('RA', cost_d_a, router_queue_size)
+ router_b = network_3.Router('RB', cost_d_b, router_queue_size)
+ router_c = network_3.Router('RC', cost_d_c, router_queue_size)
+ router_d = network_3.Router('RD', cost_d_d, router_queue_size)
+ object_L.append(host_1)
+ object_L.append(host_2)
+ object_L.append(router_a)
+ object_L.append(router_b)
+ object_L.append(router_c)
+ object_L.append(router_d)
+ # create a link_3 Layer to keep track of link_3s between network_3 nodes
+ link_layer = link_3.LinkLayer()
+ object_L.append(link_layer)
+
+ # add all the links - need to reflect the connectivity in cost_D tables above
+ link_layer.add_link(link_3.Link(host_1, 0, router_a, 0))
+ link_layer.add_link(link_3.Link(router_a, 1, router_b, 0))
+ link_layer.add_link(link_3.Link(router_a, 2, router_c, 0))
+ link_layer.add_link(link_3.Link(router_c, 1, router_d, 1))
+ link_layer.add_link(link_3.Link(router_b, 1, router_d, 0))
+ link_layer.add_link(link_3.Link(router_d, 2, host_2, 0))
+
+
+ # start all the objects
+ thread_L = []
+ for obj in object_L:
+ thread_L.append(threading.Thread(name=obj.__str__(), target=obj.run))
+ sys.stdout.write("\n")
+ for t in thread_L:
+ t.start()
+
+ # compute routing tables
+ router_d.send_routes(1) # one update starts the routing process
+ router_a.send_routes(1)
+ sleep(simulation_time) # let the tables converge
+ print("Converged routing tables")
+ # Table Header Bottom
+ for i in range(len(object_L)):
+ if str(type(object_L[i])) == "":
+ object_L[i].print_routes()
+
+ # send packet from host 1 to host 2
+ host_1.udt_send('H2', 'MESSAGE_FROM_H1')
+ host_2.udt_send('H1', 'MESSAGE FROM H2')
+ sleep(simulation_time)
+
+ # join all threads
+ for o in object_L:
+ o.stop = True
+ for t in thread_L:
+ t.join()
+
+ print("All simulation threads joined")
diff --git a/youtube_video.txt b/youtube_video.txt
new file mode 100644
index 0000000..2cdf379
--- /dev/null
+++ b/youtube_video.txt
@@ -0,0 +1 @@
+https://www.youtube.com/watch?v=YSov91NWGeQ&feature=youtu.be
\ No newline at end of file